五、行为型模式
-
**行为模式的定义:**行为型模式是对不同的对象之间划分职责和算法的抽象化。行为型模式定义了系统中对象之间的交互与通信,研究系统在运行时对象之间的相互通信与协作,进一步明确对象的职责,包括对系统中较为复杂的流程的控制。
-
行为模式的分类:
类行为型模式:使用继承关系在几个类之间分配行为,主要通过多态等方式来分配父类与子类的职责;
对象行为型模式:使用对象的聚合关联关系来分配行为,主要通过对象关联等方式来分配两个或多个类的职责。
5.1 命令模式
5.1.1 命令模式的定义
(描述对象之间的调用关系)
**1.模式动机:**将请求发送者和接收者完全解耦;发送者与接收者之间没有直接引用关系;发送请求的对象只需要知道如何发送请求,而不必知道如何完成请求
**2.模式定义:**将一个请求封装为一个对象,从而使我们可用不同的请求对客户进行参数化;对请求排队或者记录请求日志,以及支持可撤销的操作。
5.1.2 命令模式的结构与分析
//调用者
public class Invoker {private AbstractCommand ac;public Invoker(AbstractCommand ac) {this.ac = ac;}public void send() {ac.execute();}
}
//接收者
public class Receiver {public void action() {}
}
//抽象命令类
public abstract class AbstractCommand {public abstract void execute();
}
//具体命令类
public class ConcreteCommand extends AbstractCommand {private Receiver receiver = new Receiver();@Overridepublic void execute() {receiver.action();}
}
-
命令模式的本质是对请求进行封装,一个请求对应于一个命令,将发出命令的责任和执行命令的责任分开
-
将请求发送者和接收者完全解耦,使得请求的一方不必知道接收请求的一方的接口,更不必知道请求如何被接收、操作是否被执行、何时被执行,以及是怎么被执行的。
5.1.3 命令模式的案例
电视机是请求的接收者,遥控器是请求的发送者,遥控器上有一些按钮,不同的按钮对应电视机的不同操作。抽象命令角色由一个命令接口来扮演,有三个具体的命令类实现了抽象命令接口,这三个具体命令类分别代表三种操作:打开电视机、关闭电视机和切换频道。显然,电视机遥控器就是一个典型的命令模式应用实例。
public class TV {public void open() {System.out.println("打开电视");}public void change() {System.out.println("切换频道");}public void close() {System.out.println("关掉电视");}
}public interface Command {public void execute();
}public class OpenCommand implements Command{private TV tv = new TV();@Overridepublic void execute() {tv.open();}
}public class CloseCommand implements Command{private TV tv = new TV();@Overridepublic void execute() {tv.close();}
}public class ChangeCommand implements Command {private TV tv = new TV();@Overridepublic void execute() {tv.change();}
}public class Control {private Command openCommand;private Command changeCommand;private Command closeCommand;public Control(Command openCommand, Command changeCommand, Command closeCommand) {this.openCommand = openCommand;this.changeCommand = changeCommand;this.closeCommand = closeCommand;}public void open() {openCommand.execute();}public void change() {changeCommand.execute();}public void close() {closeCommand.execute();}
}public class Main {public static void main(String[] args) {Command openCommand = new OpenCommand();Command changeCommandCommand = new ChangeCommand();Command closeCommand = new CloseCommand();Control control = new Control(openCommand, changeCommandCommand, closeCommand);control.open();control.change();control.close();}
}
5.1.4 命令模式的优缺点
优点 | 缺点 |
---|---|
1.将系统的请求调用者和请求接收者解耦 | 1.使用命令模式可能会导致某些系统有过多的具体命令类 |
2.添加命令符合开闭原则 | |
3.可以比较容易地设计一个命令队列或宏命令(组合模式) | |
4.为请求的撤销(Undo)和恢复(Redo)操作提供了一种设计和实现方案 |
5.1.5 命令模式的适用场景
-
需要将请求调用者和请求接收者解耦,使得调用者和接收者不直接交互
-
需要在不同的时间指定请求、将请求排队和执行请求
-
需要支持命令的撤销(Undo)操作和恢复(Redo)操作
-
需要将一组操作组合在一起形成宏命令
5.2 迭代器模式
5.2.1 迭代器模式的定义
1.模式动机:如何访问一个聚合对象(用于存储多个对象)中的元素但又不需要暴露它的内部结构,还能提供多种不同的遍历方式
2.模式定义:提供一种方法来访问聚合对象,而不用暴露这个对象的内部表示。
5.2.2 迭代器模式的结构与分析
-
聚合对象的两个职责:存储数据,聚合对象的基本职责;遍历数据,既是可变化的,又是可分离的
-
将遍历数据的行为从聚合对象中分离出来,封装在迭代器对象中,由迭代器来提供遍历聚合对象内部数据的行为,简化聚合对象的设计,更符合单一职责原则
-
迭代器对象使用了工厂模式的设计思想,聚合类可以看作工厂模式,迭代器是产品类
实现方法一:由于集合和数组在聚合类中,迭代器需要使用它们,因此迭代器作为内部类出现
public interface Iterator {public void first();public void next();public boolean isLast();public Object currentItem();
}public abstract class Aggregate {public abstract Iterator createIterator();
}public class ConcreteAggregate extends Aggregate{private String[] strings;public ConcreteAggregate() {strings = new String[]{"d", "e", "f"};}@Overridepublic Iterator createIterator() {return new ConcreteIterator();}private class ConcreteIterator implements Iterator{private int index = 0;@Overridepublic void first() {index = 0;}@Overridepublic void next() {if (strings.length > index) {index++;} else {throw new RuntimeException("越界");}}@Overridepublic boolean isLast() {return index == strings.length;}@Overridepublic Object currentItem() {return strings[index];}}
}
实现方法二:可以在聚合类创建迭代器时,将自身传给迭代器并提供集合/数组的get方法,这样也可以实现
public interface Iterator {public void first();public void next();public boolean isLast();public Object currentItem();
}public abstract class Aggregate {public abstract Iterator createIterator();
}public class ConcreteAggregate extends Aggregate{private String[] strings;public ConcreteAggregate() {strings = new String[]{"a", "b", "c"};}public String[] getStrings() {return strings;}@Overridepublic Iterator createIterator() {return new ConcreteIterator(this);}
}public class ConcreteIterator implements Iterator{private ConcreteAggregate aggregate;private int index = 0;public ConcreteIterator(ConcreteAggregate aggregate) {this.aggregate = aggregate;}@Overridepublic void first() {index = 0;}@Overridepublic void next() {if (aggregate.getStrings().length > index) {index++;} else {throw new RuntimeException("越界");}}@Overridepublic boolean isLast() {return index == aggregate.getStrings().length;}@Overridepublic Object currentItem() {return aggregate.getStrings()[index];}
}public class Main {public static void main(String[] args) {Aggregate concreteAggregate = new ConcreteAggregate();Iterator iterator = concreteAggregate.createIterator();while (!iterator.isLast()) {System.out.println(iterator.currentItem());iterator.next();}}
}
5.2.3 迭代器模式的案例
电视机遥控器就是一个迭代器的实例,通过它可以实现对电视机频道集合的遍历操作,本实例我们将模拟电视机遥控器的实现。
- 迭代器类
public interface TVIterator {public void setChannel(int i);public Object currentChannel();public void next();public void previous();public boolean isLast();public boolean isFirst();
}public class SkyworthIterator implements TVIterator {private SkyworthTelevision skyworthTelevision;private int index = 0;public SkyworthIterator(SkyworthTelevision skyworthTelevision) {this.skyworthTelevision = skyworthTelevision;}@Overridepublic void setChannel(int i) {this.index = i;}@Overridepublic Object currentChannel() {return skyworthTelevision.getObj()[this.index];}@Overridepublic void next() {if (this.index < skyworthTelevision.getObj().length) {this.index++;}}@Overridepublic void previous() {if (this.index > 0) {this.index--;}}@Overridepublic boolean isLast() {return this.index == skyworthTelevision.getObj().length ;}@Overridepublic boolean isFirst() {return this.index == 0;}
}public class TCLIterator implements TVIterator{private TCLTelevision tclTelevision;private int index = 0;public TCLIterator(TCLTelevision tclTelevision) {this.tclTelevision = tclTelevision;}@Overridepublic void setChannel(int i) {this.index = i;}@Overridepublic Object currentChannel() {return tclTelevision.getObj()[this.index];}@Overridepublic void next() {if (this.index < tclTelevision.getObj().length) {this.index++;}}@Overridepublic void previous() {if (this.index > 0) {this.index--;}}@Overridepublic boolean isLast() {return this.index == tclTelevision.getObj().length;}@Overridepublic boolean isFirst() {return this.index == 0;}
}
- 电视类
public interface Television {public TVIterator createIterator();public Object[] getObj();
}public class SkyworthTelevision implements Television{private Object[] obj = {"CCTV-1","CCTV-2","CCTV-3","CCTV-4","CCTV-5","CCTV-6","CCTV-7","CCTV-8"};@Overridepublic TVIterator createIterator() {return new SkyworthIterator(this);}@Overridepublic Object[] getObj() {return obj;}
}public class TCLTelevision implements Television{private Object[] obj = {"CCTV-1","CCTV-2","CCTV-3","CCTV-4","CCTV-5","CCTV-6","CCTV-7","CCTV-8"};@Overridepublic TVIterator createIterator() {return new TCLIterator(this);}@Overridepublic Object[] getObj() {return obj;}
}
- 客户端
import org.w3c.dom.Document;import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.File;public class XMLUtil {public static Object getBean() {try {//获取XMLDocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();DocumentBuilder db = dbf.newDocumentBuilder();Document parse = db.parse(new File("src/main/java/com/tyut/text3/example1/tv.xml"));String tv = parse.getElementsByTagName("tv").item(0).getFirstChild().getNodeValue();//获取对象Class<?> aClass = Class.forName(tv);return aClass.newInstance();} catch (Exception e) {return new RuntimeException(e);}}
}<?xml version="1.0" encoding="UTF-8" ?>
<tv>com.tyut.text3.example1.TCLTelevision</tv>public class Main {public static void main(String[] args) {TCLTelevision bean = (TCLTelevision) XMLUtil.getBean();TVIterator iterator = bean.createIterator();//正序遍历System.out.println("正序播放电视节目:");while (!iterator.isLast()) {System.out.println(iterator.currentChannel());iterator.next();}//倒序遍历System.out.println("倒序播放电视节目:");iterator.setChannel(8);while (!iterator.isFirst()) {iterator.previous();System.out.println(iterator.currentChannel());}}
}
-
如果需要增加一个新的具体聚合类,只需增加一个新的聚合子类和一个新的具体迭代器类即可,原有类库代码无须修改,符合开闭原则
-
如果需要更换一个迭代器,只需要增加一个新的具体迭代器类作为抽象迭代器类的子类,重新实现遍历方法即可,原有迭代器代码无须修改,也符合开闭原则
5.2.4 迭代器模式的优缺点
优点 | 缺点 |
---|---|
1.支持以不同的方式遍历一个聚合对象,在同一个聚合对象上可以定义多种遍历方式,并且可以对同一个聚合对象同时多次遍历 | 1.类的个数成对增加,这在一定程度上增加了系统的复杂性 |
2.简化了聚合类,符合单一职责原则 | |
3.易扩展,符合开闭原则 |
5.2.5 迭代器模式的适用场景
-
访问一个聚合对象的内容而无须暴露它的内部表示
-
需要为一个聚合对象提供多种遍历方式
-
为遍历不同的聚合结构提供一个统一的接口,在该接口的实现类中为不同的聚合结构提供不同的遍历方式,而客户端可以一致性地操作该接口
5.3 观察者模式
5.3.1 观察者模式的定义
(描述对象之间的依赖关系)
1.模式动机:一个对象的状态或行为的变化将导致其他对象的状态或行为也发生改变。定义了对象之间一种一对多的依赖关系,让一个对象的改变能够影响其他对象,发生改变的对象称为观察目标,被通知的对象称为观察者。
2.模式定义:定义对象间的一种一对多依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象皆得到通知并被自动更新。
5.3.2 观察者模式的结构与分析
-
有时候在具体观察者类ConcreteObserver中需要使用到具体目标类ConcreteSubject中的状态(属性),会存在关联或依赖关系
-
如果在具体层之间具有关联关系,系统的扩展性将受到一定的影响,增加新的具体目标类有时候需要修改原有观察者的代码,在一定程度上违背了开闭原则,但是如果原有观察者类无须关联新增的具体目标,则系统扩展性不受影响
//观察者
public interface Observer {public void update();
}
public class ConcreteObserver implements Observer{@Overridepublic void update() {System.out.println("更新操作");}
}
//目标
public abstract class Subject {protected ArrayList<Observer> arrayList = new ArrayList<>();public void attach(Observer observer) {arrayList.add(observer);}public void detach(Observer observer) {arrayList.remove(observer);}public abstract void notice();
}
public class ConcreteSubject extends Subject{@Overridepublic void notice() {for (Observer observer : arrayList) {observer.update();}}
}
public class Main {public static void main(String[] args) {Observer concreteObserver1 = new ConcreteObserver();Observer concreteObserver2 = new ConcreteObserver();ConcreteSubject concreteSubject = new ConcreteSubject();concreteSubject.attach(concreteObserver1);concreteSubject.attach(concreteObserver2);concreteSubject.notice();}
}
5.3.3 观察者模式的案例
在某多人联机对战游戏中,多个玩家可以加入同一战队组成联盟,当战队中的某一成员受到敌人攻击时将给所有其他盟友发送通知,盟友收到通知后将做出响应。
public abstract class Observer {private String name;private int state;public Observer(String name) {this.name = name;this.state = 0;}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getState() {return state;}public void setState(int state) {this.state = state;}public abstract void help();public abstract void hurt(AbstractTeam team);
}
public class Player extends Observer{public Player(String name) {super(name);}@Overridepublic void help() {if (getState() == 0) System.out.println(getName() + "帮助受伤的队友");}@Overridepublic void hurt(AbstractTeam team) {setState(1);System.out.println(getName() + "受伤了呼叫" + team.getTeamName() + "队友的帮忙");team.notice();}
}
public abstract class AbstractTeam {private String teamName;protected ArrayList<Observer> observers = new ArrayList<>();public void add(Observer observer) {observers.add(observer);}public void remove(Observer observer) {observers.remove(observer);}public String getTeamName() {return teamName;}public void setTeamName(String teamName) {this.teamName = teamName;}public abstract void notice();
}
public class Team extends AbstractTeam {@Overridepublic void notice() {for (Observer observer : observers) {observer.help();}}
}
public class Main {public static void main(String[] args) {AbstractTeam team = new Team();team.setTeamName("战狼队");Player player1 = new Player("张三");Player player2 = new Player("李四");Player player3 = new Player("王五");Player player4 = new Player("赵六");team.add(player1);team.add(player2);team.add(player3);team.add(player4);player1.hurt(team);}
}
5.3.4 观察者模式的优缺点
优点 | 缺点 |
---|---|
1.可以实现表示层和数据逻辑层的分离 | 1.将所有的观察者都通知到会花费很多时间 |
2.在观察目标和观察者之间建立一个抽象的耦合,符合开闭原则,增加新的具体观察者无须修改原有系统代码 | 2.如果存在循环依赖时可能导致系统崩溃 |
3.ü支持广播通信,简化了一对多系统设计的难度 | 3.没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而只是知道观察目标发生了变化 |
5.3.5 观察者模式的适用场景
-
一个抽象模型有两个方面,其中一个方面依赖于另一个方面,将这两个方面封装在独立的对象中使它们可以各自独立地改变和复用
-
一个对象的改变将导致一个或多个其他对象发生改变,且并不知道具体有多少对象将发生改变,也不知道这些对象是谁
-
需要在系统中创建一个触发链,A对象影响B对象,B对象影响C对象。