目录
4.行为型模式
行为型模式用于描述程序在运行时复杂的流程控制,即描述多个类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,它涉及算法与对象间职责的分配。
行为型模式分为类行为模式和对象行为模式,前者采用继承机制来在类间分派行为,后者采用组合或聚合在对象间分配行为。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象行为模式比类行为模式具有更大的灵活性。
行为型模式中除了模板方法模式和解释器模式以外都是对象行为模式
1.模板方法模式
概述:定义一个算法结构,而将一些步骤延迟到子类实现。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
例如,去银行办理业务一般要经过以下4个流程:取号、排队、办理具体业务、对银行工作人员进行评分等,其中取号、排队和对银行工作人员进行评分的业务对每个客户是一样的,可以在父类中实现,但是办理具体业务却因人而异,它可能是存款、取款或者转账等,可以延迟到子类中实现。
结构:
- 抽象类(Abstract Class):负责给出一一个算法的轮廓和骨架。它由一个模板方法和若千个基本方法构成。
- 模板方法:定义了算法的骨架,按某种顺序调用其包含的基本方法。
- 基本方法:是实现算法各个步骤的方法,是模板方法的组成部分。基本方法又可以分为三种::
- 抽象方法 (Abstract Method):一个抽象方法由抽象类声明、 由其具体子类实现。
- 具体方法(Concrete Method):一个具体方法由-一个抽象类或具体类声明并实现,其子类可以进行覆盖也可以直接继承。
- 钩子方法(Hook Method):在抽象类中已经实现,包括用于判断的逻辑方法和需要子类重写的空方法两种。一般钩子方法是用于判断的逻辑方法, 这类方法名一般为i sXxx,返回值类型为boolean类型。
- 具体子类(Concrete Class):实现抽象类中所定义的抽象方法和钩子方法,它们是一个顶级逻辑的组成步骤。
优点:
- 提高代码复用性。将相同部分的代码放在抽象的父类中,而将不同的代码放入不同的子类中。
- 实现了反向控制。通过一个父类调用其子类的操作,通过对子类的具体实现扩展不同的行为,实现了反向控制, 并符合“开闭原则”。
缺点:
- 对每个不同的实现都需要定义一个子类,这会导致类的个数增加,系统更加庞大,设计也更加抽象。
- 父类中的抽象方法由子类实现,子类执行的结果会影响父类的结果,这导致一种反向的控制结构,它提高了代码阅读的难度。
使用场景:1、算法的整体步骤很固定,但其中个别部分易变时,这时候可以使用模板方法模式,将容易变的部分抽象出来,供子类实现。2、需要通过子类来决定父类算法中某个步骤是否执行,实现子类对父类的反向控制。
主要解决:一些方法通用,却在每一个子类都重新写了这一方法。
案例:炒菜
炒菜的步骤是固定的T分为倒油、热油、倒蔬菜、倒调料品、翻炒等步骤。现通过模板方法模式来用代码模拟。类图如下:
//抽象类:模板方法和基本方法
public abstract class AbstractClass {
//模板方法
public final void cookProcess(){
pourOil();
heatOil();
pourVegetable();
pourSauce();
fry();
}
//倒油:具体方法
public void pourOil(){
System.out.println("倒油");
}
//热油:具体方法
public void heatOil(){
System.out.println("热油");
}
//倒蔬菜是不一样的,(一个下包菜,一个下菜心):抽象方法
public abstract void pourVegetable();
//倒调味料:抽象方法
public abstract void pourSauce();
//翻炒:具体方法
public void fry(){
System.out.println("翻炒,炒熟");
}
}
//炒菜心类
public class ConcreteClass_Caixin extends AbstractClass{
@Override
public void pourVegetable() {
System.out.println("下锅的蔬菜是菜心");
}
@Override
public void pourSauce() {
System.out.println("下锅的酱料是蒜蓉");
}
}
//炒包菜类
public class ConcreteClass_Baocai extends AbstractClass{
@Override
public void pourVegetable() {
System.out.println("下锅的蔬菜是包菜");
}
@Override
public void pourSauce() {
System.out.println("下锅的酱料是辣椒");
}
}
public class Client {
public static void main(String[] args) {
//炒包菜
ConcreteClass_Baocai baocai=new ConcreteClass_Baocai();
//调用父类的炒菜功能
baocai.cookProcess();
System.out.println("==========");
ConcreteClass_Caixin caixin = new ConcreteClass_Caixin();
caixin.cookProcess();
}
}
2.策略模式
概述:该模式定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的变化不会影响使用算法的客户。
结构:
- 抽象策略(Strategy)类:这是一个抽象角色,通常由一个接口或抽象类实现。此角色给出所有的具体策略类所需的接口。
- 具体策略(Concrete Strategy)类:实现了抽象策略定义的接口,提供具体的算法实现或行为。
- 环境(Context)类:持有一个策略类的引用,最终给客户端调用。
优点:
- 策略类之间可以自由切换。由于策略类都实现同一一个接口,所以使它们之间可以自由切换。
- 易于扩展。增加一个新的策略只需要添加一个具体的策略类即可,基本不需要改变原有的代码,符合“开闭原则"
- 避免使用多重条件选择语句(if Telse),充分体现面向对象设计思想。
缺点:
- 客户端必须知道所有的策略类,并自行决定使用哪一个策略类。
- 策略模式将造成产生很多策略类,可以通过使用享元模式在一定程度上减少对象的数量。
使用场景:
- 一个系统需要动态地在几种算法中选择一种时,可将每个算法封装到策略类中。
- 一个类定义了多种行为,并且这些行为在这个类的操作中以多个条件语句的形式出现,可将每个条件分支移入它们各自的策略类中以代替这些条件语句。
- 系统中各算法彼此完全独立,且要求对客户隐藏具体算法的实现细节时。
- 系统要求使用算法的客户不应该知道其操作的数据时,可使用策略模式来隐藏与算法相关的数据结构。
- 多个类只区别在表现行为不同,可以使用策略模式,在运行时动态选择具体要执行的行为。
主要解决:在有多种算法相似的情况下,使用 if...else 所带来的复杂和难以维护。
案例:促销活动
//抽象策略类
public interface Strategy {
void show();
}
//具体策略类,封装算法
public class StrategyA implements Strategy{
@Override
public void show() {
System.out.println("买一送一");
}
}
//具体策略类,封装算法
public class StrategyB implements Strategy{
@Override
public void show() {
System.out.println("满200减50");
}
}
//具体策略类,封装算法
public class StrategyC implements Strategy{
@Override
public void show() {
System.out.println("满1000加1元换购一件200元以下商品");
}
}
//销售员:环境类
public class SalesMan {
//聚合策略类对象
private Strategy strategy;
public SalesMan(Strategy strategy) {
this.strategy = strategy;
}
//由促销员展示促销活动给用户
public void salesManShow(){
strategy.show();
}
public Strategy getStrategy() {
return strategy;
}
public void setStrategy(Strategy strategy) {
this.strategy = strategy;
}
}
public class Client {
public static void main(String[] args) {
//春节来了。使用春节促销活动
SalesMan salesMan=new SalesMan(new StrategyA());
//展示促销活动
salesMan.salesManShow();
System.out.println("===========");
//中秋节来了。使用中秋节促销活动
salesMan.setStrategy(new StrategyB());
//展示促销活动
salesMan.salesManShow();
System.out.println("===========");
//圣诞节来了。使用圣诞节促销活动
salesMan.setStrategy(new StrategyC());
//展示促销活动
salesMan.salesManShow();
}
}
3.命令模式
请求以命令的形式包裹在对象中,并传给调用对象。调用对象寻找可以处理该命令的合适的对象,并把该命令传给相应的对象,该对象执行命令。
概念:将一个请求封装成一个对象,从而使您可以用不同的请求对客户进行参数化。
结构:
- 抽象命令类(Comnand)角色:定义命令的接口,声明执行的方法。
- 具体命令(Concrete Command)角色:具体的命令,实现命令接口;通常会持有接收者,并调用接收者的功能来完成命令要执行的操作。
- 实现者/接收者(Receiver)角色:接收者,真正执行命令的对象。任何类都可能成为一个接收者,只要它能够实现命令要求实现的相应功能。
- 调用者/请求者(Invoker)角色:要求命令对象执行请求,通常会持有命令对象,可以持有很多的命令对象。这个是客户端真正触发命令并要求命令执行相应操作的地方,也就是说相当于使用命令对象的入口。
优点:
- 降低系统的耦合度。命令模式能将调用操作的对象与实现该操作的对象解耦。
- 增加或删除命令非常方便。采用命令模式增加与删除命令不会影响其他类,它满足“开闭原则”,对扩展比较灵活。
- 可以实现宏命令。命令模式可以与组合模式结合,将多个命令装配成一个组合命令, 即宏命令。
- 方便实现Undo和Redo操作。命令模式可以与后面介绍的备忘录模式结合,实现命令的撤销与恢复。
缺点:
- 使用命令模式可能会导致某些系统有过多的具体命令类。
- 系统结构更加复杂。
使用场景:
- 系统需要将请求调用者和请求接收者解耦,使得调用者和接收者不直接交互。
- 系统需要在不同的时间指定请求、将请求排队和执行请求。
- 系统需要支持命令的撤销(Undo)操作和恢复(Redo)操作。
主要解决:在软件系统中,行为请求者与行为实现者通常是一种紧耦合的关系,但某些场合,比如需要对行为进行记录、撤销或重做、事务等处理时,这种无法抵御变化的紧耦合的设计就不太合适。
案例:点餐案例
//订单类
public class Order {
//餐桌号码
private int diningTable;
//下单的餐具及份数
private Map<String, Integer> foodDir=new HashMap<>();
public int getDiningTable() {
return diningTable;
}
public void setDiningTable(int diningTable) {
this.diningTable = diningTable;
}
public Map<String, Integer> getFoodDir() {
return foodDir;
}
public void setFood(String name,int num) {
foodDir.put(name,num);
}
}
//厨师类:接收者
public class SeniorChef {
public void makeFood(String name,int num){
System.out.println(num+"份"+name);
}
}
//抽象命令类
public interface Command {
void execute();
}
//具体命令类
public class OrderCommand implements Command{
//持有接收者对象
private SeniorChef receiver;
//持有订单对象
private Order order;
//命令哪个厨师完成哪个订单
public OrderCommand(SeniorChef receiver, Order order) {
this.receiver = receiver;
this.order = order;
}
@Override
public void execute() {
System.out.println(order.getDiningTable()+"桌的订单:");
Map<String, Integer> foodDir = order.getFoodDir();
//遍历map集合
Set<String> keys = foodDir.keySet();
for (String foodName : keys) {
receiver.makeFood(foodName,foodDir.get(foodName));
}
System.out.println(order.getDiningTable()+"的饭准备完毕");
}
}
//服务员类:请求者类
public class Waitor {
//持有多个命令对象
private List<Command> commands=new ArrayList<>();
public void setCommand(Command cmd) {
//将cmd对象存储到list集合中
commands.add(cmd);
}
//发起命令的功能:喊一句订单来了
public void orderUp(){
System.out.println("美女服务员:大厨,新订单来了");
for (Command command : commands) {
if (command!=null){
command.execute();
}
}
}
}
public class Client {
public static void main(String[] args) {
//创建一个订单对象
Order order = new Order();
order.setDiningTable(1);
order.setFood("西红柿鸡蛋面",1);
order.setFood("小杯可乐",2);
//创建一个订单对象
Order order2 = new Order();
order2.setDiningTable(2);
order2.setFood("尖椒肉丝面",1);
order2.setFood("小杯雪碧",1);
//创建厨师对象
SeniorChef receiver=new SeniorChef();
//创建命令对象
OrderCommand cmd1=new OrderCommand(receiver,order);
OrderCommand cmd2=new OrderCommand(receiver,order2);
//创建调用者:服务员对象
Waitor invoke=new Waitor();
invoke.setCommand(cmd1);
invoke.setCommand(cmd2);
//让我们服务元发起命令
invoke.orderUp();
}
}
4.责任链模式
在现实生活中,常常会出现这样的事例:一个请求有多个对象可以处理,但每个对象的处理条件或权限不同。例如,公司员工请假,可批假的领导有部门负责人、副总经理、总经理等,但每个领导能批准的天数不同,员工必须根据自己要请假的天数去找不同的领导签名,也就是说员工必须记住每个领导的姓名、电话和地址等信息,这增加了难度。这样的例子还有很多,如找领导出差报销、生活中的“击鼓传花"游戏
概念:避免请求发送者与接收者耦合在一起,让多个对象都有可能接收请求,将这些对象连接成一条链,并且沿着这条链传递请求,直到有对象处理它为止。
结构:
- 抽象处理者(Handler)角色:定义一个处理请求的接口,包含抽象处理方法和一个后继连接。
- 具体处理者(Concrete Handler)角色: