设计模式个人解读3(行为型模式)
创建型模式请查看:https://blog.csdn.net/zyfhhhw/article/details/108650710
结构型模式请查看:https://blog.csdn.net/zyfhhhw/article/details/108749056
文章目录
目录
四、职责链(Chain of Responsibility)模式
(3)行为型模式(11种)
行为型模式:用于描述类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,以及怎样分配职责。
行为型模式分为以下 11 种:
- 模板方法(Template Method)模式:定义一个操作中的算法骨架,将算法的一些步骤延迟到子类中,使得子类在可以不改变该算法结构的情况下重定义该算法的某些特定步骤。
- 策略(Strategy)模式:定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的改变不会影响使用算法的客户。
- 命令(Command)模式:将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。
- 职责链(Chain of Responsibility)模式:把请求从链中的一个对象传到下一个对象,直到请求被响应为止。通过这种方式去除对象之间的耦合。
- 状态(State)模式:允许一个对象在其内部状态发生改变时改变其行为能力。
- 观察者(Observer)模式:多个对象间存在一对多关系,当一个对象发生改变时,把这种改变通知给其他多个对象,从而影响其他对象的行为。
- 中介者(Mediator)模式:定义一个中介对象来简化原有对象之间的交互关系,降低系统中对象间的耦合度,使原有对象之间不必相互了解。
- 迭代器(Iterator)模式:提供一种方法来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示。
- 访问者(Visitor)模式:在不改变集合元素的前提下,为一个集合中的每个元素提供多种访问方式,即每个元素有多个访问者对象访问。
- 备忘录(Memento)模式:在不破坏封装性的前提下,获取并保存一个对象的内部状态,以便以后恢复它。
- 解释器(Interpreter)模式:提供如何定义语言的文法,以及对语言句子的解释方法,即解释器。
除了模板方法模式和解释器是类行为型模式,其他的全部属于对象行为型模式。
一、模板方法模式
模板方法(Template Method)概念:定义一个操作中的算法骨架,将算法的一些步骤延迟到子类中,使得子类在可以不改变该算法结构的情况下重定义该算法的某些特定步骤。
模板方法在现实生活中的例子有很多,例如,一个人每天会起床、吃饭、做事、睡觉等,其中“做事”的内容每天可能不同。我们把这些规定了流程或格式的实例定义成模板,允许使用者根据自己的需求去更新它。
主要处理方式:一些方法通用,却在每一个子类都重新写了这一方法,我们将这些通用算法抽象出来,在抽象类实现,其他步骤在子类实现。
1、模板方法模式的优缺点
优点:
- 它封装了不变部分,扩展可变部分。它把认为是不变部分的算法封装到父类中实现,而把可变部分算法由子类继承实现,便于子类继续扩展。
- 它在父类中提取了公共的部分代码,便于代码复用。
- 部分方法是由子类实现的,因此子类可以通过扩展方式增加相应的功能,符合开闭原则。
缺点:
- 对每个不同的实现都需要定义一个子类,这会导致类的个数增加,系统更加庞大,设计也更加抽象。
- 父类中的抽象方法由子类实现,子类执行的结果会影响父类的结果,这导致一种反向的控制结构,它提高了代码阅读的难度。
2、模板方法模式的实例
抽象父类(AbstractClass):实现了模板方法,定义了算法的骨架。
具体类(ConcreteClass):实现抽象类中的抽象方法,即不同的对象的具体实现细节。
我们使用一个玩儿游戏的例子进行具体讲解,首先创建一个定义操作的 Game 抽象类,然后创建一个Football ,它扩展了 Game 的实体类,是Game的子类,然后进行后续测试。
package com.test;
/**
*模板模式
**/
public class TemplateDemo {
//抽象类
public static abstract class Game{
//定义抽象方法
//初始化方法
abstract void initialize();
//开始方法
abstract void startPlay();
//结束方法
abstract void endPlay();
//我们定义了一个play方法,这个方法是final的,它不会被重写。
public final void play(){
this.initialize();
this.startPlay();
this.endPlay();
}
}
//继承了Game抽象类
public static class Football extends Game{
@Override
void initialize() {
System.out.println("游戏初始化");
}
@Override
void startPlay() {
System.out.println("游戏开始");
}
@Override
void endPlay() {
System.out.println("游戏结束");
}
}
//测试。
public static void main(String[] args) {
Game game =new Football();
game.play();
}
}
从例子中可以看出。我们将共同的方法提取出来,然后定一个了通用的抽象类,这个抽象类我们可以看做一个是一个模板,子类是以这个模板我基础而创建的,所以我们叫这种模式为模板模式。
二、策略(Strategy)模式
策略(Strategy)模式:定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的改变不会影响使用算法的客户。
在现实生活中常常遇到实现某种目标存在多种策略可供选择的情况,比如我们想从北京到上海,我们可以选择乘坐火车、乘坐飞机、亦或者自己开车,要是你喜欢骑行也是可以的,你喜欢就好。同样的,在软件开发中,我们也常常遇到类似的事情,我们可以根据环境或者条件的不同选择不同的算法或者策略来完成某些功能,如数据排序策略有冒泡排序、选择排序、插入排序、二叉树排序等。如果使用if...else的模式,会使程序变的复杂并且难以维护,这是违背开闭原则的。在这种情况下,策略模式应运而生。
1、策略模式的优缺点
优点:
- 避免使用多重条件判断
- 策略模式提供了一系列的可供重用的算法族,恰当使用继承可以把算法族的公共代码转移到父类里面,从而避免重复的代码。
- 策略模式提供了对开闭原则的完美支持,可以在不修改原代码的情况下,灵活增加新算法。
- 策略模式把算法的使用放到环境类中,而算法的实现移到具体策略类中,实现了二者的分离。
缺点:
- 客户端必须理解所有策略算法的区别,以便适时选择恰当的算法类。
- 策略模式造成很多的策略类。
2、策略模式实例
我们创建一个抽象策略定义的接口Strategy,一般情况下各种不同的算法以不同的方式实现这个接口。环境角色使用这个接口调用不同的算法,定义一个具体策略类ConcreteStrategyA,当然我们可以多定义一个ConcreteStrategyB 的具体策略实现类(具体策略类就是以后我们可以扩展的类,开发者可以自行增加新功能点),需要注意的是,这些类实现了抽象策略定义的接口,提供具体的算法实现,最后定义一个Environment环境类,内部会持有一个抽象策略的引用。
代码实现如下所示:
package com.test;
/**
*策略模式
**/
public class StrategyDemo {
//定义抽象策略接口
interface Strategy
{
public void strategyMethod();
}
//具体策略类A
public static class ConcreteStrategyA implements Strategy{
@Override
public void strategyMethod() {
System.out.println("现在在访问具体策略类A。");
}
}
//具体策略类B
public static class ConcreteStrategyB implements Strategy{
@Override
public void strategyMethod() {
System.out.println("你们访问的是策略类B。");
}
}
//环境类
public static class Environment {
private Strategy strategy;
//设置
public void setStrategy(Strategy strategy){
this.strategy=strategy;
}
public void strategyMethod(){
strategy.strategyMethod();
}
}
//测试
public static void main(String[] args) {
//创建环境的对象。
Environment environment =new Environment();
Strategy strategy = new ConcreteStrategyA();
//設置策略实体类A
environment.setStrategy(strategy);
//执行A里面的方法
environment.strategyMethod();
System.out.println("===================");
strategy = new ConcreteStrategyB();
environment.setStrategy(strategy);
environment.strategyMethod();
}
}
三、命令(Command)模式
命令模式在生活中的运用实例也不少,比如电视机遥控器(命令发送者)通过按钮(具体命令)来遥控电视机(命令接收者)。我们日常开发也是经常使用到该模式,通过键盘输入具体命令来控制程序运行的模式等等。
命令(Command)模式概念:将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。
主要解决:在某些场合,比如要对行为进行"记录、撤销/重做、事务"等处理,这种无法抵御变化的紧耦合是不合适的。在这种情况下,如何将"行为请求者"与"行为实现者"解耦?将一组行为抽象为对象,可以实现二者之间的松耦合。
解决方式:通过调用者调用接受者执行命令,顺序:调用者→命令→接受者。
1、命令模式优缺点
优点:
- 降低系统的耦合度。命令模式能将调用操作的对象与实现该操作的对象解耦。
- 增加或删除命令非常方便。采用命令模式增加与删除命令不会影响其他类,它满足“开闭原则”,对扩展比较灵活。
- 可以实现宏命令。命令模式可以与组合模式结合,将多个命令装配成一个组合命令,即宏命令。
缺点:
- 可能产生大量具体命令类。因为计对每一个具体操作都需要设计一个具体命令类,这将增加系统的复杂性。
2、命令模式实例
我们创建调用者类Invoker,调用者调用命令,然后创建抽象命令类command,抽象命令类一定有具体的实现命令类,我们具体命令类为ConcreteCommand,命令一定是要有类去接收的并执行的,所以我们还需要创建一个接收命令的类Receiver。
上面的图示请根据描述看一下。里面说明了类之间的调用是如何关联起来的。下面我们进行代码测试。
package com.test;
/**
* 命令模式
**/
public class CommandPatternDemo {
//创建调用者
public static class Invoker{
private Command command;
//构造方法
public Invoker(Command command) {
this.command = command;
}
public void setCommand(Command command){
this.command=command;
}
//该方法让程序执行命令
public void call(){
System.out.println("调用者执行命令command...");
command.execute();
}
}
//创建抽象命令类
public interface Command{
public abstract void execute();
}
//具体命令实行类
public static class ConcreteCommand implements Command{
//接收者
private Receiver receiver;
//实例化该类的时候,直接创建了接收者的类。
public ConcreteCommand() {
this.receiver = new Receiver();
}
@Override
public void execute() {
receiver.action();
}
}
//接收者类
public static class Receiver{
public void action(){
System.out.println("接收者的action()方法被调用...");
}
}
//测试
public static void main(String[] args) {
//创建调用者和要执行的具体命令。
Invoker invoker = new Invoker(new ConcreteCommand());
invoker.call();
}
}
四、职责链(Chain of Responsibility)模式
在日常生活中,比如我们员工有事了,需要请假,可批假的领导有部门负责人、副总经理、总经理等,但每个领导能批准的天数不同,员工必须根据自己要请假的天数去找不同的领导签名,也就是说员工必须记住每个领导的姓名、电话和地址等信息,这增加了难度。这种例子很多,比如我们玩儿过的游戏“击鼓传花”,不知道这个游戏的可以百度一下;比如我们出差报销费用等等。
概念:一个请求有多个对象可以处理,但每个对象的处理条件或权限不同。把请求从链中的一个对象传到下一个对象,直到请求被响应为止。通过这种方式去除对象之间的耦合。
主要解决:职责链上的处理者负责处理请求,客户只需要将请求发送到职责链上即可,无须关心请求的处理细节和请求的传递,所以职责链将请求的发送者和请求的处理者解耦了。
1、责任链模式优缺点
优点:
- 降低了对象之间的耦合度。该模式使得一个对象无须知道到底是哪一个对象处理其请求以及链的结构,发送者和接收者也无须拥有对方的明确信息。
- 增强了系统的可扩展性。可以根据需要增加新的请求处理类,满足开闭原则。
- 增强了给对象指派职责的灵活性。当工作流程发生变化,可以动态地改变链内的成员或者调动它们的次序,也可动态地新增或者删除责任。
- 责任链简化了对象之间的连接。每个对象只需保持一个指向其后继者的引用,不需保持其他所有处理者的引用,这避免了使用众多的 if 或者 if···else 语句。
- 责任分担。每个类只需要处理自己该处理的工作,不该处理的传递给下一个对象完成,明确各类的责任范围,符合类的单一职责原则。
缺点:
- 不能保证每个请求一定被处理。由于一个请求没有明确的接收者,所以不能保证它一定会被处理,该请求可能一直传到链的末端都得不到处理。
- 对比较长的职责链,请求的处理可能涉及多个处理对象,系统性能将受到一定影响。
- 职责链建立的合理性要靠客户端来保证,增加了客户端的复杂性,可能会由于职责链的错误设置而导致系统出错,如可能会造成循环调用。
2、责任链模式实例
创建抽象处理者类Handler,有抽象处理着,就需要有继承了抽象处理者类的具体的处理者(ConcretHandlerA、ConcretHandlerB),最后我们使用ChainOfResponsibilityDemo进行测试,在main方法中中,我们组装了一下责任链的链条。
下面是代码逻辑讲解,我们需要理解这个链是怎么关联起来的。
package com.test;
/**
* 责任链模式
**/
public class ChainOfResponsibilityDemo {
//创建抽象处理者
public static abstract class Handler{
//这个其实有个逻辑就是当前链和下一个链的关联关系。
private Handler next;
//设置下一个链
public void setNext(Handler next)
{
this.next=next;
}
//得到下一个链
public Handler getNext()
{
return next;
}
//处理请求的方法
public abstract void handleRequest(String request);
}
//具体处理者A
public static class ConcreteHandlerA extends Handler{
//抽象的方法要实现
@Override
public void handleRequest(String request) {
if(request.equals("one")){
System.out.println("具体处理者A处理该请求!");
}else{
//如果不是指定的处理者A处理,那就继续向下找,看看下一个处理者是否存在,如果存在,看一下是不是它处理,依次类推。
if(getNext()!=null){
//调用B的方法
getNext().handleRequest(request);
} else {
System.out.println("没有人处理该请求!");
}
}
}
}
//具体处理者B
public static class ConcreteHandlerB extends Handler{
//实现抽象的方法,A处调用了该方法。
@Override
public void handleRequest(String request) {
if(request.equals("two")){
System.out.println("具体处理者B进行处理!");
}else {
//如果也不是B进行处理,看看是不是有下一个处理者,如果有就继续。
if(getNext()!=null){
getNext().handleRequest(request);
}else{//没有处理者就提示没有。
System.out.println("没有人处理该请求!");
}
}
}
}
public static void main(String[] args) {
//组装责任链
Handler handlerA = new ConcreteHandlerA();
Handler handlerB = new ConcreteHandlerB();
//A的下一个是B
handlerA.setNext(handlerB);
//然后提交一个请求,测试
handlerA.handleRequest("two");
//handlerA.handleRequest("three");
}
}
另外职责链模式存在以下两种情况。
- 纯的职责链模式:一个请求必须被某一个处理者对象所接收,且一个具体处理者对某个请求的处理只能采用以下两种行为之一:自己处理(承担责任);把责任推给下家处理。
- 不纯的职责链模式:允许出现某一个具体处理者对象在承担了请求的一部分责任后又将剩余的责任传给下家的情况,且一个请求可以最终不被任何接收端对象所接收。
具体的情况,需要具体分析,我们现在主要是理解何谓责任链模式,至于实际应用需要结合实际情况而定。
五、状态(State)模式
在软件开发过程中,应用程序中的有些对象可能会根据不同的情况做出不同的行为,我们把这种对象称为有状态的对象,而把影响对象行为的一个或多个动态变化的属性称为状态。当有状态的对象与外部事件产生互动时,其内部状态会发生改变,从而使得其行为也随之发生改变。比如我们从公司回家,我们把自己看成一个有状态的对象,如果我们加班儿,那下班儿后我们就乘坐公共汽车回家,如果我们不加班儿,我们就骑自行车回去,我们把加班儿和不加班儿看成是状态的变化;又比如打篮球的时候,运动员可以有正常状态、不正常状态和超常状态。
概念:对有状态的对象,把复杂的“判断逻辑”提取到不同的状态对象中,允许状态对象在其内部状态发生改变时改变其行为。状
1、状态模式优缺点
优点:
- 状态模式将与特定状态相关的行为局部化到一个状态中,并且将不同状态的行为分割开来,满足“单一职责原则”。
- 减少对象间的相互依赖。将不同的状态引入独立的对象中会使得状态转换变得更加明确,且减少对象间的相互依赖。
- 有利于程序的扩展。通过定义新的子类很容易地增加新的状态和转换。
缺点:
- 状态模式的使用必然会增加系统的类与对象的个数。
- 状态模式的结构与实现都较为复杂,如果使用不当会导致程序结构和代码的混乱。
- 状态模式对"开闭原则"的支持不太好。
2、状态模式实例
我们创建一个接口State,创建实现了这个接口的具体状态类StartState和StopState,创建环境类Context,将与状态相关的操作委托给该对象来处理。
下面是具体的代码示例,我们需要关注的是这个Context环境类是如何实现该状态逻辑的。
package com.test;
/**
* 状态模式
*
**/
public class StatePatternDemo {
//创建一个状态接口
public interface State {
public void doAction(Context context);
}
//创建实现接口的实体类StartState
public static class StartState implements State {
@Override
public void doAction(Context context) {
System.out.println("正在处于开始状态");
context.setState(this);
}
public String toString(){
return "开始的状态。";
}
}
//创建实现接口的实体类StopState
public static class StopState implements State {
public void doAction(Context context) {
System.out.println("已经处于结束状态");
context.setState(this);
}
public String toString(){
return "结束状态";
}
}
//创建Context类
public static class Context {
//包含状态的对象。
private State state;
public Context(){
state = null;
}
//设置状态对象
public void setState(State state){
this.state = state;
}
//得到当前状态对象
public State getState(){
return state;
}
}
//测试
public static void main(String[] args) {
//使用 Context 来查看当状态 State 改变时的行为变化。
Context context = new Context();
//新建一个开始状态对象
State startState = new StartState();
//执行了这个方法,意思就是给context对象传递了一个状态对象。
startState.doAction(context);
//我们使用Context里面的状态对象查看一下当前状态
System.out.println(context.getState().toString());
System.out.println("=================");
StopState stopState = new StopState();
stopState.doAction(context);
System.out.println(context.getState().toString());
}
}
六、观察者(Observer)模式
我们说,在日常生活中,很多事物都不是单独存在的,一个对象发生了变化将会影响很多其他对象都发生相应的一些变化。比我我们说你的工资进行了调整,随之而来的是你的五险一金也会发生相应的调整,你给老婆上交的钱也会增多(^-^)。这样的例子还有很多,自己可以联想一下。
概念:多个对象间存在一对多关系,当一个对象发生改变时,所有依赖于它的对象都将得到通知并被自动更新,从而影响其他对象的行为。这种模式有时又称作发布-订阅模式、模型-视图模式。
主要解决:一个对象状态改变给其他对象通知的问题,而且要考虑到易用和低耦合,保证高度的协作。
解决方式:使用面向对象技术,可以将这种依赖关系弱化。
1、观察者模式优缺点
优点:
- 降低了目标与观察者之间的耦合关系,两者之间是抽象耦合关系。
- 目标与观察者之间建立了一套触发机制。
缺点:
- 目标与观察者之间的依赖关系并没有完全解除,而且有可能出现循环引用。
- 当观察者对象很多时,通知的发布会花费很多时间,影响程序的效率。
2、观察者模式实例
我们创建一个Observer的观察者接口,创建实现了该接口的具体观察者ConcreteObserver1和ConcreteObserver2,创建一个抽象目标类Subject,它提供了一个用于保存观察者对象的聚集类和增加、删除观察者对象的方法,以及通知所有观察者的抽象方法。创建一个继承了该类的具体目标类ConcreteSubject。
具体代码实现我们如下:
package com.test;
import java.util.ArrayList;
import java.util.List;
/**
* 观察者模式
**/
public class ObserverDemo {
//创建一个抽象观察者
public interface Observer{
void response();
}
//具体观察者1
static class ConcreteObserver1 implements Observer{
@Override
public void response() {
System.out.println("具体观察者1的连锁反应!");
}
}
//具体观察者2
static class ConcreteObserver2 implements Observer{
@Override
public void response() {
System.out.println("具体观察者2受到影响!");
}
}
//创建抽象目标类
public static abstract class Subject{
//创建观察者的集合对象
protected List<Observer> observers=new ArrayList<Observer>();
//通知观察者方法
public abstract void notifyObserver();
//增加观察者方法
public void add(Observer observer){
observers.add(observer);
}
//删除观察者方法
public void remove(Observer observer){
observers.remove(observer);
}
}
//创建具体目标类
public static class ConcreteSubject extends Subject{
@Override
public void notifyObserver() {
System.out.println("具体目标发生改变...");
//当目标类发生改变,观察者类会受到相应影响。
for(Object obs:observers){
((Observer)obs).response();
}
}
}
//测试
public static void main(String[] args) {
//创建抽象目标对象
Subject subject=new ConcreteSubject();
//具体观察者1
Observer obs1=new ConcreteObserver1();
//具体观察者2
Observer obs2=new ConcreteObserver2();
//将观察者放到目标类的集合中。
subject.add(obs1);
subject.add(obs2);
//当目标类发生改变,观察者也发生相应变化。
subject.notifyObserver();
}
}
扩展
在Java中,通过 java.util.Observable 类和 java.util.Observer 接口定义了观察者模式,只要实现它们的子类就可以编写观察者模式实例。
1. Observable类(就是例子中的Subject)
分析:Observable 类是抽象目标类,它有一个 Vector 向量,用于保存所有要通知的观察者对象,下面来介绍它最重要的 3 个方法。
- void addObserver(Observer o) 方法:用于将新的观察者对象添加到向量中。
- void notifyObservers(Object arg) 方法:调用向量中的所有观察者对象的 update。方法,通知它们数据发生改变。通常越晚加入向量的观察者越先得到通知。
- void setChange() 方法:用来设置一个 boolean 类型的内部标志位,注明目标对象发生了变化。当它为真时,notifyObservers() 才会通知观察者。
2. Observer 接口(对应于例子中的Observer接口)
分析:Observer 接口是抽象观察者,它监视目标对象的变化,当目标对象发生变化时,观察者得到通知,并调用 void update(Observable o,Object arg) 方法,进行相应的工作。
七、中介者(Mediator)模式
在现实生活中,常常会出现好多对象之间存在复杂的交互关系,这种交互关系常常是“网状结构”,它要求每个对象都必须知道它需要交互的对象。假如我们没手机,我们需要用脑子记住所有朋友的电话;而且,朋友中如果有人的电话修改了,他(她)必须告诉其他所有的朋友修改,这叫作“牵一发而动全身”,非常复杂。如果把这种“网状结构”改为“星形结构”的话,将大大降低它们之间的“耦合性”,这时只要找一个“中介者”就可以了。如前面所说的“每个人必须记住所有朋友电话”的问题,只要在网上建立一个每个朋友都可以访问的“通信录”就解决了。这样的例子还有很多,例如,你刚刚参加工作想租房,可以找“房屋中介”;或者,自己刚刚到一个陌生城市找工作,可以找“人才交流中心”帮忙。
概念:定义一个中介对象来简化原有对象之间的交互关系,降低系统中对象间的耦合度,使原有对象之间不必相互了解。中介者模式又叫调停模式,它是迪米特法则的典型应用。
主要解决:对象与对象之间存在大量的关联关系,这样势必会导致系统的结构变得很复杂,同时若一个对象发生改变,我们也需要跟踪与之相关联的对象,同时做出相应的处理。
解决方式:多个类相互耦合,形成了网状结构,将上述网状结构分离为星型结构。
1、中介者模式优缺点
优点:
- 降低了对象之间的耦合性,使得对象易于独立地被复用。
- 将对象间的一对多关联转变为一对一的关联,提高系统的灵活性,使得系统易于维护和扩展。
缺点:
- 当同事类太多时,中介者的职责将很大,它会变得复杂而庞大,以至于系统难以维护。
2、中介者模式实例
我们通过聊天室实例来演示中介者模式。实例中,多个用户可以向聊天室发送消息,聊天室向所有的用户显示消息。我们将创建两个类 ChatRoom 和 User。User 对象使用 ChatRoom 方法来分享他们的消息。
我们列出代码实例:
package com.test;
import java.util.Date;
/**
* 中介者模式
**/
public class MediatorPatternDemo {
//创建User
public static class User {
//姓名
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
//构造方法
public User(String name){
this.name = name;
}
//发送信息,这里使用ChatRoom进行的操作
public void sendMessage(String message){
ChatRoom.showMessage(this,message);
}
}
//创建ChatRoom类
public static class ChatRoom {
public static void showMessage(User user, String message){
System.out.println(new Date().toString()+ " [" + user.getName() +"] : " + message);
}
}
//创建测试用例
public static void main(String[] args) {
//这是两个用户的通话
User Zhang = new User("张三");
User Li = new User("李四");
Zhang.sendMessage("Hi! Li!");
Li.sendMessage("Hello! Zhang!");
}
}
八、迭代器(Iterator)模式
在现实生活以及程序设计中,经常要访问一个聚合对象中的各个元素,通常的做法是将链表的创建和遍历都放在同一个类中,但这种方式不利于程序的扩展,如果要更换遍历方法就必须修改程序源代码,这违背了 “开闭原则”。
既然将遍历方法封装在聚合类中不可取,那么聚合类中不提供遍历方法,将遍历方法由用户自己实现是否可行呢?答案是同样不可取,因为这种方式会存在两个缺点:
- 暴露了聚合类的内部表示,使其数据不安全;
- 增加了客户的负担。
“迭代器模式”能较好地克服以上缺点,它在客户访问类与聚合类之间插入一个迭代器,这分离了聚合对象与其遍历行为,对客户也隐藏了其内部细节,且满足“单一职责原则”和“开闭原则”。
概念:提供一种方法来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示。它是 Java 和 .Net 编程环境中非常常用的设计模式。这种模式用于顺序访问集合对象的元素,不需要知道集合对象的底层表示。
主要解决:不同的方式来遍历整个整合对象。
如何解决:遍历一个聚合对象,把在元素之间游走的责任交给迭代器,而不是聚合对象。
1、迭代器模式优缺点
优点:
- 访问一个聚合对象的内容而无须暴露它的内部表示。
- 遍历任务交由迭代器完成,这简化了聚合类。
- 它支持以不同方式遍历一个聚合,甚至可以自定义迭代器的子类以支持新的遍历。
- 增加新的聚合类和迭代器类都很方便,无须修改原有代码。
- 封装性良好,为遍历不同的聚合结构提供一个统一的接口。
缺点:
- 增加了类的个数,这在一定程度上增加了系统的复杂性。
2、迭代器模式实例
我们创建一个 Iterator 接口,创建返回迭代器的 Container 接口和实现了该接口的实体类NameRepository,该类有实现了 Iterator 接口的内部类 NameIterator。实现了 Container 接口的实体类将负责实现 Iterator 接口。
代码逻辑如下:
package com.test;
/**
* 迭代器模式
**/
public class IteratorPatternDemo {
//创建Iterator接口
public interface Iterator {
public boolean hasNext();
public Object next();
}
//创建Container接口
public interface Container {
//返回迭代器
public Iterator getIterator();
}
//创建实现了 Container 接口的实体类NameRepository,该类有实现了 Iterator 接口的内部类 NameIterator
public static class NameRepository implements Container {
//姓名数组
public String names[] = {"张三" , "李四" ,"王五" , "赵六"};
@Override
public Iterator getIterator() {
return new NameIterator();
}
//内部类
private class NameIterator implements Iterator {
int index;
@Override
public boolean hasNext() {
//遍历,查看是否还有下一个,有的话就可以继续循环,否则跳出。
if(index < names.length){
return true;
}
return false;
}
@Override
public Object next() {
if(this.hasNext()){
return names[index++];
}
return null;
}
}
}
//测试
public static void main(String[] args) {
//创建可以返回迭代器的对象
NameRepository namesRepository = new NameRepository();
//遍历集合。
for(Iterator iter = namesRepository.getIterator(); iter.hasNext();){
String name = (String)iter.next();
System.out.println("Name : " + name);
}
}
}
九、访问者(Visitor)模式
在现实生活中,有些集合对象中存在多种不同的元素,且每种元素也存在多种不同的访问者和处理方式。例如,电影或电视剧中的人物角色,不同的观众对他们的评价也不同;还有顾客在商场购物时放在“购物车”中的商品,顾客主要关心所选商品的性价比,而收银员关心的是商品的价格和数量。这些被处理的数据元素相对稳定而访问方式多种多样的数据结构,如果用“访问者模式”来处理就十分便捷。访问者模式能把处理方法从数据结构中分离出来,并可以根据需要增加新的处理方法,且不用修改原来的程序代码与数据结构,这提高了程序的扩展性和灵活性。
概念:在不改变集合元素的前提下,为一个集合中的每个元素提供多种访问方式,即每个元素有多个访问者对象访问。它将对数据的操作与数据结构进行分离,是行为类模式中最复杂的一种模式。
1、访问者模式优缺点
优点:
- 扩展性好。能够在不修改对象结构中的元素的情况下,为对象结构中的元素添加新的功能。
- 复用性好。可以通过访问者来定义整个对象结构通用的功能,从而提高系统的复用程度。
- 灵活性好。访问者模式将数据结构与作用于结构上的操作解耦,使得操作集合可相对自由地演化而不影响系统的数据结构。
- 符合单一职责原则。访问者模式把相关的行为封装在一起,构成一个访问者,使每一个访问者的功能都比较单一。
缺点:
- 增加新的元素类很困难。在访问者模式中,每增加一个新的元素类,都要在每一个具体访问者类中增加相应的具体操作,这违背了“开闭原则”。
- 破坏封装。访问者模式中具体元素对访问者公布细节,这破坏了对象的封装性。
- 违反了依赖倒置原则。访问者模式依赖了具体类,而没有依赖抽象类。
2、访问者模式实例
我们定义一个表示元素的抽象接口ComputerPart ,定义具体元素Keyboard、Mouse、Monitor 和 Computer ,它们都是实现了 ComputerPart 接口的实体类。接着我们定义一个抽象访问者接口 ComputerPartVisitor,它主要是体现访问者类的操作,定义一个实体访问者ComputerPartDisplayVisitor ,它实现了ComputerPartVisitor的接口。
下面我们用代码实例进行讲解
package com.test;
/**
* 访问者模式
**/
public class VisitorDemo {
//定义一个元素的抽象接口
public interface ComputerPart {
public void accept(ComputerPartVisitor computerPartVisitor);
}
//定义几个实现了上面接口的具体元素
public static class Keyboard implements ComputerPart {
//用访问者类进行访问
@Override
public void accept(ComputerPartVisitor computerPartVisitor) {
computerPartVisitor.visit(this);
}
}
//同上
public static class Monitor implements ComputerPart {
@Override
public void accept(ComputerPartVisitor computerPartVisitor) {
computerPartVisitor.visit(this);
}
}
//同上
public static class Mouse implements ComputerPart {
@Override
public void accept(ComputerPartVisitor computerPartVisitor) {
computerPartVisitor.visit(this);
}
}
//对象结构类,它是一个包含元素的容器,提供让访问者对象遍历容器中的所有元素的方法
//其实这个类就是把上面所有元素组合在了一起,你可以看成是一个聚合元素。
public static class Computer implements ComputerPart {
//定义一个数组,根据名字我们就可以知道,这个数组里面需要填充的就是上面的具体元素
ComputerPart[] parts;
public Computer(){
//当前是这三个元素
parts = new ComputerPart[] {new Mouse(), new Keyboard(), new Monitor()};
}
@Override
public void accept(ComputerPartVisitor computerPartVisitor) {
for (int i = 0; i < parts.length; i++) {
parts[i].accept(computerPartVisitor);
}
computerPartVisitor.visit(this);
}
}
//定义一个抽象的访问者接口
public interface ComputerPartVisitor {
public void visit(Computer computer);
public void visit(Mouse mouse);
public void visit(Keyboard keyboard);
public void visit(Monitor monitor);
}
//创建一个实现了ComputerPartVisitor接口的实体类
public static class ComputerPartDisplayVisitor implements ComputerPartVisitor {
@Override
public void visit(Computer computer) {
System.out.println("当前显示的是电脑:Computer.");
}
@Override
public void visit(Mouse mouse) {
System.out.println("当前显示的是鼠标:Mouse.");
}
@Override
public void visit(Keyboard keyboard) {
System.out.println("当前显示的是键盘:Keyboard.");
}
@Override
public void visit(Monitor monitor) {
System.out.println("当前显示的是显示器:Monitor.");
}
}
//测试
public static void main(String[] args) {
//我们定义了电脑的这个聚合的对象,其实它里面已经有了上面的几个具体元素
ComputerPart computer = new Computer();
//使用 ComputerPartDisplayVisitor 来显示 Computer 的组成部分
//打印出来的依次是鼠标、键盘、显示器、电脑,因为我们组合的顺序是这样的顺序。大家可以看看实现的逻辑。
computer.accept(new ComputerPartDisplayVisitor());
}
}
模式的扩展
访问者(Visitor)模式是使用频率较高的一种设计模式,它常常同以下两种设计模式联用。
(1)与“迭代器模式”联用。因为访问者模式中的“对象结构”是一个包含元素角色的容器,当访问者遍历容器中的所有元素时,常常要用迭代器。如果对象结构中的聚合类没有提供迭代器,也可以用迭代器模式自定义一个。
(2)访问者(Visitor)模式同“组合模式”联用。因为访问者(Visitor)模式中的“元素对象”可能是叶子对象或者是容器对象,如果元素对象包含容器对象,就必须用到组合模式。
十、备忘录(Memento)模式
每个人都有犯错误的时候,都希望有种“后悔药”能弥补自己的过失,让自己重新开始,但现实是残酷的。在计算机应用中,客户同样会常常犯错误,能否提供“后悔药”给他们呢?当然是可以的,而且是有必要的。这个功能由“备忘录模式”来实现。
概念:在不破坏封装性的前提下,获取并保存一个对象的内部状态,并在该对象之外保存这个状态,以便恢复原先保存的状态。该模式又叫快照模式。
主要解决:所谓备忘录模式就是在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样可以在以后将对象恢复到原先保存的状态。
解决方式:很多时候我们总是需要记录一个对象的内部状态,这样做的目的就是为了允许用户取消不确定或者错误的操作,能够恢复到他原先的状态,使得他有"后悔药"可吃。通过一个备忘录类专门存储对象状态,以此来定义这个“后悔药”。
1、备忘录模式优缺点
优点:
- 提供了一种可以恢复状态的机制。当用户需要时能够比较方便地将数据恢复到某个历史的状态。
- 实现了内部状态的封装。除了创建它的发起人之外,其他对象都不能够访问这些状态信息。
- 简化了发起人类。发起人不需要管理和保存其内部状态的各个备份,所有状态信息都保存在备忘录中,并由管理者进行管理,这符合单一职责原则。
缺点:
- 消耗资源。如果类的成员变量过多,势必会占用比较大的资源,而且每一次保存都会消耗一定的内存。
2、备忘录模式实例
首先,我们先创建一个发起人的角色类(Originator),它主要是记录当前时刻的内部状态信息,提供创建备忘录和恢复备忘录数据的功能,实现其他业务功能,它可以访问备忘录里的所有信息;其次,创建一个备忘录(Memento)角色,它主要是负责存储发起人的状态,在需要恢复的时候,将内部状态传给发起人;最后,我们需要创建一个管理者(Caretaker)的角色,它主要是是对备忘录进行管理,但是它不能对备忘录的内容进行访问与修改。
下面我们对代码进行分析:
package com.test;
/**
* 备忘录模式
**/
public class MementoPatternDemo {
//创建发起人类
public static class Originator{
//定义一个状态
private String state;
public void setState(String state){
this.state=state;
}
public String getState(){
return state;
}
public Memento createMemento(){
return new Memento(state);
}
public void restoreMemento(Memento m){
this.setState(m.getState());
}
}
//创建备忘录类
public static class Memento {
private String state;
public Memento(String state){
this.state=state;
}
public void setState(String state){
this.state=state;
}
public String getState() {
return state;
}
}
//创建管理者类
public static class Caretaker {
private Memento memento;
public void setMemento(Memento m){
memento=m;
}
public Memento getMemento() {
return memento;
}
}
//测试
public static void main(String[] args) {
//实例化发起人对象
Originator or=new Originator();
//实例化管理者对象
Caretaker cr=new Caretaker();
//我们先设置了一个状态,定义为初始化状态
or.setState("S0");
System.out.println("初始状态:"+or.getState());
//我们将这个状态保存到管理者对象中。
cr.setMemento(or.createMemento());
//然后我们重新设置了一个状态给发起者。
or.setState("S1");
System.out.println("新的状态:"+or.getState());
//恢复状态,我们恢复的状态是从管理者里面拿出来的。
or.restoreMemento(cr.getMemento());
System.out.println("恢复状态:"+or.getState());
//其实,这个我们是可以结合原型模式进行备份的,我们可以想一想,原型是有clone(),功能的,
// 这个备忘录模式我们可以看成是原型模式的变形
}
}
十一、解释器(Interpreter)模式
在软件开发中,会遇到有些问题多次重复出现,而且有一定的相似性和规律性。如果将它们归纳成一种简单的语言,那么这些问题实例将是该语言的一些句子,这样就可以用“编译原理”中的解释器模式来实现了。
概念:提供如何定义语言的文法,以及对语言句子的解释方法,即解释器。也就是说,用编译语言的方式来分析应用中的实例。
1、解释器模式优缺点
优点:
- 扩展性好。由于在解释器模式中使用类来表示语言的文法规则,因此可以通过继承等机制来改变或扩展文法。
- 容易实现。在语法树中的每个表达式节点类都是相似的,所以实现其文法较为容易。
缺点:
- 执行效率较低。解释器模式中通常使用大量的循环和递归调用,当要解释的句子较复杂时,其运行速度很慢,且代码的调试过程也比较麻烦。
- 会引起类膨胀。解释器模式中的每条规则至少需要定义一个类,当包含的文法规则很多时,类的个数将急剧增加,导致系统难以管理与维护。
- 可应用的场景比较少。
2、解释器模式实例
在项目开发中,如果要对数据表达式进行分析与计算,无须再用解释器模式进行设计了,Java 提供了以下强大的数学公式解析器:Expression4J、MESP(Math Expression String Parser) 和 Jep 等,它们可以解释一些复杂的文法,功能强大,使用简单。有想深入研究的,可以去自行百度一下学习。我们这里只进行一个实例的演示。
我们将创建一个接口 Expression 和实现了 Expression 接口的实体类。定义作为上下文中主要解释器的 TerminalExpression 类。其他的类 OrExpression、AndExpression 用于创建组合式表达式。
package com.test;
/**
* 解释器模式
**/
public class InterpreterDemo {
//创建一个接口
public interface Expression {
public boolean interpret(String context);
}
//创建实现了上述接口的实体类。
public static class TerminalExpression implements Expression {
private String data;
public TerminalExpression(String data){
this.data = data;
}
@Override
public boolean interpret(String context) {
//判断传输的数据是否包含了指定的那些规则的数据,
if(context.contains(data)){
return true;
}
return false;
}
}
public static class OrExpression implements Expression {
private Expression expr1 = null;
private Expression expr2 = null;
public OrExpression(Expression expr1, Expression expr2) {
this.expr1 = expr1;
this.expr2 = expr2;
}
//这是或的关系
@Override
public boolean interpret(String context) {
return expr1.interpret(context) || expr2.interpret(context);
}
}
public static class AndExpression implements Expression {
private Expression expr1 = null;
private Expression expr2 = null;
public AndExpression(Expression expr1, Expression expr2) {
this.expr1 = expr1;
this.expr2 = expr2;
}
//这是且的关系
@Override
public boolean interpret(String context) {
return expr1.interpret(context) && expr2.interpret(context);
}
}
//使用Expression 类来创建规则,并解析它们。
//规则:Robert 和 John 是男性
public static Expression getMaleExpression(){
Expression robert = new TerminalExpression("张三");
Expression john = new TerminalExpression("李四");
return new OrExpression(robert, john);
}
//规则:Julie 是一个已婚的女性
public static Expression getMarriedWomanExpression(){
Expression julie = new TerminalExpression("梅梅");
Expression married = new TerminalExpression("已婚");
return new AndExpression(julie, married);
}
//测试
public static void main(String[] args) {
//是否是男性的规则
Expression isMale = getMaleExpression();
//是否是已婚女性
Expression isMarriedWoman = getMarriedWomanExpression();
System.out.println("张三是男性吗? " + isMale.interpret("张三"));
System.out.println("梅梅是已婚女性吗? "+ isMarriedWoman.interpret("梅梅已婚"));
}
}