Janker

V1

2022/09/04阅读:8主题:全栈蓝

设计模式第五弹-行为型模式[一]

叨叨

之前的文章里我们分享了创建型模式和结构型模式。接下来我们就叨叨一下行为型模式。

创建型模式重点在于如何生成对象,结构型模式重点在于将类或对象按照某种布局组成更大的结构,二结构型模式则用于描述运行时复杂的流程控制。即多个类或者对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,它涉及算法与对象间的职责分配。

如果把程序比作装修的话,创建型模式描述构成零件的方式。结构型模式描述的是这些零件如何组合能构成一个小模块,行为型模式描述的是这些对象或者模块间如何优雅的相互协作完成任务,既装修的思路。

概述

行为型模式是设计模式中最为庞大的一类,它包含一下11种模式。

  1. 模板方法(Template Method)模式:定义一个操作中的算法骨架,将算法的一些步骤延迟到子类中,使得子类在可以不改变该算法结构的情况下重定义该算法的某些特定步骤。
  2. 策略(Strategy)模式:定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的改变不会影响使用算法的客户。
  3. 命令(Command)模式:将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。
  4. 责任链(Chain of Responsibility)模式:把请求从链中的一个对象传到下一个对象,直到请求被响应为止。通过这种方式去除对象之间的耦合。
  5. 状态(State)模式:允许一个对象在其内部状态发生改变时改变其行为能力。
  6. 观察者(Observer)模式:多个对象间存在一对多关系,当一个对象发生改变时,把这种改变通知给其他多个对象,从而影响其他对象的行为。
  7. 中介者(Mediator)模式:定义一个中介对象来简化原有对象之间的交互关系,降低系统中对象间的耦合度,使原有对象之间不必相互了解。
  8. 迭代器(Iterator)模式:提供一种方法来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示。
  9. 访问者(Visitor)模式:在不改变集合元素的前提下,为一个集合中的每个元素提供多种访问方式,即每个元素有多个访问者对象访问。
  10. 备忘录(Memento)模式:在不破坏封装性的前提下,获取并保存一个对象的内部状态,以便以后恢复它。
  11. 解释器(Interpreter)模式:提供如何定义语言的文法,以及对语言句子的解释方法,即解释器。

以上 11 种行为型模式,除了模板方法模式和解释器模式是类行为型模式,其他的全部属于对象行为型模式,下面我们将详细介绍它们的特点、结构与应用。

模板方法模式

介绍设计模式不搞点场景属实不好理解。

场景:阿峰最近做订单业务,对于下单逻辑阿峰轻车熟路,但是多个终端业务适配让阿峰犯了难,下单核心逻辑关键步骤都是统一固定的,并且执行顺序也是确定的,但是具体执行的时候,不同终端有一些细微的差异,比如说某团的小程序下单有些店铺会比app下单便宜,某东某店铺的app下单比pc要便宜几块钱,无论是业务规则原因还是营销手段,作为程序设计者都给他整的明明白白的。于是阿峰带着问题请教阿雄,雄哥张口就来,这个用模板方法模式刚刚好,噼里啪啦一顿敲,写下了模板方法模式的伪代码。(代码在后面实现部分)

定义与特点

模板方法(Template Method)模式的定义如下:定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤。它是一种类行为型模式。

优点

  1. 封装了不变的部分,拓展可变部分。它把认为不变部分的逻辑封装(一般是程序骨架)到父类中实现,而把可变部分算法由子类继承实现,便于子类继续扩展。
  2. 父类中提取公共执行代码,便于代码复用。
  3. 部分方法是由子类实现的,因此子类可以通过扩展方式增加相应的功能,符合开闭原则。

缺点

  1. 每一个不同的实现,都需要定义一个子类,这会使类的数量增多,系统更加庞大,设计的更加抽象,同时增加了系统的复杂性。
  2. 父类中的抽象方法让子类实现,子类的实现会影响父类的结果,这是一种反向控制的结构,提供了代码的阅读难度。
  3. 由于继承关系自身的缺点,如果父类添加新的抽象方法,则所有子类都要改一遍。

结构与实现

模板方法模式需要注意抽象类与具体子类之间的协作。如果是java语言的话运用了多态,现在我们来看看模板方法模式的基本结构。

模式的结构

模板方法模式包含以下主要角色。

1. 抽象类/抽象模板(Abstract Class)

抽象模板类,负责给出一个算法的轮廓和骨架。它由一个模板方法和若干个基本方法构成。这些方法的定义如下。

  1. 模板方法:定义了算法的骨架,按某种顺序调用其包含的基本方法。
  2. 基本方法:是整个算法中的一个步骤,包含以下几种类型。
  • 抽象方法:在抽象类中声明,由具体子类实现。
  • 具体方法:在抽象类中已经实现,在具体子类中可以继承或重写它。
  • 钩子方法:在抽象类中已经实现,包括用于判断的逻辑方法和需要子类重写的空方法两种。
2. 具体子类/具体实现(Concrete Class)

具体实现类,实现抽象类中所定义的抽象方法和钩子方法,它们是一个顶级逻辑的一个组成步骤。

模板方法模式的结构图如图 1 所示。

模式的实现

模板方法模式的代码如下:

1. 抽象模板类(AbstractGenerateOrder)
public abstract class AbstractGenerateOrder {

    public void order(){
        subValidateParam();
        commonValidateParam();
        createOrder();
    }

    protected abstract void subValidateParam();

    private void createOrder(){
        System.out.println("通用创建订单操作");
    }

    private void commonValidateParam(){
        System.out.println("通用校验参数");
    }
}
2. PC下单具体实现类(PcGenerateOrder)
public class PcGenerateOrder extends AbstractGenerateOrder {
    @Override
    public void subValidateParam() {
        System.out.println("pc特殊逻辑校验");
    }
}
3. App下单具体实现类(AppGenerateOrder)
public class AppGenerateOrder extends AbstractGenerateOrder{
    @Override
    public void subValidateParam() {
        System.out.println("app特殊逻辑校验");
    }
}
4. 模板方法测试类(TemplateMethodPatternTest)
public class TemplateMethodPatternTest {
    public static void main(String[] args) {
        AbstractGenerateOrder appGenerateOrder = new AppGenerateOrder();
        appGenerateOrder.order();

        AbstractGenerateOrder pcGenerateOrder = new PcGenerateOrder();
        pcGenerateOrder.order();
    }
}
5. 运行结果
app特殊逻辑校验
通用校验参数
通用创建订单操作
pc特殊逻辑校验
通用校验参数
通用创建订单操作

策略模式

平时我们为了实现某个目标存在多种解决方案(策略)可供选择,例如我们的出行方式,可以打车,也可以骑自行车,有条件的还可以自己开车。阿峰追女朋友也是有挺多招数(策略)的,比如说甜言蜜语,又或者是说投其所好买点东西啥的。都是阿峰为了能交上女朋友策略。

在软件开发中也常常遇到类似的情况,当实现某一个功能存在多种算法或者策略,我们可以根据环境或者条件的不同选择不同的算法或者策略来完成该功能,如数据排序策略有冒泡排序、选择排序、插入排序、二叉树排序等。

当然为了实现我们要的效果硬编码方式也能解决,但是我们在修改算法(逻辑)的时候,其实挺头疼的,那么多if else 头都大了,不易维护,违背开闭原则。如果采用策略模式就能很好的解决这个问题。

定义与特点

策略(Strategy)模式的定义:这个模式定义了一系列的的算法(也可以是特殊的逻辑处理),并将每个算法封装起来,使用户可以根据自己的场景或者喜好自由替换,并且算法的改变也不影响使用算法的用户。策略模式属于对象行为模式,它通过对算法进行封装,把使用算法的责任和算法的实现分割开来,并委派给不同的对象对这些算法进行管理。

优点

  1. 避免使用多重条件语句,如if...else...语句、switch...case 语句。
  2. 策略模式提供了一系列的可供重用的算法族,恰当使用继承可以把算法族的公共代码转移到父类里面,达到代码复用的效果。
  3. 策略模式可以提供相同行为的不同实现,客户可以根据不同时间或空间要求选择不同的。
  4. 策略模式符合开闭原则,可以在不修改原代码的情况下,灵活增加新算法。
  5. 策略模式把算法的使用放到环境类中,而算法的实现移到具体策略类中,实现了二者的分离。

缺点

  1. 客户端必须理解所有策略算法的区别,以便适时选择恰当的算法类。
  2. 策略模式造成很多的策略类,增加维护难度。

结构与实现

策略模式是准备一组算法,并将这组算法封装到一系列的策略类里面,作为一个抽象策略类的子类。策略模式的重心不是如何实现算法,而是如何组织这些算法,从而让程序结构更加灵活,具有更好的维护性和扩展性,现在我们来分析其基本结构和实现方法。

1. 模式的结构

策略模式的主要角色如下。

  1. 抽象策略(Strategy)类:定义了一个公共接口,各种不同的算法以不同的方式实现这个接口,环境角色使用这个接口调用不同的算法,一般使用接口或抽象类实现。
  2. 具体策略(Concrete Strategy)类:实现了抽象策略定义的接口,提供具体的算法实现。
  3. 环境(Context)类:持有一个策略类的引用,最终给客户端调用。

咱们拿如何上班通勤为例。其结构图如图 1 所示。

####2. 模式的实现

1. 抽象策略类(Strategy)
public interface Strategy {
    /**
     * 通勤
     */

    void commute();
}
2. 具体策略(BicycleStrategy-自行车策略)
public class BicycleStrategy implements Strategy {

    @Override
    public void commute() {
        System.out.println("骑自行车上班");
    }
}
3. 具体策略(BicycleStrategy-公交车策略)
public class BusStrategy implements Strategy {
    @Override
    public void commute() {
        System.out.println("坐公交车上班");
    }
}
4. 环境类(Context)
public class Context {
    private Strategy strategy;
    public Strategy getStrategy() {
        return strategy;
    }
    public void setStrategy(Strategy strategy) {
        this.strategy = strategy;
    }

    public void commute(){
        strategy.commute();
    }
}
5. 策略模式测试类(StrategyPatternTest)
public class StrategyPatternTest {
    public static void main(String[] args) {
        Context c = new Context();
        Strategy strategy = new BicycleStrategy();
        c.setStrategy(strategy);
        c.commute();

        strategy = new BusStrategy();
        c.setStrategy(strategy);
        c.commute();
    }
}
运行结果
骑自行车上班
坐公交车上班

##命令模式

在软件开发系统中,“方法的请求者”与“方法的实现者”之间经常存在紧密的耦合关系,这不利于软件功能的扩展与维护。例如,想对方法进行“撤销、重做、记录”等处理都很不方便,因此“如何将方法的请求者与实现者解耦?”变得很重要,命令模式就能很好地解决这个问题。

在现实生活中,命令模式的例子也很多。比如看电视时,我们只需要轻轻一按遥控器就能完成频道的切换,这就是命令模式,将换台请求和换台处理完全解耦了。电视机遥控器(命令发送者)通过按钮(具体命令)来遥控电视机(命令接收者)。

再比如,我们去餐厅吃饭,菜单不是等到客人来了之后才定制的,而是已经预先配置好的。这样,客人来了就只需要点菜,而不是任由客人临时定制。餐厅提供的菜单就相当于把请求和处理进行了解耦,这就是命令模式的体现。

定义与特点

命令(Command)模式的定义如下:将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。这样两者之间通过命令对象进行沟通,这样方便将命令对象进行储存、传递、调用、增加与管理。

优点

  1. 通过引入中间件(抽象接口)降低系统的耦合度。
  2. 扩展性良好,增加或删除命令非常方便。采用命令模式增加与删除命令不会影响其他类,且满足“开闭原则”。
  3. 可以实现宏命令。命令模式可以与 组合模式结合,将多个命令装配成一个组合命令,即宏命令。
  4. 方便实现 Undo 和 Redo 操作。命令模式可以与后面介绍的备忘录模式结合,实现命令的撤销与恢复。
  5. 可以在现有命令的基础上,增加额外功能。比如日志记录,结合装饰器模式会更加灵活。

缺点

  1. 可能产生大量具体的命令类。因为每一个具体操作都需要设计一个具体命令类,这会增加系统的复杂性。
  2. 命令模式的结果其实就是接收方的执行结果,但是为了以命令的形式进行架构、解耦请求与实现,引入了额外类型结构(引入了请求方与抽象命令接口),增加了理解上的困难。不过这也是设计模式的通病,抽象必然会额外增加类的数量,代码抽离肯定比代码聚合更加难理解。

结构与实现

可以将系统中的相关操作抽象成命令,使调用者与实现者相关分离,其结构如下。

模式的结构

命令模式包含以下主要角色。

  1. 抽象命令类(Command)角色:声明执行命令的接口,拥有执行命令的抽象方法 execute()。
  2. 具体命令类(Concrete Command)角色:是抽象命令类的具体实现类,它拥有接收者对象,并通过调用接收者的功能来完成命令要执行的操作。
  3. 实现者/接收者(Receiver)角色:执行命令功能的相关操作,是具体命令对象业务的真正实现者。
  4. 调用者/请求者(Invoker)角色:是请求的发送者,它通常拥有很多的命令对象,并通过访问命令对象来执行相关请求,它不直接访问接收者。

其结构图如图所示。

模式的实现

1. 抽象命令类(Command)
public interface Command {
    void execute();
}
2. 调用者(Invoker)
public 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();
    }
}
3. 具体命令实现类(ConcreteCommand)
public class ConcreteCommand implements Command {
    private Receiver receiver;
    ConcreteCommand() {
        receiver = new Receiver();
    }
    @Override
    public void execute() {
        receiver.action();
    }
}
4. 接收者(Receiver)
public class Receiver {
    
    public void action() {
        System.out.println("接收者的action()方法被调用...");
    }
}
5. 命令模式测试类(CommandPatternTest)
public class CommandPatternTest {
    public static void main(String[] args) {
        Command cmd = new ConcreteCommand();
        Invoker ir = new Invoker(cmd);
        System.out.println("客户访问调用者的call()方法...");
        ir.call();
    }
}
6. 运行结果
客户访问调用者的call()方法...
调用者执行命令command...
接收者的action()方法被调用...

责任链模式

在现实生活中,一个事件需要经过多个对象处理是很常见的场景。例如,采购审批流程、请假流程等。公司员工请假,可批假的领导有部门负责人、副总经理、总经理等,但每个领导能批准的天数不同,员工必须根据需要请假的天数去找不同的领导签名,也就是说员工必须记住每个领导的姓名、电话和地址等信息,这无疑增加了难度。

在计算机软硬件中也有相关例子,如总线网中数据报传送,每台计算机根据目标地址是否同自己的地址相同来决定是否接收;还有异常处理中,处理程序根据异常的类型决定自己是否处理该异常;还有 Struts2 的拦截器、JSPServletFilter 等,所有这些,都可以考虑使用责任链模式来实现。

定义与特点

责任链(Chain of Responsibility)模式的定义:为了避免请求发送者与多个请求处理者耦合在一起,于是将所有请求的处理者通过前一对象记住其下一个对象的引用而连成一条链;当有请求发生时,可将请求沿着这条链传递,直到有对象处理它为止。

注意:责任链模式也叫职责链模式。

在责任链模式中,客户只需要将请求发送到责任链上即可,无须关心请求的处理细节和请求的传递过程,请求会自动进行传递。所以责任链将请求的发送者和请求的处理者解耦了。

责任链模式是一种对象行为型模式。

优点

  1. 降低了对象之间的耦合度。该模式使得一个对象无须知道到底是哪一个对象处理其请求以及链的结构,发送者和接收者也无须拥有对方的明确信息。
  2. 增强了系统的可扩展性。可以根据需要增加新的请求处理类,满足开闭原则。
  3. 增强了给对象指派职责的灵活性。当工作流程发生变化,可以动态地改变链内的成员或者调动它们的次序,也可动态地新增或者删除责任。
  4. 责任链简化了对象之间的连接。每个对象只需保持一个指向其后继者的引用,不需保持其他所有处理者的引用,这避免了使用众多的 if 或者 if···else 语句。
  5. 责任分担。每个类只需要处理自己该处理的工作,不该处理的传递给下一个对象完成,明确各类的责任范围,符合类的单一职责原则。

缺点

  1. 不能保证每个请求一定被处理。由于一个请求没有明确的接收者,所以不能保证它一定会被处理,该请求可能一直传到链的末端都得不到处理。
  2. 对比较长的职责链,请求的处理可能涉及多个处理对象,系统性能将受到一定影响。
  3. 职责链建立的合理性要靠客户端来保证,增加了客户端的复杂性,可能会由于职责链的错误设置而导致系统出错,如可能会造成循环调用。

结构与实现

通常情况下,可以通过数据链表来实现职责链模式的数据结构。

模式的结构

职责链模式主要包含以下角色。

  1. 抽象处理者(Handler)角色:定义一个处理请求的接口,包含抽象处理方法和一个后继连接。
  2. 具体处理者(Concrete Handler)角色:实现抽象处理者的处理方法,判断能否处理本次请求,如果可以处理请求则处理,否则将该请求转给它的后继者。
  3. 客户类(Client)角色:创建处理链,并向链头的具体处理者对象提交请求,它不关心处理细节和请求的传递过程。

责任链模式的本质是解耦请求与处理,让请求在处理链中能进行传递与被处理;理解责任链模式应当理解其模式,而不是其具体实现。责任链模式的独到之处是将其节点处理者组合成了链式结构,并允许节点自身决定是否进行请求处理或转发,相当于让请求流动起来。

其结构图如图 所示。客户端可按下面第二张图所示设置责任链。

模式的实现

1. 抽象处理类(Handler)
public 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);
}
2. 具体处理类(ConcreteHandler1
public class ConcreteHandler1 extends Handler {
    public void handleRequest(String request) {
        if (request.equals("one")) {
            System.out.println("具体处理者1负责处理该请求!");
        } else {
            if (getNext() != null) {
                getNext().handleRequest(request);
            } else {
                System.out.println("没有人处理该请求!");
            }
        }
    }
}
3. 具体处理类(ConcreteHandler2)
public class ConcreteHandler2 extends Handler {
    public void handleRequest(String request) {
        if (request.equals("two")) {
            System.out.println("具体处理者2负责处理该请求!");
        } else {
            if (getNext() != null) {
                getNext().handleRequest(request);
            } else {
                System.out.println("没有人处理该请求!");
            }
        }
    }
}
4. 责任链模式测试类(ChainOfResponsibilityPatternTest)
public class ChainOfResponsibilityPatternTest {
    public static void main(String[] args) {
        //组装责任链
        Handler handler1 = new ConcreteHandler1();
        Handler handler2 = new ConcreteHandler2();
        handler1.setNext(handler2);
        //提交请求
        handler1.handleRequest("two");
    }
}
5. 运行结果
具体处理者2负责处理该请求!

状态模式

在软件开发过程中,应用程序中的部分对象可能会根据不同的情况做出不同的行为,我们把这种对象称为有状态的对象,而把影响对象行为的一个或多个动态变化的属性称为状态。当有状态的对象与外部事件产生互动时,其内部状态就会发生改变,从而使其行为也发生改变。如人都有高兴和伤心的时候,不同的情绪有不同的行为,当然外界也会影响其情绪变化。

对这种有状态的对象编程,传统的解决方案是:将这些所有可能发生的情况全都考虑到,然后使用 if-elseswitch-case 语句来做状态判断,再进行不同情况的处理。但是显然这种做法对复杂的状态判断存在天然弊端,条件判断语句会过于臃肿,可读性差,且不具备扩展性,维护难度也大。且增加新的状态时要添加新的 if-else 语句,这违背了“开闭原则”,不利于程序的扩展。

以上问题如果采用“状态模式”就能很好地得到解决。状态模式的解决思想是:当控制一个对象状态转换的条件表达式过于复杂时,把相关“判断逻辑”提取出来,用各个不同的类进行表示,系统处于哪种情况,直接使用相应的状态类对象进行处理,这样能把原来复杂的逻辑判断简单化,消除了 if-elseswitch-case 等冗余语句,代码更有层次性,并且具备良好的扩展力。

定义与特点

状态(State)模式的定义:对有状态的对象,把复杂的“判断逻辑”提取到不同的状态对象中,允许状态对象在其内部状态发生改变时改变其行为。

状态模式是一种对象行为型模式。

优点

  1. 结构清晰,状态模式将与特定状态相关的行为局部化到一个状态中,并且将不同状态的行为分割开来,满足“单一职责原则”。
  2. 将状态转换显示化,减少对象间的相互依赖。将不同的状态引入独立的对象中会使得状态转换变得更加明确,且减少对象间的相互依赖。
  3. 状态类职责明确,有利于程序的扩展。通过定义新的子类很容易地增加新的状态和转换。

缺点

  1. 状态模式的使用必然会增加系统的类与对象的个数。
  2. 状态模式的结构与实现都较为复杂,如果使用不当会导致程序结构和代码的混乱。
  3. 状态模式对开闭原则的支持并不太好,对于可以切换状态的状态模式,增加新的状态类需要修改那些负责状态转换的源码,否则无法切换到新增状态,而且修改某个状态类的行为也需要修改对应类的源码。

结构与实现

状态模式把受环境改变的对象行为包装在不同的状态对象里,其意图是让一个对象在其内部状态改变的时候,其行为也随之改变。现在我们来分析其基本结构和实现方法。

1. 模式的结构

状态模式包含以下主要角色。

  1. 环境类(Context)角色:也称为上下文,它定义了客户端需要的接口,内部维护一个当前状态,并负责具体状态的切换。
  2. 抽象状态(State)角色:定义一个接口,用以封装环境对象中的特定状态所对应的行为,可以有一个或多个行为。
  3. 具体状态(Concrete State)角色:实现抽象状态所对应的行为,并且在需要的情况下进行状态切换。

其结构图如图所示。

2. 模式的实现

1. 环境类(Context)
public class Context {
    private State state;
    //定义环境类的初始状态
    public Context() {
        this.state = new ConcreteStateA();
    }
    //设置新状态
    public void setState(State state) {
        this.state = state;
    }
    //读取状态
    public State getState() {
        return (state);
    }
    //对请求做处理
    public void handle() {
        state.handle(this);
    }
}
2. 抽象状态类(State)
public abstract class State {
    public abstract void handle(Context context);
}
3. 具体状态A类(ConcreteStateA)
public class ConcreteStateA extends State {
    public void handle(Context context) {
        System.out.println("当前状态是 A.");
        context.setState(new ConcreteStateB());
    }
}
4. 具体状态B类(ConcreteStateB)
public class ConcreteStateB extends State {
    public void handle(Context context) {
        System.out.println("当前状态是 B.");
        context.setState(new ConcreteStateA());
    }
}
5. 状态模式测试类(StatePatternTest)
public class StatePatternTest {
    public static void main(String[] args) {
        Context context = new Context();    //创建环境
        context.handle();    //处理请求
        context.handle();
        context.handle();
        context.handle();
    }
}
6. 测试结果
当前状态是 A.
当前状态是 B.
当前状态是 A.
当前状态是 B.

观察者模式

在现实世界中,许多对象并不是独立存在的,其中一个对象的行为发生改变可能会导致一个或者多个其他对象的行为也发生改变。例如,某种商品的物价上涨时会导致部分商家高兴,而消费者伤心;还有,当我们开车到交叉路口时,遇到红灯会停,遇到绿灯会行。这样的例子还有很多,例如,股票价格与股民、微信公众号与微信用户、气象局的天气预报与听众、小偷与警察等。

在软件世界也是这样,例如,Excel 中的数据与折线图、饼状图、柱状图之间的关系;MVC 模式中的模型与视图的关系;事件模型中的事件源与事件处理者。所有这些,如果用观察者模式来实现就非常方便。

定义与特点

观察者(Observer)模式的定义:指多个对象间存在一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。这种模式有时又称作发布-订阅模式、模型-视图模式,它是对象行为型模式。

观察者模式是一种对象行为型模式。

优点

  1. 降低了目标与观察者之间的耦合关系,两者之间是抽象耦合关系。符合依赖倒置原则。
  2. 目标与观察者之间建立了一套触发机制。

缺点

  1. 目标与观察者之间的依赖关系并没有完全解除,而且有可能出现循环引用。
  2. 当观察者对象很多时,通知的发布会花费很多时间,影响程序的效率。

结构与实现

实现观察者模式时要注意具体目标对象和具体观察者对象之间不能直接调用,否则将使两者之间紧密耦合起来,这违反了面向对象的设计原则。

模式的结构

观察者模式的主要角色如下。

  1. 抽象主题(Subject)角色:也叫抽象目标类,它提供了一个用于保存观察者对象的聚集类和增加、删除观察者对象的方法,以及通知所有观察者的抽象方法。
  2. 具体主题(Concrete Subject)角色:也叫具体目标类,它实现抽象目标中的通知方法,当具体主题的内部状态发生改变时,通知所有注册过的观察者对象。
  3. 抽象观察者(Observer)角色:它是一个抽象类或接口,它包含了一个更新自己的抽象方法,当接到具体主题的更改通知时被调用。
  4. 具体观察者(Concrete Observer)角色:实现抽象观察者中定义的抽象方法,以便在得到目标的更改通知时更新自身的状态。

观察者模式的结构图如图

模式的实现

1. 抽象主题类(Subject)
public abstract class Subject {
    protected List<Observer> observers = new ArrayList<>();

    public void add(Observer observer){
        observers.add(observer);
    }
    public void remove(Observer observer){
        observers.remove(observer);
    }

    public abstract void notifyObserver();
}
2. 具体主题类(ConcreteSubject)
public class ConcreteSubject extends Subject {
    @Override
    public void notifyObserver() {
        System.out.println("具体目标发生改变...");
        System.out.println("--------------");
        for (Object obs : observers) {
            ((Observer) obs).response();
        }
    }
}
3. 抽象观察者(Observer)
public interface Observer {
    void response()//反应
}
4. 具体观察者1(ConcreteObserver1)
public class ConcreteObserver1 implements Observer{
    @Override
    public void response() {
        System.out.println("具体观察者1作出反应!");
    }
}

5. 具体观察者2(ConcreteObserver2)
public class ConcreteObserver2 implements Observer{
    @Override
    public void response() {
        System.out.println("具体观察者2作出反应!");
    }
}
6. 观察者模式测试类(ObserverPatternTest)
public class ObserverPatternTest {
    public static void main(String[] args) {
        Subject subject = new ConcreteSubject();
        Observer obs1 = new ConcreteObserver1();
        Observer obs2 = new ConcreteObserver2();
        subject.add(obs1);
        subject.add(obs2);
        subject.notifyObserver();
    }
}
7. 测试结果
具体目标发生改变...
--------------
具体观察者1作出反应!
具体观察者2作出反应!

总结

行为型模式其实是对在不同的对象之间划分责任和算法的抽象化,以上就是行为型模式中的其中六种,下一篇介绍剩下的五种行为型模式。我是janker,大家下期见。

分类:

后端

标签:

后端

作者介绍

Janker
V1