理解设计模式之美:设计模式是什么?

367 阅读27分钟

一、设计模式是什么?

(一)介绍

设计模式,是一种标准化、规范化的设计方法,是一种编程设计技巧,是对软件设计中普遍存在(反复出现)的各种问题,所提出的解决方案。无论使用哪种开发语言,设计模式都可以对系统设计和开发提供指导意义。

设计模式,最早起源于建筑师克里斯托佛·亚历山大的《建筑模式语⾔》一书,主要描述和组织建筑设计中的模式的方法。

而在软件开发领域,设计模式是GoF四人帮(Gang of Four,分别是:Erich Gamma、Richard Helm、Ralph Johnson和John Vlissides)所提出,他们在1994年出版了经典著作《设计模式:可复用面向对象软件的基础》(Design Patterns - Elements of Reusable Object-Oriented Software)。在书中他们定义了23中经典的设计模式,并给出了每种模式的定义、结构、使用场景、优缺点以及示例代码。

一般来说,设计模式的要素有:

  1. 模式名称(Pattern Name):每个设计模式都有一个独特的名称,用于描述该模式的目的和功能。
  2. 问题(Problem):描述了该模式所解决的具体问题或需求场景。
  3. 解决方案(Solution):描述了该模式的设计思路和解决方案,包括结构、角色、关系等。
  4. 特点(Key Features)/ 效果:描述了该模式的主要特点和优势,以及适用的情况。
  5. 结构图(Structure Diagram):通常使用类图或对象图展示该模式的结构,包括类、接口、关联关系等。
  6. 代码示例(Code Example):给出一个具体的代码示例,展示如何实现和应用该模式。
  7. 相关模式(Related Patterns):介绍与该模式相关联的其他设计模式,以及它们之间的关系和区别。

前四个是设计模式中必须存在的基本要素,这七个要素共同构成了设计模式的基本描述和说明,帮助开发人员理解和应用模式。

(二)为什么需要设计模式?

因为其使代码具有更好的:

  • 代码复用性(相同功能代码无需重复编写)
  • 可读性(代码规范,易读与维护)
  • 可扩展性(可随时增加新需求功能)
  • 可靠性(扩展功能不影响原有功能)
  • 使程序呈现高内聚、低耦合的特征。

设计模式主要研究的是“变”与“不变”,以及如何将它们分离、解耦、组装,将其中“不变”的部分沉淀下来,避免“重复造轮子”,而对于“变”的部分则可以用抽象化、多态化等方式,增强软件的兼容性、可扩展性。

(三)设计模式与算法?

  • 设计模式,是可复用的软件设计问题解决方案,关注软件架构和结构、对象间的关系和沟通。
  • 算法,是解决问题的一系列步骤和规则,关注的是问题的解决方法和执行过程、数据资源的处理和利用。

二者是相辅相成的关系。

对于程序员来说,算法是单兵的作战能力和战术装备,设计模式是战役的战术策略、兵法布阵,二者既是单独行动,又是相辅相成的关系。

算法能使程序员能够不断优化程序效率,这需要长期有意识的训练才行。设计模式是工作后经过大量的实践经验积累才能养成。

它们都是个人能力、编码习惯的体现,要是想要成长,二者是必修课!

(四)模式分类

设计模式,最初根据其解决问题的类型分为3个子类:

  • 创建型模式(对象创建):关注对象的创建机制,是一种实例化对象的方式,解决对象创建过程中的问题。
  • 结构型模式(对象组装):关注对象间的组合和关联方式,以实现更大的结构和功能,强调类和对象间的静态结构、关系、交互行为。
  • 行为型模式(对象交互):关注对象间的交互和职责分配,以实现特定的行为和沟通方式,强调对象之间的动态行为(相互作用、通信和职责分配)。

二、7大设计原则

设计原则是,是一组指导性原则,能够帮助开发者设计出良好的软件结构。常用共有七大设计原则:

  1. 单一职责原则:Single Responsibility Principle,SRP
  2. 开闭原则:Open-Closed Principle,OCP
  3. 里氏替换原则:Liskov Substitution Principle,LSP
  4. 接口隔离原则:Interface Segregation Principle,ISP
  5. 依赖反转原则:Dependency Inversion Principle,DIP
  6. 迪米特法则:Law of Demeter,LoD
  7. 组合/聚合复用原则:Composition/Aggregation Reuse Principle,CARP

而1-5又被合称为SOLID原则,是面向对象编程和面向对象设计的五个基本原则。

(一)单一职责原则

1、原则

单一职责原则,指一个类只负责一项职责,只有一个引起变化的原因。

当一个类A,拥有两个不同职责(职责1、职责2),当职责1需求变更而改变A时,造成了职责2的执行错误。此时应将A的粒度细化,分解为A1、A2.

该原则核心就是解耦和增强内聚性,它能够应用到类、方法上。

  • 类:在实体内有多种不同类型职责的方法存在,按照原则应分成不同的多个实体。
  • 方法:方法内通过if判断等方式,进行不同的逻辑业务,按照原则应分成不同的多个方法。

该原则降低了变更引起的风险,提高了代码的可读性、可维护性和可测试性。

在遵守单一职责原则时,需要合理的去对职责/行为进行分解,划分为不同的实体。

2、示例

变更前

图片.png

变更后

图片.png

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
class Employee {
    private String name;
    private String address;
    private String phoneNumber;
    private double salary;

/* 将以下代码行为拆分为不同的类
    public void calculateSalary() {
        // 计算员工的薪水
        // ...
    }

    public void saveEmployee() {
        // 将员工信息保存到数据库
        // ...
    }

    public void selectEmployee() {
        // 查询数据库中的员工信息
    }

    public void printEmployeeReport() {
        // 打印员工报告
        // ...
    }
*/
}

class SalaryCalculator {
    public void calculateSalary(Employee employee) {
        // 计算员工的薪水
        // ...
    }
}

class EmployeeRepository {
    public void saveEmployee(Employee employee) {
        // 将员工信息保存到数据库
        // ...
    }

    public void selectEmployee(Employee employee) {
        // 查询数据库中的员工信息
    }
}

class EmployeeReportPrinter {
    public void printEmployeeReport(Employee employee) {
        // 打印员工报告
        // ...
    }
}

在上述示例中,在原Employee内有calculateSalary()、saveEmployee()、selectEmployee()、printEmployeeReport()三种不同的职责,因此违反了原则

(二)开闭原则

1、原则

开闭原则,指对于某个软件中的实体(类、方法、模块...),对外扩展是开放的,对内修改是封闭的。

详解:当实体发生变化时,在不修改原有代码的情况下,通过抽象化和接口定义,尽可能通过扩展实体的行为来实现变化。

即,提供一个固有的接口,然后将所有可能发生变化的类都实现这个接口,让固定的借口与相关对象进行交互。

它是所有面向对象原则的核心,是编程中最基础、最重要的设计原则。使用设计模式的目的就是遵循开闭原则。

2、示例

图片.png

interface Shape {
    void draw();
}

class Rectangle implements Shape {
    @Override
    public void draw() {
        System.out.println("Drawing a rectangle");
    }
}

class Circle implements Shape {
    @Override
    public void draw() {
        System.out.println("Drawing a circle");
    }
}

class ShapeRenderer {
    public void render(Shape shape) {
        shape.draw();
    }
}

若此时需要新增一个Triangle类,那么只需要新增一个实现类即可,不需要修改ShapeRenderer类。

(三)里氏替换原则

1、原则

里氏替换原则,指 “ 派生类(子类)对象可以在程序中代替其基类(超类)对象。”

如果对于每个类型为T的对象t,都有类型为S的对象s,使得以S定义的所有程序P在所有的对象t都代替s时,程序P的行为没有发生变化,那么类型S是类型T的子类型。

即,代码中任何父类对象出现的地方,都可以使用其子类对象来替换,并且可以保证其代码的逻辑行为和正确性。

换句话说,在使用继承时,不应去修改父类已有的行为,而只应添加自己的特定行为。

该原则是对开闭原则的补充,其目的是为了确保使用继承关系时,子类能完全替换掉父类,且不产生意外的行为。简单来说,就是教我们合理的利用继承。

原则中所说的行为指:函数所要实现的功能,对输入、输出、异常的约定,甚至包括注释中一些特殊说明等。

在实际编程中,还需注意:

  • 子类的方法参数要比父类的方法参数更宽松(即接收范围更大)。
  • 子类的方法返回值要比父类的方法返回值更严格(即返回范围更小)。

2、示例

违反法则:

图片.png

class Rectangle {
    protected int width;
    protected int height;

    public void setWidth(int width) {
        this.width = width;
    }

    public void setHeight(int height) {
        this.height = height;
    }

    public int getArea() {
        return width * height;
    }
}

class Square extends Rectangle {
    @Override
    public void setWidth(int width) {
        super.setWidth(width);
        super.setHeight(width);
    }

    @Override
    public void setHeight(int height) {
        super.setWidth(height);
        super.setHeight(height);
    }
}

在上述示例中,子类在继承父类的方法时直接完全改变了方法的内容,从而破坏了原来的行为。

符合法则:

图片.png

abstract class Shape {
    abstract int getArea();
}

class Rectangle extends Shape {
    protected int width;
    protected int height;

    public void setWidth(int width) {
        this.width = width;
    }

    public void setHeight(int height) {
        this.height = height;
    }

    @Override
    public int getArea() {
        return width * height;
    }
}

class Square extends Shape {
    protected int sideLength;

    public void setSideLength(int sideLength) {
        this.sideLength = sideLength;
    }

    @Override
    public int getArea() {
        return sideLength * sideLength;
    }
}

(四)接口隔离原则

1、原则

接口隔离原则,指明客户(client)不应被迫使用对其而言无用的方法或功能,应拆分非常庞大臃肿的接口成为更小的和更具体的接口,这样客户将会只需要知道他们感兴趣的方法。这种缩小的接口也被称为角色接口。

即,不要让一个接口承担太多职责,当实现类中如果有多余无用的方法时,需要将接口进行拆分。

接口设计要精简单一。

其目的是为了避免臃肿的接口设计和不必要的依赖关系。

需注意:

  • 本原则是建立在单一职责原则之上,必须满足单一职责。
  • 接口隔离设计不能过小,否则变成了“字节码编程”。

2、单一职责原则与接口隔离原则

单一职责原则与接口隔离原则都强调了职责的单一性和模块的内聚性,体现了封装的思想。但二者的关注点不同。

单一职责原则 - ①,接口隔离原则 - ②

  • 从设计角度看:①关注类或模块的设计,更偏向于业务角度;②关注接口的设计,更偏向于架构角度。
  • 从职责划分看:①对实体划分更为精细(可能同功能但不同职责);②注重接口内是相同的功能。

3、示例

违反法则:

图片.png

interface Interface {
    /**
     * A专属方法
     */
    void methodA();

    /**
     * B专属方法
     */
    void methodB();
}

class A implements Interface {
    @Override
    public void methodA() {
        System.out.println("A 实现了 methodA");
    }

    @Override
    public void methodB() {

    }
}

class B implements Interface {
    @Override
    public void methodA() {

    }

    @Override
    public void methodB() {
        System.out.println("B 实现了 methodB");
    }
}

在上述示例中,两个实现类都存在不属于自己的方法,因此违反了原则。

符合法则:

图片.png

interface InterfaceA {
    /**
     * A专属方法
     */
    void methodA();
}

interface InterfaceB {
    /**
     * B专属方法
     */
    void methodB();
}

class A implements InterfaceA {
    @Override
    public void methodA() {
        System.out.println("A 实现了 methodA");
    }
}

class B implements InterfaceB {
    @Override
    public void methodB() {
        System.out.println("B 实现了 methodB");
    }
}

(五)依赖反转原则

1、依赖

在了解原则前,我们需要对“依赖”,以及其分类有所了解。

依赖,实体A use 实体B,即A对B产生了依赖。可以分为:

  • 非依赖:零耦合关系,实体独立于其他实体,与其他实体没有任何关系。
  • 弱依赖:抽象耦合关系,实体通过抽象的接口、方法参数依赖于其他实体,其他实体的修改不会影响当前实体,实体之间耦合度较低。
  • 强依赖:直接耦合关系,实体通过具体实现(方法、成员属性)依赖于其他实体,其他实体的改变会影响当前实体,实体之间耦合度较高。

依赖注入的三种方式:

  • xxxSetter()注入
  • 构造器注入
  • 接口传递注入(即通过接口方法参数注入依赖对象,)

2、原则

依赖反转原则,又称依赖倒置原则,是指一种特定的解耦形式,使得高层次的模块不依赖于低层次的模块的实现细节,依赖关系被颠倒(反转),从而使得低层次模块依赖于高层次模块的需求抽象。

该原则规定:

  1. 高层模块不应该依赖于低层模块,两者都应该依赖于抽象接口。
  2. 抽象接口不应该依赖于具体实现。而具体实现则应该依赖于抽象接口。

简单来说,程序要依赖抽象接口,不依赖与具体实现。

依赖倒置的中心思想是面向接口编程,目的是把高层模块从对低层模块的依赖中解耦出来。

  • 高层模块:指具有较高级别抽象和功能的模块或组件。它们通常实现系统的核心业务逻辑,并提供对外的接口。高层模块更加抽象和稳定,不依赖于具体的实现细节。它们通常是其他低层模块的调用者和上层模块的提供者。
  • 低层模块:指实现系统具体功能的模块或组件。它们通常包含具体的实现逻辑和细节,为高层模块提供支持和服务。低层模块通常是高层模块的实现者,负责执行具体的任务或提供具体的功能。

在传统的依赖关系中,高层模块或类直接依赖于低层模块或类,高层模块依赖于具体的实现细节,这导致高层模块与低层模块之间存在紧耦合关系。当低层模块发生变化时,高层模块也需要相应地修改或调整,这破坏了系统的灵活性和可维护性。

而依赖反转原则,就是要使系统依赖于抽象,以抽象为基础搭建的架构比以细节为基础的架构要稳的多。

为了遵循依赖反转原则,我们可以采取以下设计方法:

  1. 使用接口或抽象类来定义抽象,高层模块只依赖于抽象而不依赖于具体实现。
  2. 通过构造函数、方法参数或依赖注入来传递依赖关系,而不是在高层模块中直接创建依赖对象。
  3. 使用工厂模式、依赖注入容器等机制来管理依赖关系的创建和注入。

3、示例

违反原则

图片.png

class Email{
    public String getInfo(){
        return "电子邮件信息: hello,world";
    }
}
class Phone{
    public void receive(){
        Email email = new Email();
        System.out.println(email.getInfo());
    }
}

在上述示例中,Phone直接依赖于Email,并没有采取依赖抽象的方式,因此违反了原则。

符合原则

图片.png

interface Receiver{
    String getInfo();
}

class Email implements Receiver{
    @Override
    public String getInfo() {
        return "电子邮件信息: hello,world";
    }
}

class WeChat implements Receiver{
    @Override
    public String getInfo() {
        return "微信信息: hello,world";
    }
}

class Phone{
    public void receive(Receiver receiver){
        System.out.println(receiver.getInfo());
    }
}

(六)迪米特法则

1、原则

迪米特法则(得墨忒耳定律),又称最少知识原则(Principle of Least Knowledge),指一个软件实体应与其他实体保持最小的相互关系了。

具体来说:

  1. 每个实体只与直接的朋友进行通讯。即,不直接操纵朋友的属性,而是通过其方法。
  2. 每个实体对于其他实体,保持最少的了解。

解释:

  1. 直接的朋友:指两个对象之间有耦合关系,泛指以下3种对象,

    • 当前对象本身:可访问自身的属性和方法。
    • 当前对象所拥有的成员对象:可访问自身持有的成员属性对象的属性和方法。
    • 当前对象中被用为参数形式传递的对象:作为参数调用其属性和方法。

    需注意,对象A的某方法的参数对象B内有一成员对象C,A与C是间接关系,非直接关系。

  2. 最少的了解:无论一个类的逻辑多么复杂,只对外提供公共方法,不对外泄漏任何信息。

其目的是为了降低类之间的耦合。

2、示例

图片.png

违反原则:

interface House {
    void rent();
}

class HouseImpl implements House {
    @Override
    public void rent() {
        System.out.println("租房");
    }
}

class Intermediary {
    private House house;

    public Intermediary() {
        this.house = new HouseImpl();
    }

    public void findHouse() {
        house.rent();
    }
}

class Customer {
    private Intermediary intermediary;

    public Customer() {
        this.intermediary = new Intermediary();
    }

    public void findHouse() {
        intermediary.findHouse();
    }
}

在上述示例中,Intermediary直接与HouseImpl,Customer直接与Intermediary进行交互。它们都知道了与其交互对象的内部细节,并直接调用方法,对其依赖太多,增加了耦合性。

符合原则:

图片.png

interface House {
    void rent();
}

class HouseImpl implements House {
    @Override
    public void rent() {
        System.out.println("租房");
    }
}

class Intermediary {
    private House house;

    public Intermediary(House house) {
        this.house = house;
    }

    public void findHouse() {
        house.rent();
    }
}

class Customer {
    private Intermediary intermediary;

    public Customer(Intermediary intermediary) {
        this.intermediary = intermediary;
    }

    public void findHouse() {
        intermediary.findHouse();
    }
}

图片.png

interface House {
    void rent();
}

class HouseImpl implements House {
    @Override
    public void rent() {
        System.out.println("租房");
    }
}

class Intermediary {
    public void findHouse(House house) {
        house.rent();
    }
}

class Customer {
    public void findHouse(Intermediary intermediary) {
        HouseImpl house = new HouseImpl();
        intermediary.findHouse(house);
    }
}

(七)组合/聚合复用原则

1、原则

组合/聚合复用原则,指尽量使用组合/聚合关系复用代码,不使用继承关系复用代码。它关注的是对象之间的关联方式:组合,聚合,是一种整体与部分的关系。

组合:

  • 是一种强关联关系。

  • 表示一个对象包含其他对象,并且这些对象的生命周期是一致的。

  • 整体与部分关系:

    • 整体对象拥有部分对象,整体对象控制着部分对象的创建、生命周期和销毁。
    • 整体对象负责管理和维护部分对象,部分对象没有独立存在的意义。

聚合:

  • 是一种弱关联关系。

  • 表示一个对象包含其他对象,但这些对象的生命周期可以独立存在。

  • 整体与部分关系:

    • 整体对象持有对部分对象的引用,但部分对象具有一定的独立性,可以被多个整体对象共享。

二者的区别在于对象之间的生命周期和依赖性。

2、示例

组合示例:

在以下示例中,Car​类属于主体类,控制着Engine​和Wheel​类,它们的生命周期是一致的。当Car​被创建时,Engine​和Wheel​也会被创建,它们是不可分割的。

class Engine {
    public void start() {
        System.out.println("引擎启动");
    }

    public void stop() {
        System.out.println("引擎停止");
    }
}

class Wheel {
    public void turn() {
        System.out.println("轮子转动");
    }

    public void stop() {
        System.out.println("轮子停止");
    }
}

class Car {
    private Engine engine;
    private Wheel[] wheel;

    public Car() {
        this.engine = new Engine();
        this.wheel = new Wheel[4];
        for (int i = 0; i < 4; i++) {
            this.wheel[i] = new Wheel();
        }
    }

    public void start() {
        engine.start();
        for (int i = 0; i < 4; i++) {
            wheel[i].turn();
        }
    }

    public void stop() {
        engine.stop();
        for (int i = 0; i < 4; i++) {
            wheel[i].stop();
        }
    }
}

聚合示例:

在以下示例中,ClassRoom​和Park​类都持有一个List​ 类型的成员变量 studentList​,表示实体内的学生数量。但Student​对象可以单独存在,被其他的类所使用。

class Student {
    public void study() {
        System.out.println("学生学习");
    }

    public void play() {
        System.out.println("学生玩耍");
    }

    public void goHome() {
        System.out.println("学生回家");
    }
}

class ClassRoom {
    private List<Student> studentList;

    public ClassRoom(List<Student> studentList) {
        this.studentList = studentList;
    }

    public void addStudent(Student student) {
        studentList.add(student);
    }

    public void study() {
        for (Student student : studentList) {
            student.study();
        }
    }
}

class Park{
    private List<Student> studentList;

    public Park(List<Student> studentList) {
        this.studentList = studentList;
    }

    public void addTourist(Student student) {
        studentList.add(student);
    }

    public void play() {
        for (Student student : studentList) {
            student.play();
        }
    }
}

(八)原则总结

我们将以上原则简单理解就是:

  1. 开闭原则是总纲,指导对扩展开放、对修改关闭;
  2. 单一职责原则,指导实现类要职责单一;
  3. 里氏替换原则,指导不要破坏继承体系;
  4. 依赖反转原则,指导面向接口编程;
  5. 接口隔离原则,指导设计接口要精简单一;
  6. 迪米特原则, 指导要降低类之间的耦合性;
  7. 组合/聚合复用原则,指导代码复用的最佳方式。

这七个是公认的设计模式中至少需要包含的设计原则(包含越多越好),它们指导开发者设计一个好的设计模式。

设计模式本身就是一套方法论,一种高内聚、低耦合的设计思想。学习它必须深入到某一特定场景领域,将业务和领域模型结合,在实践中去探索,这样才能体会到设计模式思想的精髓。

三、经典设计模式

在《设计模式:可复用面向对象软件的基础》(Design Patterns - Elements of Reusable Object-Oriented Software)一书中,共收录了23中设计模式,它们也被后世称为经典设计模式。

按照解决的问题类型分为了创建型模式、结构型模式、行为型模式三个子分类。

以下设计模式名称都省略了“模式”二字。

创建型模式(7种)

名称英文描述
简单工厂Simple Factory一个类中存在单独实例化某个类的操作,此类即为简单工厂。由简单工厂类来决定应该用哪个具体子类实例化。
工厂方法Factory Method pattern定义一个接口用于创建对象,但由子类决定初始化哪个类。
抽象工厂Abstract Factory为一个产品族(对象家族)提供了统一的创建接口。即,通过抽象工厂接口决定使用哪个具体工厂实现类,然后再用工厂实现类创建一组相关的对象。
建造者Builder Pattern将一个复杂对象的构建与其表示分离,使得同样的构建过程可以创建不同的表示。
原型Prototype pattern用原型实例指定创建对象的种类,并且通过复制这个原型来创建新对象。
单例Singleton pattern确保一个类​只有​一个实例,并提供对该实例的全局访问点。

简单工厂模式并不属于GoF 23个经典设计模式,但通常将它作为学习其他工厂模式的基础,它的设计思想很简单。

结构型模式(7种)

名称英文描述
适配器Adapter pattern将某个类的接口转换成期望的另一个接口,消除因接口不匹配所造成的类兼容性问题。
桥接Bridge pattern将抽象与实现进行解耦,以便两者可以独立的变化。
组合Composite pattern把多个对象组成树状结构,来表示“整体/局部”层次结构,因此可以用一致的方式来处理单个对象和对象组合。
装饰Decorator pattern向某个对象动态地添加更多的责任(功能)。
外观Facade pattern定义一个高层接口,用来访问子系统的一组接口,使得子系统更加方便使用。
享元Flyweight pattern通过共享以便有效的支持大量细粒度的对象。
代理Proxy pattern为其他对象提供一个代理以控制此对象的访问。

行为型模式(11种)

名称英文描述
责任链Chain-of-responsibility pattern为请求创建了一个接受者对象链,每个对象依序检查此请求,并且对它进行处理或传递给链中的下一个对象。解决了请求的发送者和接受者之间的耦合。
命令Command pattern发送者将请求封装成一个命令对象,接受者根据命令去执行相对应的动作。发送者和接受者通过命令对象进行通信,不直接进行交互,实现了二者之间的解耦。 命令模式还支持保存、撤销、重做命令的操作。
解释器Interpreter pattern给定一个语言, 定义它的文法的一种表示,并定义一个解释器, 该解释器使用该表示来解释语言中的句子。
迭代器Iterator pattern提供一种方法顺序访问一个聚合对象中各个元素, 而又不需暴露该对象的内部表示。
中介者Mediator pattern包装了一系列对象相互作用的方式,使得这些对象不必相互明显作用,从而使它们可以松散偶合。当某些对象之间的作用发生改变时,不会立即影响其他的一些对象之间的作用,保证这些作用可以彼此独立的变化。
备忘录Memento pattern备忘录对象是一个用来存储另外一个对象内部状态的快照的对象。备忘录模式的用意是在不破坏封装的条件下,将一个对象的状态捉住,并外部化,存储起来,从而可以在将来合适的时候把这个对象还原到存储起来的状态。
观察者Observer pattern定义对象之间一对多的依赖关系,当一个对象发生改变,其相关依赖对象会收到通知并被自动更新。
状态State pattern对有状态的对象,把复杂的 “判断逻辑” 提取到不同的状态对象中,允许状态对象在其内部状态发生改变时改变其行为。即,系统状态变化时,选定不同的状态行为。
策略Strategy pattern定义算法族,并且分别进行封装,每个算法之间可以互相替换,使得算法的变化不影响使用的客户。
模版方法Template method pattern定义一个可操纵的算法骨架,将步骤延迟到子类中,子类在不改变算法结构情况下,可重新定义该算法的特定步骤。
访问者Visitor在不改变数据结构情况下,扩展结构中元素的新操作。为数据结构中的每个元素提供多种访问方式,是一种将算法与对象结构分离的设计模式。

四、新设计模式

软件设计模式自1994年诞生至今,已经过去29年时间,在众多程序员的努力下,设计模式如雨后春笋一样多样化现身。

Pattern Languages of Programs - PLoP 大会是由The Hillside Group赞助的年度会议,是模式作者和用户收集、讨论和了解更多有关模式的首要会议。 PLoP 的目的是促进主要关于软件的模式、模式语言、技术和模式体验的发展。

若有兴趣,可进入PLoP官网目录(设计模式目录)查看更多设计模式。

以下是近些年发展较为欢迎的新设计模式(取自Wiki - 软件设计模式

创建型模式

名称英文描述
依赖注入Dependency Injection类从注入器接受它所需的对象,而不是直接创建对象。
惰性初始化Lazy initialization推迟对象的创建、数据的计算等需要耗费较多资源的操作,只有在第一次访问的时候才执行。通常与工厂方法模式合作。
对象池Object pool pattern通过回收利用对象避免获取和释放资源所需的昂贵成本。
多例Multiton pattern确保一个类只有命名的实例,并提供对这些实例的全局访问。
资源获取即初始化RAII,Resource Acquisition Is Initialization通过绑定到合适对象的生命周期来确保资源被适当地释放。

结构型模式

名称英文描述
委托Delegation pattern一个对象将任务的执行责任委托给另一个对象,委托者本身不直接执行任务,而是通过调用委托对象来完成任务。
扩展对象Extension object pattern运行时,向现有对象添加额外的功能或行为,而无需修改原始对象的结构。
前置控制器Front controller pattern提供了一个集中式的入口点,用于接收和处理来自客户端的请求,并将请求分发给相应的处理程序或控制器进行处理。该模式与 Web 应用程序的设计相关。
标记接口模式Marker interface pattern使用一个空接口(未定义任何方法),给类或对象打上标记,表示它们具有特定的特征或行为。
模块Module pattern定义模块,将全局使用的多个相关元素(例如类、单例、方法)分组为单个概念实体封装进模块,只提供对外接口进行访问。
双生Twin pattern一对相互关联的对象(强关联),使其可以相互协作、补充、提供额外功能。一个对象为主体,另一个对象为镜像或代理。

行为型模式

名称英文描述
黑板Blackboard pattern广义的观察者在系统范围内交流信息,允许多位读者和写者。
流式接口Fluent interface pattern通过方法链式调用,来转发一系列对象方法调用的上下文。
空对象模式Null object pattern定义一个空对象,承担处理null对象的责任(取代NULL对象实例的检查)。即使对象为NULL时,仍可以返回默认值。
雇工/仆人Servant pattern定义一个对象,用于向一组类提供某些功能。无需在每个类中定义该功能。
规格Specification pattern通过条件关系组合成为规则,来处理业务逻辑。每个条件都是一个规则,多个规则串联形成一个组合式的规则。该模式是组合模式的扩展。

并发型模式

名称英文描述
活动对象Active Object将方法执行与驻留在其自己的控制线程中的方法调用分离。 目标是通过使用异步方法调用和处理请求的调度程序来引入并发性。
阻碍Balking仅当对象处于特定状态时才对对象执行操作。
双重检查锁定模式Double-checked locking首先验证锁定条件(第一次检查),只有通过锁定条件验证才真正的进行加锁逻辑并再次验证条件(第二次检查)。有的时候,这一模式被看做是反模式。
异步方法调用(事件驱动异步模式)Event-based asynchronous解决多线程程序中出现的异步模式问题。
守护暂停Guarded suspension一个或多个线程(被称为"请求线程")在某个条件满足之前进入等待状态,而另一个线程(被称为"守护线程")负责在满足条件时通知等待线程继续执行。
连接Join-pattern提供了一种通过消息传递来编写并发、并行和分布式程序的方法。 与使用线程和锁相比,这是一种高级编程模型。
消息传递Messaging design pattern (MDP)允许组件和应用程序之间交换信息(即消息)。
反应器模式Reactor当请求抵达后,服务处理程序使用解多路分配策略,然后同步地派发这些请求至相关的请求处理程序。

参考资料

版权声明:个人学习记录,本博客所有文章均采用 CC-BY-NC-SA 许可协议。转载请注明出处。

若有侵权,请留言联系~

如果您觉得文章对您有帮助,请一件三连。您的鼓励是博主的最大动力!