程序员社区

设计模式总结(下)

五、行为型模式

行为模式主要是根据具体的场景描述各个对象之间的相互关系和职责和通信方式。

5.1 模板方法模式

定义

在一个方法中定义一个算法的骨架(执行顺序),而将一些步骤延迟到子类中。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。

使用前后对比

public class Coffee {
    void prepareRecipe() {
        boilWater();
        brewCoffeeGrind();
        pourInCup();
        addSugarAndMilk();
    }

    private void boilWater() {
        System.out.println("烧热水。。。");
    }

    private void brewCoffeeGrind() {
        System.out.println("冲泡咖啡粉。。。");
    }

    private void pourInCup() {
        System.out.println("倒入杯子。。。");
    }

    private void addSugarAndMilk() {
        System.out.println("加糖和牛奶。。。");
    }

}

public class Tea {
    void prepareRecipe() {
        boilWater();
        steepTeaBag();
        pourInCup();
        addLemon();
    }

    private void boilWater() {
        System.out.println("烧热水。。。");
    }

    private void steepTeaBag() {
        System.out.println("泡茶包。。。");
    }

    private void pourInCup() {
        System.out.println("倒入杯子。。。");
    }

    private void addLemon() {
        System.out.println("加柠檬。。。");
    }

}

我们发现了重复的代码,而且我们发现,其实他们的步骤也很相似,所以,我们可以将其抽取出来。

public abstract class CaffeineBeverage {

    /**
     * 准备饮料(模板方法)
     */
    public final void prepareRecipe() {
        this.boilWater();
        this.brew();
        this.pourInCup();
        if (this.customerWantsCondiments()) {
            this.addCondiments();
        }
    }

    // 烧热水
    protected void boilWater() {
        System.out.println("烧热水中~");
    }

    // 煮
    protected abstract void brew();

    // 饮料倒入杯中
    protected void pourInCup() {
        System.out.println("饮料倒入杯中~");
    }

    // 加调料
    protected abstract void addCondiments();

    // 钩子函数:在整个程序的生命周期中,提供一个可以在指定时机做某事的东西,例如AOP和析构函数
    // 一般是空方法,子类可以选择不重写;在这里做的是判断客户是否加调料
    protected boolean customerWantsCondiments() {
        return false;
    }
}

class Tea extends CaffeineBeverage {

    @Override
    protected void brew() {
        System.out.println("煮茶中~");
    }

    @Override
    protected void addCondiments() {
        System.out.println("加柠檬~");
    }

    public static void main(String[] args) {
        CaffeineBeverage caffeineBeverage = new Tea();
        caffeineBeverage.prepareRecipe();
    }

    // 子类可以重写钩子方法,来控制算法的流程。

}

总结

优点:

  1. 在父类中形式化地定义一个算法,而由它的子类来实现细节的处理,在子类实现详细的处理算法时并不会改变算法中步骤的执行次序
  2. 模板方法模式是一种代码复用技术,它在类库设计中尤为重要,它提取了类库中的公共行为,将公共行为放在父类中,而通过其子类来实现不同的行为,它鼓励我们恰当使用继承来实现代码复用。
  3. 可实现一种反向控制结构,通过子类覆盖父类的钩子方法来决定某一特定步骤是否需要执行
  4. 在模板方法模式中可以通过子类来覆盖父类的基本方法,不同的子类可以提供基本方法的不同实现,更换和增加新的子类很方便,符合单一职责原则和开闭原则。

缺点:

需要为每一个基本方法的不同实现提供一个子类,如果父类中可变的基本方法太多,将会导致类的个数增加,系统更加庞大,设计也更加抽象,此时,可结合桥接模式来进行设计

何时使用:

  1. 对一些复杂的算法进行分割,将其算法中固定不变的部分设计为模板方法和父类具体方法,而一些可以改变的 细节由其子类来实现。即:一次性实现一个算法的不变部分,并将可变的行为留给子类来实现。
  2. 各子类中公共的行为应被提取出来并集中到一个公共父类中以避免代码重复。
  3. 需要通过子类来决定父类算法中某个步骤是否执行,实现子类对父类的反向控制。
设计模式总结(下)插图
Spring在初始化IOC容器时使用到

符合6大设计原则的哪些

单一职责 符合

接口隔离 未涉及

依赖倒置 未涉及

里式替换 未涉及

开闭原则 符合

迪米特法则 未涉及

好莱坞原则:低层组件不可以直接调用高层组件,但是高层组件控制何时以及如何让低层子组件参与。模板方法设计时,我们要告诉子类,“不要调用我们,我们会调用你”(工厂方法、观察者模式也使用过)

5.2 命令模式

定义

将“请求”封装成对象,以便使用不同的请求、队列或者日志来参数化其他对象。命令模式也支持可撤销的操作。

解决动作请求者直接调用动作接收者导致的高度耦合。

设计模式总结(下)插图1
  • Command(抽象命令类):抽象命令类一般是一个抽象类或接口,在其中声明了用于执行请求的 execute() 等方法,通过这些方法可以调用请求接收者的相关操作。
  • ConcreteCommand(具体命令类):具体命令类是抽象命令类的子类,实现了在抽象命令类中声明的方法,它对应具体的接收者对象,将接收者对象的动作绑定其中。在实现 execute() 方法时,将调用接收者对象的相关操作(Action)
  • Invoker(调用者):调用者即请求发送者,它通过命令对象来执行请求。一个调用者并不需要在设计时确定其接收者,因此它只与抽象命令类之间存在关联关系。在程序运行时可以将一个具体命令对象注入其中,再调用具体命令对象的 execute() 方法,从而实现间接调用请求接收者的相关操作。
  • Receiver(接收者):接收者执行与请求相关的操作,它具体实现对请求的业务处理。

使用前后对比

设计模式总结(下)插图2
使用前,动作请求者直接调用动作接收者
public class Client {
    public static void main(String[] args) {
        pushDownButton(1);
    }

    /**
     * 当按钮按下时调用
     */
    public static void pushDownButton(int slot) {
        // 动作接收者
        Light light = new Light();
        if (slot == 1) {
            // 开灯
            light.on();
        } else if (slot == 2) {
            light.off();
        } // 开电视、关电视。。。
    }
}

我们发现,这种写法将“灯”、“电视”等动作接收者全部耦合在了代码中,这样会出现一个问题,日后增加或修改动作接收者,或者按键修改时,我们就需要修改这部分代码,违背了“开闭原则”。所以,下面我们采用命令模式进行改造。

interface Command {

    void execute();
}

// 管理命令的对象,命令的保存+执行,我认为如果Invoker中只维护一个命令对象,可以省略调Invoker,在Client新增一个方法用于命令的执行即可
public class RemoteControl {

    Command[] commands;

    public RemoteControl() {
        // 遥控器上有14个按钮
        commands = new Command[14];

        // 为了避免运行的时候判空,赋予默认的什么都不干的命令
        Command noCommand = new NoCommand();
        for (int i = 0; i < 14; i++) {
            commands[i] = noCommand;
        }
    }

    /**
     * 用于客户端动态添加/修改不同插槽的命令
     *
     * @param slot
     * @param command
     */
    public void setCommand(int slot, Command command) {
        commands[slot] = command;
    }

    /**
     * 当按钮按下时调用
     *
     * @param slot
     */
    public void pushDownButton(int slot) {
        // if (commands[slot] != null) {  使用NoCommand就可以避免判空。
        commands[slot].execute();
    }


    // 客户端
    public static void main(String[] args) {

        RemoteControl remoteControl = new RemoteControl();
        remoteControl.setCommand(1, new LightOnCommand(new Light()));   // 前两句相当于:Thread thread = new Thread(() -> {});
        // remoteControl.setCommand(2, new XXXCommand(new XXX()));
        // ...
        remoteControl.pushDownButton(1);    // 相当于:thread.run()

    }
}
设计模式总结(下)插图3
class NewThread extends Thread {
    void run() {
        Reciver.xxx();
    }
}

采用上面这种方式,是否违背了命令模式?

是的,这样的话动作的请求者(Thread)与动作接收者(Reciver)就耦合在一起,如果日后需要修改成其他的Reciver,就需要修改NewThread这个类中的代码,而使用“命令模式”就只需增加一个新的命令类,修改下配置文件即可。

注:撤销命令、命令队列(例如线程池)、记录请求(命令)日志部分功能看书吧。

总结

优点

  1. 降低系统的耦合度。由于请求者与接收者之间不存在直接引用,因此请求者与接收者之间实现完全解耦,相同的请求者可以对应不同的接收者,同样,相同的接收者也可以供不同的请求者使用,两者之间具有良好的独立性。
  2. 新的命令可以很容易地加入到系统中。由于增加新的具体命令类不会影响到其他类,因此增加新的具体命令类很容易,无须修改原有系统源代码,甚至客户类代码,满足“开闭原则”的要求。
  3. 可以比较容易地设计一个命令队列或宏命令(组合命令)。
  4. 为请求的撤销(Undo)和恢复(Redo)操作提供了一种设计和实现方案。

何时使用

系统需要将请求调用者和请求接收者解耦,使得调用者和接收者不直接交互。请求调用者无须知道接收者的存在,也无须知道接收者是谁,接收者也无须关心何时被调用

符合6大设计原则的哪些

单一职责 符合

接口隔离 符合

依赖倒置 符合

里式替换 未涉及

开闭原则 新的命令可以很容易地加入到系统中。由于增加新的具体命令类不会影响到其他类,因此增加新的具体命令类很容易,无须修改原有系统源代码,甚至客户类代码,满足“开闭原则”的要求。

迪米特法则 未涉及

5.3 访问者模式

定义

提供一个作用于某对象结构中的各元素的操作表示,它使我们可以在不改变各元素的类的前提下定义作用于这些元素的新操作。访问者模式是一种对象行为型模式。(注:这个模式一定程度上打破了封装)

设计模式总结(下)插图4

Vistor(抽象访问者):抽象访问者为对象结构中每一个具体元素类 ConcreteElement 声明一个访问操作,从这个操作的名称或参数类型可以清楚知道需要访问的具体元素的类型,具体访问者需要实现这些操作方法,定义对这些元素的访问操作

ConcreteVisitor(具体访问者):具体访问者实现了每个由抽象访问者声明的操作,每一个操作用于访问对象结构中一种类型的元素。

Element(抽象元素):抽象元素一般是抽象类或者接口,它定义一个 accept() 方法,该方法通常以一个抽象访问者作为参数。

ConcreteElement(具体元素):具体元素实现了 accept() 方法,在 accept() 方法中调用访问者的访问方法以便完成对一个元素的操作。

ObjectStructure(对象结构):对象结构是一个元素的集合,它用于存放元素对象,并且提供了遍历其内部元素的方法。它可以结合组合模式来实现,也可以是一个简单的集合对象,如一个 List 对象或一个 Set 对象。(我认为可以省略)

使用前后对比

使用前

interface Employee {

}

class EmployeeList {
    private ArrayList<Employee> list = new ArrayList<>(); //员工集合

    //增加员工
    public void addEmployee(Employee employee) {
        list.add(employee);
    }

    //处理员工数据
    public void handle(String departmentName) {
        if (departmentName.equalsIgnoreCase("财务部")) {//财务部处理员工数据

            for (Object obj : list) {
                if (obj.getClass().getName().equalsIgnoreCase("FulltimeEmployee")) {
                    System.out.println("财务部处理全职员工数据!");
                } else {
                    System.out.println("财务部处理兼职员工数据!");
                }
            }
        } else if (departmentName.equalsIgnoreCase("人力资源部")) { //人力资源部处理员工数据
            for (Object obj : list) {
                if (obj.getClass().getName().equalsIgnoreCase("FulltimeEmployee")) {
                    System.out.println("人力资源部处理全职员工数据!");
                } else {
                    System.out.println("人力资源部处理兼职员工数据!");
                }
            }
        }
    }
}
设计模式总结(下)插图5
interface Employee {
    void accept(Department handler); //接受一个抽象访问者访问
}

//全职员工类:具体元素类
class FulltimeEmployee implements Employee {
    private String name;
    private double weeklyWage;
    private int workTime;

    public FulltimeEmployee(String name, double weeklyWage, int workTime) {
        this.name = name;
        this.weeklyWage = weeklyWage;
        this.workTime = workTime;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setWeeklyWage(double weeklyWage) {
        this.weeklyWage = weeklyWage;
    }

    public void setWorkTime(int workTime) {
        this.workTime = workTime;
    }

    public String getName() {
        return (this.name);
    }

    public double getWeeklyWage() {
        return (this.weeklyWage);
    }

    public int getWorkTime() {
        return (this.workTime);
    }

    public void accept(Department handler) {
        handler.visit(this); //调用访问者的访问方法
    }
}

//兼职员工类:具体元素类
class ParttimeEmployee implements Employee {
    private String name;
    private double hourWage;
    private int workTime;

    public ParttimeEmployee(String name, double hourWage, int workTime) {
        this.name = name;
        this.hourWage = hourWage;
        this.workTime = workTime;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setHourWage(double hourWage) {
        this.hourWage = hourWage;
    }

    public void setWorkTime(int workTime) {
        this.workTime = workTime;
    }

    public String getName() {
        return (this.name);
    }

    public double getHourWage() {
        return (this.hourWage);
    }

    public int getWorkTime() {
        return (this.workTime);
    }

    public void accept(Department handler) {
        handler.visit(this); //调用访问者的访问方法
    }
}

//部门类:抽象访问者类
abstract class Department {
    //声明一组重载的访问方法,用于访问不同类型的具体元素
    public abstract void visit(FulltimeEmployee employee);

    public abstract void visit(ParttimeEmployee employee);
}

//人力资源部类:具体访问者类
class HRDepartment extends Department {
    //实现人力资源部对全职员工的访问
    public void visit(FulltimeEmployee employee) {
        int workTime = employee.getWorkTime();
        System.out.println("正式员工" + employee.getName() + "实际工作时间为:" + workTime + "小时。");
        if (workTime > 40) {
            System.out.println("正式员工" + employee.getName() + "加班时间为:" + (workTime - 40) + "小时。");
        } else if (workTime < 40) {
            System.out.println("正式员工" + employee.getName() + "请假时间为:" + (40 - workTime) + "小时。");
        }
    }

    //实现人力资源部对兼职员工的访问
    public void visit(ParttimeEmployee employee) {
        int workTime = employee.getWorkTime();
        System.out.println("临时工" + employee.getName() + "实际工作时间为:" + workTime + "小时。");
    }
}

//员工列表类:对象结构
class EmployeeList {
    //定义一个集合用于存储员工对象
    private ArrayList<Employee> list = new ArrayList<>();

    public void addEmployee(Employee employee) {
        list.add(employee);
    }

    //遍历访问员工集合中的每一个员工对象
    public void accept(Department handler) {
        for (Employee e : list) {
            e.accept(handler);
        }
    }
}

class Client {
    public static void main(String args[]) {
        EmployeeList list = new EmployeeList();
        Employee fte1, fte2, fte3, pte1, pte2;
        fte1 = new FulltimeEmployee("张无忌", 3200.00, 45);
        fte2 = new FulltimeEmployee("杨过", 2000.00, 40);
        fte3 = new FulltimeEmployee("段誉", 2400.00, 38);
        pte1 = new ParttimeEmployee("洪七公", 80.00, 20);
        pte2 = new ParttimeEmployee("郭靖", 60.00, 18);
        list.addEmployee(fte1);
        list.addEmployee(fte2);
        list.addEmployee(fte3);
        list.addEmployee(pte1);
        list.addEmployee(pte2);
        Department dep = new HRDepartment();    // 访问者(从配置文件来)
        list.accept(dep);
    }
}

如果要在系统中增加一种新的访问者,无须修改源代码,只要增加一个新的具体访问者类即可,在该具体访问者 中封装了新的操作元素对象的方法。从增加新的访问者的角度来看,访问者模式符合“开闭原则”。

如果要在系统中增加一种新的具体元素,例如增加一种新的员工类型为“退休人员”,由于原有系统并未提供相 应的访问接口(在抽象访问者中没有声明任何访问“退休人员”的方法),因此必须对原有系统进行修改,在原有的抽象访问者类和具体访问者类中增加相应的访问方法。从增加新的元素的角度来看,访问者模式违背了“开闭原则”。

综上所述,访问者模式与抽象工厂模式类似,对“开闭原则”的支持具有倾斜性,可以很方便地添加新的访问者,但是添加新的元素较为麻烦

访问者+组合模式

设计模式总结(下)插图6
例子看源码中组合模式文件夹

总结

如果有很多方法需要根据原有对象中的信息做一些事情,如果新增一个“这类方法”,那么就要改变类的代码,这样违背了“开闭原则”;如果在类中提供一个visitor方法,当新增一个“这类方法”,只需要增加一个visitor即可。

优点

  1. 增加新的访问操作很方便。使用访问者模式,增加新的访问操作就意味着增加一个新的具体访问者类,实现简单,无须修改源代码,符合“开闭原则”。
  2. 有关元素对象的访问行为集中到一个访问者对象中,而不是分散在一个个的元素类中。类的职责更加清晰,有利于对象结构中元素对象的复用,相同的对象结构可以供多个不同的访问者访问。

缺点

  1. 增加新的元素类很困难。在访问者模式中,每增加一个新的元素类都意味着要在抽象访问者角色中增加一个新的抽象操作,并在每一个具体访问者类中增加相应的具体操作,这违背了“开闭原则”的要求。
  2. 破坏封装。访问者模式要求访问者对象访问并调用每一个元素对象的操作,这意味着元素对象有时候必须暴露一些自己的内部操作和内部状态,否则无法供访问者访问。

何时使用

一个对象结构包含多个类型的对象,希望对这些对象实施一些依赖其具体类型的操作。在访问者中针对每一种具体的类型都提供了一个访问操作**,不同类型的对象可以有不同的访问操作。

符合6大设计原则的哪些

单一职责 符合

接口隔离 符合

依赖倒置 符合

里式替换 未涉及

开闭原则 倾斜性

迪米特法则 未涉及

5.4 迭代器模式

定义

通过引入迭代器可以将数据的遍历功能从聚合对象中分离出来,聚合对象只负责存储数据,而遍历数据由迭代器来完成。由于很多编程语言的类库都已经实现了迭代器模式,因此在实际开发中,我们只需要直接使用 Java、C# 等语言已定义好的迭代器即可,迭代器已经成为我们操 作聚合对象的基本工具之一。

总结

优点

  1. 迭代器模式将存储数据和遍历数据的职责分离
  2. 在迭代器模式中,由于引入了抽象层,增加新的聚合类和迭代器类都很方便,无须修改原有代码,满足“开闭原则”的要求。

何时使用

访问一个聚合对象的内容而无须暴露它的内部表示。将聚合对象的访问与内部数据的存储分离,使得访问聚合对象时无须了解其内部实现细节。

符合6大设计原则的哪些

单一职责 符合,迭代器模式将存储数据和遍历数据的职责分离

接口隔离 符合

依赖倒置 符合

里式替换 未涉及

开闭原则 符合,在迭代器模式中,由于引入了抽象层,增加新的聚合类和迭代器类都很方便,无须修改原有代码,满足“开闭原则”的要求。

迪米特法则 未涉及

5.5 观察者模式

定义

定义对象之间的一种一对多依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象皆得到通知并被自动更新。观察者模式的别名包括发布-订阅(Publish/Subscribe)模式。

设计模式总结(下)插图7
  • Subject(目标):目标又称为主题,它是指被观察的对象。在目标中定义了一个观察者集合,一个观察目标可以接受任意数量的观察者来观察,它提供一系列方法来增加和删除观察者对象,同时它定义了通知方法 notify()。目标类可以是接口,也可以是抽象类或具体类。
  • ConcreteSubject(具体目标):具体目标是目标类的子类,通常它包含有经常发生改变的数据,当它的状态发生改变时,向它的各个观察者发出通知;同时它还实现了在目标类中定义的抽象业务逻辑方法(如果有的话)。如果无须扩展目标类,则具体目标类可以省略。
  • Observer(观察者):观察者将对观察目标的改变做出反应,观察者一般定义为接口,该接口声明了更新数 据的方法update(),因此又称为抽象观察者。
  • ConcreteObserver(具体观察者):在具体观察者中维护一个指向具体目标对象的引用,它存储具体观察 者的有关状态,这些状态需要和具体目标的状态保持一致;它实现了在抽象观察者 Observer 中定义的 update() 方法。通常在实现时,可以调用具体目标类的 attach() 方法将自己添加到目标类的集合中或通过 detach() 方法将自己从目标类的集合中删除。

使用前后对比

使用前

public class WeatherData {
    
    private CurrentConditionsDisplay currentConditionsDisplay = new CurrentConditionsDisplay();

    private float temperature;
    private float humidity;
    private float pressure;

    // 温度
    public float getTemperature() {
        return temperature;
    }

    // 湿度
    public float getHumidity() {
        return humidity;
    }

    // 气压
    public float getPressure() {
        return pressure;
    }

    /**
     * 如果气象测量更新会调用这个方法
     */
    public void measurementsChanged() {
        currentConditionsDisplay.update(temperature, humidity, pressure);
        xxxDisplay.update(temperature, humidity, pressure);
        yyyDisplay.update(temperature, humidity, pressure);
    }
}

class CurrentConditionsDisplay implements DisplayElement {

    private float temperature;
    private float humidity;

    public void update(float temperature, float humidity, float pressure) {
        // 获取该布告板需要的数据
        this.temperature = temperature;
        this.humidity = humidity;

        this.display();
    }

    @Override
    public void display() {
        System.out.println("xxx");
    }
}

上面的代码有一个问题,measurementsChanged方法中是针对具体实现编程,如果日后需要对其进行增删会很麻烦,违背了“开闭原则”、“依赖倒置”。

/**
 * 主题
 */
interface Subject {

    /**
     * 注册观察者
     *
     * @param observer
     */
    void registerObserver(Observer observer);

    /**
     * 移除观察者
     *
     * @param observer
     */
    void removeObserver(Observer observer);

    /**
     * 通知观察者们
     */
    void notifyObservers();
}

class WeatherData implements Subject {

    private float temperature;
    private float humidity;
    private float pressure;

    // 温度
    public float getTemperature() {
        return temperature;
    }

    // 湿度
    public float getHumidity() {
        return humidity;
    }

    // 气压
    public float getPressure() {
        return pressure;
    }

    /**
     * 如果气象测量更新会调用这个方法
     */
    public void measurementsChanged() {
        this.notifyObservers();
    }

    private List<Observer> observers = new ArrayList<>();

    /**
     * 注册观察者
     *
     * @param observer
     */
    @Override
    public void registerObserver(Observer observer) {
        observers.add(observer);
    }

    /**
     * 移除观察者
     *
     * @param observer
     */
    @Override
    public void removeObserver(Observer observer) {
        observers.remove(observer);
    }

    /**
     * 通知观察者们
     */
    @Override
    public void notifyObservers() {
        observers.forEach(observer -> observer.update(temperature, humidity, pressure));
    }
}


// 观察者
interface Observer {

    void update(float temperature, float humidity, float pressure);
}


/**
 * 所有的布告板都要实现这个
 */
interface DisplayElement {

    void display();
}

public class CurrentConditionsDisplay implements Observer, DisplayElement {

    private Subject subject;

    private float temperature;
    private float humidity;

    public CurrentConditionsDisplay(Subject subject) {
        this.subject = subject;

        subject.registerObserver(this);
    }

    @Override
    public void update(float temperature, float humidity, float pressure) {
        // 获取该布告板需要的数据
        this.temperature = temperature;
        this.humidity = humidity;

        this.display();
    }

    @Override
    public void display() {
        System.out.println("xxx");
    }
}

class Client {
    // 客户端
    public static void main(String[] args) {

        Subject subject = new WeatherData();

        Observer observer = new CurrentConditionsDisplay(subject);
        // 等待气象信息改变就行了
    }
}

总结

优点

  1. 观察者模式可以实现表示层和数据逻辑层的分离,定义了稳定的消息更新传递机制,并抽象了更新接口,使得可以有各种各样不同的表示层充当具体观察者角色。
  2. 观察者模式在观察目标和观察者之间建立一个抽象的耦合。观察目标只需要维持一个抽象观察者的集合,无须了解其具体观察者**。由于观察目标和观察者没有紧密地耦合在一起,因此它们可以属于不同的抽象化层次。
  3. 观察者模式支持广播通信,观察目标会向所有已注册的观察者对象发送通知,简化了一对多系统设计的难度。
  4. 观察者模式满足“开闭原则”的要求,增加新的具体观察者无须修改原有系统代码,在具体观察者与观察目标之间不存在关联关系的情况下,增加新的观察目标也很方便。

何时使用

一个对象的改变将导致一个或多个其他对象也发生改变,而并不知道具体有多少对象将发生改变,也不知道这些对象是谁。

需要在系统中创建一个触发链,A 对象的行为将影响B对象,B 对象的行为将影响 C 对象......,可以使用观察者模式创建一种链式触发机制。

符合6大设计原则的哪些

单一职责 符合

接口隔离 符合

依赖倒置 符合

里式替换 未涉及

开闭原则 符合

迪米特法则 未涉及

5.6 中介者模式

定义

用一个中介对象(中介者)来封装一系列的对象交互,中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。

设计模式总结(下)插图8
  • Mediator(抽象中介者):它定义一个接口,该接口用于与各同事对象之间进行通信。
  • ConcreteMediator(具体中介者):它是抽象中介者的子类,通过协调各个同事对象来实现协作行为,它维持了对各个同事对象的引用。
  • Colleague(抽象同事类):它定义各个同事类公有的方法,并声明了一些抽象方法来供子类实现,同时它维持了一个对抽象中介者类的引用,其子类可以通过该引用来与中介者通信。
  • ConcreteColleague(具体同事类):它是抽象同事类的子类;每一个同事对象在需要和其他同事对象通信时,先与中介者通信,通过中介者来间接完成与其他同事类的通信;在具体同事类中实现了在抽象同事类中 声明的抽象方法。

中介者模式的核心在于中介者类的引入,在中介者模式中,中介者类承担了两方面的职责:

(1) 中转作用(结构性):通过中介者提供的中转作用,各个同事对象就不再需要显式引用其他同事,当需要和其他同事进行通信时,可通过中介者来实现间接调用。该中转作用属于中介者在结构上的支持。

(2) 协调作用(行为性):中介者可以更进一步的对同事之间的关系进行封装,同事可以一致的和中介者进行交互,而不需要指明中介者需要具体怎么做中介者根据封装在自身内部的协调逻辑,对同事的请求进行进一步处理,将同事成员之间的关系行为进行分离和封装。该协调作用属于中介者在行为上的支持。

使用前后对比

设计模式总结(下)插图9
设计模式总结(下)插图10
//抽象中介者
abstract class Mediator {
    public abstract void componentChanged(Component c);
}

//具体中介者
class ConcreteMediator extends Mediator {
    //维持对各个同事对象的引用
    public List list;
    public TextBox userNameTextBox;
    public ComboBox cb;

    //封装同事对象之间的交互(此处可以采用命令模式进行包装)
    public void componentChanged(Component c) {
        //单击按钮
        //从列表框选择客户
        if (c == list) {
            System.out.println("--从列表框选择客户--");
            cb.select();
            userNameTextBox.setText();
        }
        //从组合框选择客户
        else if (c == cb) {
            System.out.println("--从组合框选择客户--");
            cb.select();
            userNameTextBox.setText();
        }
    }

}

//抽象组件类:抽象同事类
abstract class Component {
    protected Mediator mediator;

    public void setMediator(Mediator mediator) {
        this.mediator = mediator;
    }

    //转发调用
    public void changed() {
        mediator.componentChanged(this);
    }

    public abstract void update();
}

//列表框类:具体同事类
class List extends Component {
    public void update() {
        System.out.println("列表框增加一项:张无忌。");
    }

    public void select() {
        System.out.println("列表框选中项:小龙女。");
    }
}

//组合框类:具体同事类
class ComboBox extends Component {
    public void update() {
        System.out.println("组合框增加一项:张无忌。");
    }

    public void select() {
        System.out.println("组合框选中项:小龙女。");
    }
}

//文本框类:具体同事类
class TextBox extends Component {
    public void update() {
        System.out.println("客户信息增加成功后文本框清空。");
    }

    public void setText() {
        System.out.println("文本框显示:小龙女。");
    }
}

class Client {
    public static void main(String args[]) {

        //定义中介者对象
        ConcreteMediator mediator = new ConcreteMediator();

        //定义同事对象
        List list = new List();
        ComboBox cb = new ComboBox();
        TextBox userNameTB = new TextBox();

        // 为同事对象设置中介者
        list.setMediator(mediator);
        cb.setMediator(mediator);
        userNameTB.setMediator(mediator);

        // 为中介者设置同事类
        mediator.list = list;
        mediator.cb = cb;
        mediator.userNameTextBox = userNameTB;

        // 调用
        list.changed();
    }
}

总结

优点

  1. 简化了对象之间的交互,它用中介者和同事的一对多交互代替了原来同事之间的多对多交互,一对多关系更容易理解、维护和扩展,将原本难以理解的网状结构转换成相对简单的星型结构。
  2. 将各同事对象解耦。中介者有利于各同事之间的松耦合,我们可以独立的改变和复用每一个同事和中介者,增加新的中介者和新的同事类都比较方便,更好地符合“开闭原则”。
  3. 可以减少子类生成,中介者将原本分布于多个对象间的行为集中在一起,改变这些行为只需生成新的中介者子类即可,这使各个同事类可被重用,无须对同事类进行扩展。

缺点

  1. 在具体中介者类中包含了大量同事之间的交互细节,可能会导致具体中介者类非常复杂,使得系统难以维护。

何时使用

  1. 系统中对象之间存在复杂的引用关系,系统结构混乱且难以理解。
  2. 一个对象由于引用了其他很多对象并且直接和这些对象通信,导致难以复用该对象

符合6大设计原则的哪些

单一职责 符合

接口隔离 符合

依赖倒置 符合

里式替换 未涉及

开闭原则 符合

迪米特法则 未涉及

5.7 备忘录模式

定义

在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样可以在以后将对象恢复到原先保存的状态。

设计模式总结(下)插图11
  • Originator(原发器):它是一个普通类,可以创建一个备忘录,并存储它的当前内部状态,也可以使用备忘录来恢复其内部状态,一般将需要保存内部状态的类设计为原发器。
  • Memento(备忘录):存储原发器的内部状态,根据原发器来决定保存哪些内部状态。备忘录的设计一般可以参考原发器的设计,根据实际需要确定备忘录类中的属性。需要注意的是,除了原发器本身与负责人类之外,备忘录对象不能直接供其他类使用,原发器的设计在不同的编程语言中实现机制会有所不同。
  • Caretaker(负责人):负责人又称为管理者,它负责保存备忘录,但是不能对备忘录的内容进行操作或检查。在负责人类中可以存储一个或多个备忘录对象,它只负责存储对象,而不能修改对象,也无须知道对象 的实现细节。

备忘录是一个很特殊的对象,只有原发器对它拥有控制的权力,负责人只负责管理,而其他类无法访问到备忘录,因此我们需要对备忘录进行封装。

为了实现对备忘录对象的封装,需要对备忘录的调用进行控制,对于原发器而言,它可以调用备忘录的所有信息,允许原发器访问返回到先前状态所需的所有数据;对于负责人而言,只负责备忘录的保存并将备忘录传递给其他对象;对于其他对象而言,只需要从负责人处取出备忘录对象并将原发器对象的状态恢复,而无须关心备忘录的保存细节。理想的情况是只允许生成该备忘录的那个原发器访问备忘录的内部状态。

使用前后对比

传统的方式是简单地做备份,new出另外一个对象出来,再把需要备份的数据放到这个新对象,但这就暴露了对象内部的细节

//象棋棋子类:原发器
class Chessman {
    private String label;
    private int x;
    private int y;

    public Chessman(String label, int x, int y) {
        this.label = label;
        this.x = x;
        this.y = y;
    }

    public void setLabel(String label) {
        this.label = label;
    }

    public void setX(int x) {
        this.x = x;
    }

    public void setY(int y) {
        this.y = y;
    }

    public String getLabel() {
        return (this.label);
    }

    public int getX() {
        return (this.x);
    }

    public int getY() {
        return (this.y);
    }

    //保存状态
    public ChessmanMemento save() {
        return new ChessmanMemento(this.label, this.x, this.y);
    }

    //恢复状态
    public void restore(ChessmanMemento memento) {
        this.label = memento.getLabel();
        this.x = memento.getX();
        this.y = memento.getY();
    }
}

//象棋棋子备忘录类:备忘录
class ChessmanMemento {
    private String label;
    private int x;
    private int y;

    public ChessmanMemento(String label, int x, int y) {
        this.label = label;
        this.x = x;
        this.y = y;
    }

    public void setLabel(String label) {
        this.label = label;
    }

    public void setX(int x) {
        this.x = x;
    }

    public void setY(int y) {
        this.y = y;
    }

    public String getLabel() {
        return (this.label);
    }

    public int getX() {
        return (this.x);
    }

    public int getY() {
        return (this.y);
    }
}

//象棋棋子备忘录管理类:负责人
class MementoCaretaker {
    private ChessmanMemento memento;

    public ChessmanMemento getMemento() {
        return memento;
    }

    public void setMemento(ChessmanMemento memento) {
        this.memento = memento;
    }
}

class Client {
    public static void main(String args[]) {
        MementoCaretaker mc = new MementoCaretaker();

        Chessman chess = new Chessman("车", 1, 1);
        display(chess);

        mc.setMemento(chess.save()); //保存状态
        chess.setY(4);
        display(chess);

        mc.setMemento(chess.save()); //保存状态
        chess.setX(5);
        display(chess);
        
        System.out.println("******悔棋******");
        chess.restore(mc.getMemento()); //恢复状态
        display(chess);
    }

    public static void display(Chessman chess) {
        System.out.println("棋子" + chess.getLabel() + "当前位置为:" + "第" + chess.getX() + "行" + "第" + chess.getY() + "列。");
    }
}

如果遇到多次撤销功能,一定要看书,书上写的很好。

总结

优点

  1. 提供了一种状态恢复的实现机制,使得用户可以方便地回到一个特定的历史步骤,当新的状态无效或者存在问题时,可以使用暂时存储起来的备忘录将状态复原。

何时使用

  1. 保存一个对象在某一个时刻的全部状态或部分状态,这样以后需要时它能够恢复到先前的状态,实现撤销操作。
  2. 防止外界对象破坏一个对象历史状态的封装性,避免将对象历史状态的实现细节暴露给外界对象。

符合6大设计原则的哪些

单一职责 符合,将保存状态的工作与对象分离开来

接口隔离 未涉及

依赖倒置 未涉及

里式替换 未涉及

开闭原则 未涉及

迪米特法则 未涉及

5.8 解释器模式

5.9 状态模式

定义

允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类。

设计模式总结(下)插图12
  • Context(环境类):环境类又称为上下文类,它拥有多种状态的对象。由于环境类的状态存在多样性且在不同状态下对象的行为有所不同,因此将状态独立出去形成单独的状态类。在环境类中维护一个抽象状态 类 State 的实例,这个实例定义当前状态,在具体实现时,它是一个 State 子类的对象。
  • State(抽象状态类):它用于定义一个接口以封装与环境类的一个特定状态相关的行为**,在抽象状态类中声明了各种不同状态对应的方法,而在其子类中实现类这些方法,由于不同状态下对象的行为可能不同,因此 在不同子类中方法的实现可能存在不同,相同的方法可以写在抽象状态类中。
  • ConcreteState(具体状态类):它是抽象状态类的子类,每一个子类实现一个与环境类的一个状态相关的行为,每一个具体状态类对应环境的一个具体状态,不同的具体状态类其行为有所不同。

使用前后对比

使用前

/*
问题:

1. 几乎每个方法中都包含状态判断语句,以判断在该状态下是否具有该方法以及在特定状态下该方法如何实现,导致代码非常冗长,可维护性较差;
2. 拥有一个较为复杂的 stateCheck() 方法,包含大量的 if...else if...else... 语句用于进行状态转换,代码测试难度较大,且不易于维护;
3. 系统扩展性较差,如果需要增加一种新的状态,如冻结状态(Frozen State,在该状态下既不允许存款也不允许取款),需要对原有代码进行大量修改(几乎要对每一个方法都要改),扩展起来非常麻烦。
 */
class Account {
    private String state; //状态
    private int balance; //余额

    //存款操作
    public void deposit() {
        //存款...
        stateCheck();
    }

    //取款操作
    public void withdraw() {
        if (state.equalsIgnoreCase("正常状态") || state.equalsIgnoreCase("透支状态")) {
            //取款...
            stateCheck();
        } else {
            //取款受限
        }
    }

    //计算利息操作
    public void computeInterest() {
        if (state.equalsIgnoreCase("透支状态") || state.equalsIgnoreCase("限制状态")) {
            //计算利息...
        }
    }

    //状态检查和转换操作
    public void stateCheck() {
        if (balance >= 0) {
            state = "正常状态";
        } else if (balance > -2000 && balance < 0) {
            state = "透支状态";
        } else if (balance == -2000) {
            state = "限制状态";
        } else if (balance < -2000) {
            //操作受限,不修改状态
        }
    }
}

使用后:

//银行账户:环境类
class Account {
    private AccountState state; //维持一个对抽象状态对象的引用
    private String owner; //开户名
    private double balance = 0; //账户余额

    public Account(String owner, double balance) {
        this.owner = owner;
        this.balance = balance;
        this.state = new NormalState(this); //设置初始状态
        System.out.println(this.owner + "开户,初始金额为" + balance);
        System.out.println("---------------------------------------------");
    }

    public double getBalance() {
        return this.balance;
    }

    public void setBalance(double balance) {
        this.balance = balance;
    }

    public void setState(AccountState state) {
        this.state = state;
    }

    public void deposit(double amount) {
        System.out.println(this.owner + "存款" + amount);
        state.deposit(amount); //调用状态对象的deposit()方法
        System.out.println("现在余额为" + this.balance);
        System.out.println("现在帐户状态为" + this.state.getClass().getName());
        System.out.println("---------------------------------------------");
    }

    public void withdraw(double amount) {
        System.out.println(this.owner + "取款" + amount);
        state.withdraw(amount); //调用状态对象的withdraw()方法
        System.out.println("现在余额为" + this.balance);
        System.out.println("现在帐户状态为" + this.state.getClass().getName());
        System.out.println("---------------------------------------------");
    }

    public void computeInterest() {
        state.computeInterest(); //调用状态对象的computeInterest()方法
    }
}

//抽象状态类
abstract class AccountState {
    protected Account acc;

    public abstract void deposit(double amount);

    public abstract void withdraw(double amount);

    public abstract void computeInterest();

    public abstract void stateCheck();
}

//正常状态:具体状态类
class NormalState extends AccountState {
    public NormalState(Account acc) {
        this.acc = acc;
    }

    public NormalState(AccountState state) {
        this.acc = state.acc;
    }

    public void deposit(double amount) {
        acc.setBalance(acc.getBalance() + amount);
        stateCheck();
    }

    public void withdraw(double amount) {
        acc.setBalance(acc.getBalance() - amount);
        stateCheck();
    }

    public void computeInterest() {
        System.out.println("正常状态,无须支付利息!");
    }

    //状态转换
    public void stateCheck() {
        if (acc.getBalance() > -2000 && acc.getBalance() <= 0) {
            acc.setState(new OverdraftState(this));
        } else if (acc.getBalance() == -2000) {
            acc.setState(new RestrictedState(this));
        } else if (acc.getBalance() < -2000) {
            System.out.println("操作受限!");
        }
    }
}

//透支状态:具体状态类
class OverdraftState extends AccountState {
    public OverdraftState(AccountState state) {
        this.acc = state.acc;
    }

    public void deposit(double amount) {
        acc.setBalance(acc.getBalance() + amount);
        stateCheck();
    }

    public void withdraw(double amount) {
        acc.setBalance(acc.getBalance() - amount);
        stateCheck();
    }

    public void computeInterest() {
        System.out.println("计算利息!");
    }

    //状态转换
    public void stateCheck() {
        if (acc.getBalance() > 0) {
            acc.setState(new NormalState(this));
        } else if (acc.getBalance() == -2000) {
            acc.setState(new RestrictedState(this));
        } else if (acc.getBalance() < -2000) {
            System.out.println("操作受限!");
        }
    }
}

//受限状态:具体状态类
class RestrictedState extends AccountState {
    public RestrictedState(AccountState state) {
        this.acc = state.acc;
    }

    public void deposit(double amount) {
        acc.setBalance(acc.getBalance() + amount);
        stateCheck();
    }

    public void withdraw(double amount) {
        System.out.println("帐号受限,取款失败");
    }

    public void computeInterest() {
        System.out.println("计算利息!");
    }

    //状态转换
    public void stateCheck() {
        if (acc.getBalance() > 0) {
            acc.setState(new NormalState(this));
        } else if (acc.getBalance() > -2000) {
            acc.setState(new OverdraftState(this));
        }
    }
}

class Client {
    public static void main(String args[]) {
        Account acc = new Account("段誉", 0.0);
        acc.deposit(1000);
        acc.withdraw(2000);
        acc.deposit(3000);
        acc.withdraw(4000);
        acc.withdraw(1000);
        acc.computeInterest();
    }
}

这就是状态模式的典型实现,其中我们注意到,状态类好像不满足“单一职责”,它即负责状态的行为又管理了状态的切换,当然这是状态模式做的取舍。不过我们可以把状态的切换转移到Context类中,解决这个问题,例如:

//屏幕类
class Screen {
    //枚举所有的状态,currentState表示当前状态
    private State currentState, normalState, largerState, largestState;

    public Screen() {
        this.normalState = new NormalState(); //创建正常状态对象
        this.largerState = new LargerState(); //创建二倍放大状态对象
        this.largestState = new LargestState(); //创建四倍放大状态对象
        this.currentState = normalState; //设置初始状态
        this.currentState.display();
    }

    public void setState(State state) {
        this.currentState = state;
    }

    //单击事件处理方法,封转了对状态类中业务方法的调用和状态的转换
    public void onClick() {
        if (this.currentState == normalState) {
            this.setState(largerState);
            this.currentState.display();
        } else if (this.currentState == largerState) {
            this.setState(largestState);
            this.currentState.display();
        } else if (this.currentState == largestState) {
            this.setState(normalState);
            this.currentState.display();
        }
    }
}

//抽象状态类
abstract class State {
    public abstract void display();
}

//正常状态类
class NormalState extends State {
    public void display() {
        System.out.println("正常大小!");
    }
}

//二倍状态类
class LargerState extends State {
    public void display() {
        System.out.println("二倍大小!");
    }
}

//四倍状态类
class LargestState extends State {
    public void display() {
        System.out.println("四倍大小!");
    }
}

class Client {
    public static void main(String args[]) {
        Screen screen = new Screen();
        screen.onClick();
        screen.onClick();
        screen.onClick();
    }
}

总结

优点

  1. 封装了状态的转换规则,在状态模式中可以将状态的转换代码封装在环境类或者具体状态类中,可以对状态转换代码进行集中管理,而不是分散在一个个业务方法中。
  2. 将所有与某个状态有关的行为放到一个类中,只需要注入一个不同的状态对象即可使环境对象拥有不同的行为。
  3. 允许状态转换逻辑与状态对象合成一体,而不是提供一个巨大的条件语句块,状态模式可以让我们避免使用庞 大的条件语句来将业务方法和状态转换代码交织在一起。

缺点

状态模式对“开闭原则”的支持并不太好,增加新的状态类需要修改那些负责状态转换的源代码,否则无法转换到新增状态;而且修改某个状态类的行为也需修改对应类的源代码。

何时使用

  1. 对象的行为依赖于它的状态(如某些属性值),状态的改变将导致行为的变化。
  2. 在代码中包含大量与对象状态有关的条件语句,这些条件语句的出现,会导致代码的可维护性和灵活性变差,不能方便地增加和删除状态,并且导致客户类与类库之间的耦合增强。

符合6大设计原则的哪些

我觉得它不符合“单一职责”<状态类负责状态和状态切换两个纬度>和“开闭原则”

单一职责 中间

接口隔离 符合

依赖倒置 符合

里式替换 未涉及

开闭原则 状态模式对“开闭原则”的支持并不太好,增加新的状态类需要修改那些负责状态转换的源代码,否则无法转换到新增状态;而且修改某个状态类的行为也需修改对应类的源代码。

迪米特法则 未涉及

5.10 策略模式

定义

实现某一个功能有多条途径,每一条途径对应一种算法,此时我们可以使用一种设计模式来实现灵活地选择解决途径,也能够方便地增加新的解决途径。

定义一系列算法类,将每一个算法封装起来,并让它们可以相互替换,策略模式让算法独立于使用它的客户而变化。

设计模式总结(下)插图13
  • Context(环境类):环境类是使用算法的角色,它在解决某个问题(即实现某个方法)时可以采用多种策略。在环境类中维持一个对抽象策略类的引用实例,用于定义所采用的策略。
  • Strategy(抽象策略类):它为所支持的算法声明了抽象方法,是所有策略类的父类,它可以是抽象类或具体类,也可以是接口。环境类通过抽象策略类中声明的方法在运行时调用具体策略类中实现的算法。
  • ConcreteStrategy(具体策略类):它实现了在抽象策略类中声明的算法,在运行时,具体策略类将覆盖在环境类中定义的抽象策略类对象,使用一种具体的算法实现某个业务处理。

使用前后对比

使用前

//电影票类
class MovieTicket {
    private double price; //电影票价格
    private String type; //电影票类型

    public void setPrice(double price) {
        this.price = price;
    }

    public void setType(String type) {
        this.type = type;
    }

    public double getPrice() {
        return this.calculate();
    }

    //计算打折之后的票价
    public double calculate() {
        //学生票折后票价计算
        if (this.type.equalsIgnoreCase("student")) {
            System.out.println("学生票:");
            return this.price * 0.8;
        }
        //儿童票折后票价计算
        else if (this.type.equalsIgnoreCase("children") && this.price >= 20) {
            System.out.println("儿童票:");
            return this.price - 10;
        }
        //VIP票折后票价计算
        else if (this.type.equalsIgnoreCase("vip")) {
            System.out.println("VIP票:");
            System.out.println("增加积分!");
            return this.price * 0.5;
        } else {
            return this.price; //如果不满足任何打折要求,则返回原始票价 }
        }
    }
}

public class Client {
    public static void main(String args[]) {
        MovieTicket mt = new MovieTicket();
        double originalPrice = 60.0; //原始票价

        double currentPrice; //折后价
        mt.setPrice(originalPrice);
        System.out.println("原始价为:" + originalPrice);
        System.out.println("---------------------------------");

        mt.setType("student"); //学生票
        currentPrice = mt.getPrice();
        System.out.println("折后价为:" + currentPrice);
        System.out.println("---------------------------------");

        mt.setType("children"); //儿童票
        currentPrice = mt.getPrice();
        System.out.println("折后价为:" + currentPrice);
    }
}

很明显,如果日后增加一种军人票,我们就需要修改MovieTicket的代码,calculate方法中封装这不同票价的计算方式,所以,可以将其提取出来。

//电影票类:Context
class MovieTicket {
    private double price;
    private Discount discount; //维持一个对抽象折扣类的引用

    public void setPrice(double price) {
        this.price = price;
    }

    //注入一个折扣类对象
    public void setDiscount(Discount discount) {
        this.discount = discount;
    }

    public double getPrice() { //调用折扣类的折扣价计算方法
        return discount.calculate(this.price);
    }

}

//折扣类:抽象策略类
interface Discount {
    public double calculate(double price);
}

//学生票折扣类:具体策略类
class StudentDiscount implements Discount {
    public double calculate(double price) {
        System.out.println("学生票:");
        return price * 0.8;
    }
}

//儿童票折扣类:具体策略类
class ChildrenDiscount implements Discount {
    public double calculate(double price) {
        System.out.println("儿童票:");
        return price - 10;
    }
}

//VIP会员票折扣类:具体策略类
class VIPDiscount implements Discount {
    public double calculate(double price) {
        System.out.println("VIP票:");
        System.out.println("增加积分!");
        return price * 0.5;
    }
}

class Client {
    public static void main(String args[]) {
        MovieTicket mt = new MovieTicket();
        double originalPrice = 60.0;
        double currentPrice;
        mt.setPrice(originalPrice);
        System.out.println("原始价为:" + originalPrice);
        System.out.println("---------------------------------");
        Discount discount = new VIPDiscount(); // 例如,采用工厂根据type获取出来啊
        mt.setDiscount(discount); //注入折扣对象
        currentPrice = mt.getPrice();
        System.out.println("折后价为:" + currentPrice);
    }
}

例如Arrays#sort(T[], Comparator<? super T> c)方法

TimSort.sort(a, 0, a.length, c, null, 0, 0);

在这里TimSort就是Context类,Comparator<? super T>就是我们的策略类。

?为啥Thread的Runable是命令类,Comparator却是策略类

命令模式中不同的命令是做不同的事情的,Runable接口我们没有定义它必须要做什么,他想干啥干啥,而策略模式的不同策略类都是为了达到同一目的的,例如不同的Comparator<T>实现类,都是为了将集合中的元素排序的。

总结

优点

  1. 策略模式提供了对“开闭原则”的完美支持,用户可以在不修改原有系统的基础上选择算法或行为,也可以灵活地增加新的算法或行为。
  2. 策略模式提供了管理相关的算法族的办法。策略类的等级结构定义了一个算法或行为族,恰当使用继承可以把公共的代码移到抽象策略类中,从而避免重复的代码。

何时使用

一个系统需要动态地在几种算法中选择一种,那么可以将这些算法封装到一个个的具体算法类中,而这些具体算法类都是一个抽象算法类的子类。

符合6大设计原则的哪些

单一职责 符合

接口隔离 符合

依赖倒置 符合

里式替换 未涉及

开闭原则 策略模式提供了对“开闭原则”的完美支持,用户可以在不修改原有系统的基础上选择算法或行为,也可以灵活地增加新的算法或行为。

迪米特法则 未涉及

5.11 责任链模式

定义

责任链模式在面向对象程式设计里是一种软件设计模式,它包含了一些命令对象和一系列的处理对象。每一个处理对象决定它能处理哪些命令对象,它也知道如何将它不能处理的命令对象传递给该链中的下一个处理对象。该模式还描述了往该处理链的末尾添加新的处理对象的方法。 —— WIKI,https://www.cnblogs.com/amei0/p/7941242.html

避免请求发送者与接收者耦合在一起,让多个对象都有可能接收请求,将这些对象连接成一条链,并且沿着这条链传递请求,直到有对象处理它为止。

设计模式总结(下)插图14
  • Handler(抽象处理者):它定义了一个处理请求的接口,一般设计为抽象类,由于不同的具体处理者处理 请求的方式不同,因此在其中定义了抽象请求处理方法。因为每一个处理者的下家还是一个处理者,因此在 抽象处理者中定义了一个抽象处理者类型的对象(如结构图中的 successor),作为其对下家的引用。通过 该引用,处理者可以连成一条链。
  • ConcreteHandler(具体处理者):它是抽象处理者的子类,可以处理用户请求,在具体处理者类中实现了 抽象处理者中定义的抽象请求处理方法,在处理请求之前需要进行判断,看是否有相应的处理权限,如果可以处理请求就处理它,否则将请求转发给后继者;在具体处理者中可以访问链中下一个对象,以便请求的转发。

具体处理者是抽象处理者的子类,它具有两大作用:第一是处理请求,不同的具体处理者以不同的形式实现抽象 请求处理方法 handleRequest();第二是转发请求,如果该请求超出了当前处理者类的权限,可以将该请求转发给下家。具体处理者类的典型代码如下:

使用前后对比

/*
问题:

1. PurchaseRequestHandler 类较为庞大,各个级别的审批方法都集中在一个类中,违反了“单一职责原则”,测试和维护难度大。
2. 如果需要增加一个新的审批级别或调整任何一级的审批金额和审批细节(例如将董事长的审批额度改为 60 万元)时都必须修改源代码并进行严格测试,此外,如果需要移除某一级别(例如金额为 10 万元及以上的采购单直接由董事长审批,不再设副董事长一职)时也必须对源代码进行修改,违反了“开闭原则”。
3. 审批流程的设置缺乏灵活性,现在的审批流程是“主任-->副董事长-->董事长-->董事会”,如果需要改 为“主任-->董事长-->董事会”,在此方案中只能通过修改源代码来实现,客户端无法定制审批流程。
 */
class PurchaseRequest {
    int amount;

    public int getAmount() {
        return amount;
    }

    public PurchaseRequest(int amount) {
        this.amount = amount;
    }
}


//采购单处理类
class PurchaseRequestHandler {
    //递交采购单给主任
    public void sendRequestToDirector(PurchaseRequest request) {
        if (request.getAmount() < 50000) { //主任可审批该采购单
            this.handleByDirector(request);
        } else if (request.getAmount() < 100000) {//副董事长可审批该采购单
            this.handleByVicePresident(request);
        } else if (request.getAmount() < 500000) { //董事长可审批该采购单
            this.handleByPresident(request);
        } else {
            //董事会可审批该采购单
            this.handleByCongress(request);
        }
    }

    //主任审批采购单
    public void handleByDirector(PurchaseRequest request) {
        //代码省略
    }

    //副董事长审批采购单
    public void handleByVicePresident(PurchaseRequest request) {
        //代码省略
    }

    //董事长审批采购单
    public void handleByPresident(PurchaseRequest request) {
        //代码省略
    }

    //董事会审批采购单
    public void handleByCongress(PurchaseRequest request) {
        //代码省略
    }
}

class Client {
    public static void main(String[] args) {
        PurchaseRequest purchaseRequest = new PurchaseRequest(10000);

        new PurchaseRequestHandler().handleByCongress(purchaseRequest);
    }
}

使用后

class PurchaseRequest {
    private double amount; //采购金额
    private int number; //采购单编号
    private String purpose; //采购目的

    public PurchaseRequest(double amount, int number, String purpose) {
        this.amount = amount;
        this.number = number;
        this.purpose = purpose;
    }

    public void setAmount(double amount) {
        this.amount = amount;
    }

    public double getAmount() {
        return this.amount;
    }

    public void setNumber(int number) {
        this.number = number;
    }

    public int getNumber() {
        return this.number;
    }

    public void setPurpose(String purpose) {
        this.purpose = purpose;
    }

    public String getPurpose() {
        return this.purpose;
    }
}

//审批者类:抽象处理者
abstract class Approver {
    protected Approver successor; //定义后继对象
    protected String name; //审批者姓名

    public Approver(String name) {
        this.name = name;
    }

    //设置后继者
    public void setSuccessor(Approver successor) {
        this.successor = successor;
    }

    //抽象请求处理方法
    public abstract void processRequest(PurchaseRequest request);
}

//主任类:具体处理者
class Director extends Approver {
    public Director(String name) {
        super(name);
    }

    //具体请求处理方法
    public void processRequest(PurchaseRequest request) {
        if (request.getAmount() < 50000) {
            System.out.println("主任" + this.name + "审批采购单:" + request.getNumber() + ",金额:" + request.getAmount());
        } else {
            this.successor.processRequest(request); //转发请求
        }
    }
}

//副董事长类:具体处理者
class VicePresident extends Approver {
    public VicePresident(String name) {
        super(name);
    }

    //具体请求处理方法
    public void processRequest(PurchaseRequest request) {
        if (request.getAmount() < 100000) {
            System.out.println("副董事长" + this.name + "审批采购单:" + request.getNumber() + ",金额:" + request.getAmount());
        } else {
            this.successor.processRequest(request); //转发请求
        }
    }
}

//董事长类:具体处理者
class President extends Approver {
    public President(String name) {
        super(name);
    }

    //具体请求处理方法
    public void processRequest(PurchaseRequest request) {
        if (request.getAmount() < 500000) {
            System.out.println("董事长" + this.name + "审批采购单:" + request.getNumber() + ",金额:" + request.getAmount());
        } else {
            this.successor.processRequest(request); //转发请求
        }
    }
}

//董事会类:具体处理者
class Congress extends Approver {
    public Congress(String name) {
        super(name);
    }

    //具体请求处理方法
    public void processRequest(PurchaseRequest request) {
        System.out.println("召开董事会审批采购单:" + request.getNumber() + ",金额:" + request.getAmount());
    }
}

class Client {
    public static void main(String[] args) {
        Approver wjzhang, gyang, jguo, meeting;
        wjzhang = new Director("张无忌");
        gyang = new VicePresident("杨过");
        jguo = new President("郭靖");
        meeting = new Congress("董事会");
        //创建职责链
        wjzhang.setSuccessor(gyang);
        gyang.setSuccessor(jguo);
        jguo.setSuccessor(meeting);

        //创建采购单
        PurchaseRequest pr1 = new PurchaseRequest(45000, 10001, "购买倚天剑");
        wjzhang.processRequest(pr1);

        PurchaseRequest pr2 = new PurchaseRequest(60000, 10002, "购买《葵花宝典》");
        wjzhang.processRequest(pr2);

        PurchaseRequest pr3 = new PurchaseRequest(160000, 10003, "购买《金刚经》");
        wjzhang.processRequest(pr3);

        PurchaseRequest pr4 = new PurchaseRequest(800000, 10004, "购买桃花岛");
        wjzhang.processRequest(pr4);
    }
}

总结

优点

  1. 将请求的发送者和接收者解耦(好熟悉啊,和命令模式一样)

命令模式通过“工厂/配置”获取一个Command,然后交由Invoker进行执行,因为在命令模式中,一个“按钮”对应一个接收者;而责任链模式则是一个请求有多个接收者,所以它通过一个链子来完成,具体的角色如下:

  • Request对象为命令角色
  • 链头为Invoker角色
  • 链子上的每个Handler为Revicver+“工厂”(判断是否由这个Handler处理)角色
  1. 在系统中增加一个新的具体请求处理者时无须修改原有系统的代码,只需要在客户端重新建链即可,从这一点来看是符合“开闭原则”的。

缺点

何时使用

  1. 在不明确指定接收者的情况下,向多个对象中的一个提交一个请求。
  2. 可动态指定一组对象处理请求,客户端可以动态创建职责链来处理请求,还可以改变链中处理者之间的先后次序。
  3. 有“往返”,向Filter那样

符合6大设计原则的哪些

单一职责 看怎么说了,它负责处理请求和转发请求2个职责

接口隔离 符合

依赖倒置 符合

里式替换 未涉及

开闭原则 在系统中增加一个新的具体请求处理者时无须修改原有系统的代码,只需要在客户端重新建链即可,从这一点来看是符合“开闭原则”的。

迪米特法则 未涉及


?Thread类符合单一原则吗

不符合

?工厂、适配器模式怎样实现了依赖倒置

?继承、组合与耦合

继承关系最大的弱点是打破了封装,子类能够访问父类的实现细节,子类与父类之间紧密耦合

组合:使用灵活,不破坏封装,整体类与局部类之间松耦合(不会因为被包装类突然加个方法就使得包装类多了一个方法),彼此相对独立

https://www.jianshu.com/p/d345d1bc7e4a

?建造者 VS 模板方法

一个是行为、一个是结构,行为包含结构

?1、适配器 VS 命令

https://www.iteye.com/topic/141240

你可以说这个地方把 HttpRequestHandler 适配成了 HandlerAdapter

也可以说这个地方把对 HttpRequestHandler 的操作封装成了一个命令

同上

?2、命令 VS 策略(计算打开文件那个地方)

我可以说开灯、开空调是一种命令

我也可以说它是打开电器的一种策略啊,我按这个灯亮,按这个空调开

https://blog.csdn.net/jiafu1115/article/details/6980423

不要纠结命令还是策略,他们的目的就是为了避免一大堆if else...导致代码逻辑复杂,不满足开闭原则。

策略模式和命令模式相似,特别是命令模式退化时,比如无接收者(接收者非常简单或者接收者是一个Java的基础操作,无需专门编写一个接收者),在这种情况下,命令模式和策略模式的类图完全一样,代码实现也比较类似,但是两者还是有区别的。

● 关注点不同

策略模式关注的是算法替换的问题,一个新的算法投产,旧算法退休,或者提供多种算法由调用者自己选择使用,算法的自由更替是它实现的要点。换句话说,策略模式关注的是算法的完整性、封装性,只有具备了这两个条件才能保证其可以自由切换。

命令模式则关注的是解耦问题,如何让请求者和执行者解耦是它需要首先解决的,解耦的要求就是把请求的内容封装为一个一个的命令,由接收者执行。由于封装成了命令,就同时可以对命令进行多种处理,例如撤销、记录等。

● 角色功能不同

在我们的例子中,策略模式中的抽象算法和具体算法与命令模式的接收者非常相似,但是它们的职责不同。策略模式中的具体算法是负责一个完整算法逻辑,它是不可再拆分的原子业务单元,一旦变更就是对算法整体的变更。

而命令模式则不同,它关注命令的实现,也就是功能的实现。例如我们在分支中也提到接收者的变更问题,它只影响到命令族的变更,对请求者没有任何影响,从这方面来说,接收者对命令负责,而与请求者无关。命令模式中的接收者只要符合六大设计原则,完全不用关心它是否完成了一个具体逻辑,它的影响范围也仅仅是抽象命令和具体命令,对它的修改不会扩散到模式外的模块。

当然,如果在命令模式中需要指定接收者,则需要考虑接收者的变化和封装,例如一个老顾客每次吃饭都点同一个厨师的饭菜,那就必须考虑接收者的抽象化问题。

● 使用场景不同

策略模式适用于算法要求变换的场景,而命令模式适用于解耦两个有紧耦合关系的对象场合或者多命令多撤销的场景。

?3、模型计算是桥接吗

是的,桥接+模板方法

赞(0) 打赏
未经允许不得转载:IDEA激活码 » 设计模式总结(下)

一个分享Java & Python知识的社区