本文最后更新于 2024-03-27,文章内容可能已经过时。

设计模式

本文主要依据《Head First》设计模式中文第二版,学校《软件体系结构与设计模式》课程ppt,优秀的设计模式网站:https://refactoringguru.cn,以及特定设计模式的CSDN与掘金文章编写。

设计模式的用途主要是对内部代码布局而言的,主要用来降低代码的编写难度,提升可读性,可拓展性和可维护性。

1.定义:

设计模式处理一个特定设计情况下反复出现的设计问题,并且为其提供一个解决方案。

软件设计模式适用于小范围的局部的程序设计。

2.工厂模式:

工厂模式(Factory Pattern)是一种创建型设计模式,用于将对象的创建过程抽象出来,使得在程序中创建对象的过程与使用对象的过程分离。这样可以降低系统的耦合度,提高代码的可维护性和可扩展性。

工厂模式定义了一个接口或抽象类作为对象的创建接口,而具体的对象的创建则由其子类来实现。这样客户端代码只需要依赖于抽象的接口或类,而不需要关心具体的对象是如何创建的。工厂模式包括三种主要形式:简单工厂模式、工厂方法模式和抽象工厂模式。

创建型设计模式是解决对象创建机制的设计模式。

该类设计模式试图根据具体的情况,以适当方式创建对象。

image-20240327144447025

2.1 方法:

将选择与初始化一个合适的类的功能封装在一个专门的类的一个专门的方法中委托.

image-20240103151029854

2.2 优点:

1.清洗客户程序 (Clean Client program) 应用对象使用工厂方法访问合适的类的实例。这样,可以取消一个应用对象处理选取不同的类的选取标准的需要。

2.隐藏初始对象的繁杂的细节

2.3 简单工厂模式:(Simple Factory Pattern):

image-20240103151157401

优缺点:

image-20240103151251902

  • 简单工厂模式并不是一个标准的设计模式,而更像是一种编程习惯。

  • 通过一个工厂类来封装对象的创建过程,客户端只需要通过调用工厂类的静态方法或非静态方法来获取所需的对象。

    public class SimplePizzaFactory {
           public Pizza CreatePizza(String ordertype) {
                  Pizza pizza = null;
                  if (ordertype.equals("cheese")) {
                         pizza = new CheesePizza();
                  } else if (ordertype.equals("greek")) {
                         pizza = new GreekPizza();
                  } else if (ordertype.equals("pepper")) {
                         pizza = new PepperPizza();
                  }
                  return pizza;
           }
    }

2.4 工厂方法模式:(Factory Method Pattern):

image-20240103151322991

  • 定义一个创建对象的接口,但由子类决定要实例化的类是哪一个。

  • 工厂方法使一个类的实例化延迟到其子类。

如果用简单工厂模式的的话,我们要去修改工厂代码,并且会增加一堆的if else语句。而工厂方法模式克服了简单工厂要修改代码的缺点.

abstract Pizza createPizza();
public class LDOrderPizza extends OrderPizza {
       Pizza createPizza(String ordertype) {
              Pizza pizza = null;
              if (ordertype.equals("cheese")) {
                     pizza = new LDCheesePizza();
              } else if (ordertype.equals("pepper")) {
                     pizza = new LDPepperPizza();
              }
              return pizza;
       }
}
public class NYOrderPizza extends OrderPizza {
 
    Pizza createPizza(String ordertype) {
        Pizza pizza = null;
 
        if (ordertype.equals("cheese")) {
            pizza = new NYCheesePizza();
        } else if (ordertype.equals("pepper")) {
            pizza = new NYPepperPizza();
        }
        return pizza;
 
    }
 
}

2.4.1 使用时机:

当一个类包含大量的有关创建对象的条件语句;

创建对象的任务分散在客户类的子类中,比较乱;可使用工厂方法模式将创建产品类的子类对象的责任分离出来.

当你想要局部化类的创建的知识的时候.

2.5 抽象工厂模式:(Abstract Factory Pattern):

image-20240103151729164

抽象工厂Creator是一个类,它提供一个接口来生成一系列对象。

  • 提供一个接口,用于创建相关或依赖对象的家族,而不需要明确指定具体类。

  • 抽象工厂模式通常涉及多个产品的创建,而工厂方法模式通常涉及单一产品的创建。

定义了一个接口用于创建相关或有依赖关系的对象族,而无需明确指定具体类。

public interface AbsFactory {
       Pizza CreatePizza(String ordertype) ;
}
public class LDFactory implements AbsFactory {
       @Override
       public Pizza CreatePizza(String ordertype) {
              Pizza pizza = null;
              if ("cheese".equals(ordertype)) {
                     pizza = new LDCheesePizza();
              } else if ("pepper".equals(ordertype)) {
                     pizza = new LDPepperPizza();
              }
              return pizza;
       }
}

3.适配器模式:

适配器模式是一种结构型设计模式, 它能使接口不兼容的对象能够相互合作。

适配器可以理解为一种特殊的装饰者。

image-20240327154436442

适配器实现了其中一个对象的接口, 并对另一个对象进行封装。 所有流行的编程语言都可以实现适配器。

image-20240103152124244

Adapter 实现Target接口类 Adapter 继承Adaptee类.

image-20240327154408197

3.1 使用场景:

你想使用一个已经存在的类,但是该类的接口不是你想要的。

你想创建一个可复用的类,该类与一些互不相关的接口不相容的类进行合作或者你要改变许多子类的接口。这种情况下,你可以使用对象适配器模式。

4.桥接模式:

桥接模式是软件设计模式中最复杂的模式之一,它把事物对象和其具体行为、具体特征分离开来,使它们可以各自独立的变化。

桥接(Bridge)模式包含以下主要角色:

抽象化(Abstraction)角色 :定义抽象类,并包含一个对实现化对象的引用。 扩展抽象化(Refined Abstraction)角色 :是抽象化角色的子类,实现父类中的业务方法,并通过组合关系调用实现化角色中的业务方法。 实现化(Implementor)角色 :定义实现化角色的接口,供扩展抽象化角色调用。 具体实现化(Concrete Implementor)角色 :给出实现化角色接口的具体实现 image-20240103153121392

5.策略模式:

策略模式是一种行为型模式,它将对象和行为分开,将行为定义为 一个行为接口具体行为的实现。策略模式最大的特点是行为的变化,行为之间可以相互替换。每个if判断都可以理解为就是一个策略。本模式使得算法可独立于使用它的用户而变化

策略模式包含如下角色:

Strategy: 抽象策略类:策略是一个接口,该接口定义若干个算法标识,即定义了若干个抽象方法(如下图的algorithm())

Context: 环境类 /上下文类:

上下文是依赖于接口的类(是面向策略设计的类,如下图Context类),即上下文包含用策略(接口)声明的变量(如下图的strategy成员变量)。 上下文提供一个方法(如下图Context类中的的lookAlgorithm()方法),持有一个策略类的引用,最终给客户端调用。该方法委托策略变量调用具体策略所实现的策略接口中的方法(实现接口的类重写策略(接口)中的方法,来完成具体功能) ConcreteStrategy: 具体策略类:具体策略是实现策略接口的类(如下图的ConcreteStrategyA类和ConcreteStrategyB类)。具体策略实现策略接口所定义的抽象方法,即给出算法标识的具体方法。(说白了就是重写策略类的方法!) image-20240103153430587

6.状态模式:

状态模式是一种行为设计模式, 让你能在一个对象的内部状态变化时改变其行为, 使其看上去就像改变了自身所属的类一样。

其主要思想是程序在任意时刻仅可处于几种有限状态中。 在任何一个特定状态中, 程序的行为都不相同, 且可瞬间从一个状态切换到另一个状态。

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

image-20240103154021561

image-20240327161155070

6.1 优缺点:

优点 封装了转换规则。 枚举可能的状态,在枚举状态之前需要确定状态种类。 将所有与某个状态有关的行为放到一个类中,并且可以方便地增加新的状态,只需要改变对象状态即可改变对象的行为。 允许状态转换逻辑与状态对象合成一体,而不是某一个巨大的条件语句块 可以让多个环境对象共享一个状态对象,从而减少系统中对象的个数。 缺点 状态模式的使用必然会增加系统类和对象的个数。 状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱。 状态模式对"开闭原则"的支持并不太好,对于可以切换状态的状态模式,增加新的状态类需要修改那些负责状态转换的源代码,否则无法切换到新增状态,而且修改某个状态类的行为也需修改对应类的源代码。

6.2 与策略模式区别:

状态模式和策略模式区别 1、策略模式封装了一组行为或者算法,它允许Client在运行时动态的切换;状态模式是帮助一个类在不同的状态下显示不同的行为,依赖于内部的状态;

2、策略模式不持有Context的引用,而是被Context所使用;状态模式的每个状态都持有Context的引用,从而在Context中实现状态的转移;

3、从理论上说,策略模式定义对象应该“怎么做”;状态模式定义了对象“是什么”,“什么时候做”。

状态模式允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它的类.

7.访问者模式:

访问者模式是一种行为型设计模式,它将算法与其所作用的对象分离开来,使得能够在不改变对象结构的前提下,对对象中的元素进行新的操作。该模式的核心思想是,定义一个访问者对象,并将其传递给需要被访问的对象,在对象接受访问者的访问时,会调用访问者对象中的方法,在该方法中实现对象对于访问者的响应操作。 image-20240103154632580

8.观察者模式:

观察者模式(又被称为发布-订阅(Publish/Subscribe)模式,属于行为型模式的一种,它定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态变化时,会通知所有的观察者对象,使他们能够自动更新自己。

结构图与解释如下:

image-20240327142234353

本质上,观察者模式=高层底层依赖于接口+一对多关系。

尽量做到交互的对象之间的松耦合设计。

9.中介者模式:

中介者模式(Mediator Pattern)又称为调节者模式或调停者模式。用一个中介对象封装一系列的对象交互,中介者使各对象不需要显式的相互作用,从而使其耦合松散,而且可以独立地改变它们之间的交互,属于行为型模式。

中介者模式的核心思想是,通过中介者解耦系统各层次对象的直接耦合,层次对象的对外依赖通信统统交由中介者转发。 image-20240103224827163

10.单例模式:

单例模式是一种设计模式,它确保一个类只有一个实例,并提供一个全局访问点。这意味着无论在应用程序的任何地方,请求获取这个类的实例都将获得同一个对象。单例模式的目的是限制某个类的实例化过程,以确保在整个应用程序中只存在一个实例,并提供一个全局访问的方式。

单例模式通常包含以下几个要素:

  1. 私有构造函数(Private Constructor): 单例类通常会将其构造函数定义为私有的,以防止外部直接实例化该类。

  2. 静态实例变量(Static Instance Variable): 单例类内部会维护一个静态实例变量,用于存储类的唯一实例。

  3. 静态方法(Static Method): 通过一个静态方法来获取该类的实例,通常这个方法被命名为 getInstance

    image-20240327144724440

1.1 类型:

饿汉式:即当类初始化的时候就创建实例对象.

懒汉式:当需要这个实例时才创建对象.

1.2 java代码实现:

public class Singleton {
​
    // 1. 私有构造函数
    private Singleton() {
        // 私有构造函数,防止外部直接实例化
    }
​
    // 2. 静态实例变量
    private static Singleton instance;
​
    // 3. 静态方法获取实例
    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

这是懒汉模式的基本代码.

1.3 线程安全问题:

这样的简单懒汉式实现是线程不安全的,如果在多线程环境中同时请求实例,可能会导致多个实例的创建。

1.3.1 加锁:

public class Singleton {

private volatile static Singleton instance;
​
private Singleton() {
    // 私有构造函数
}
​
public static Singleton getInstance() {
    if (instance == null) {
        synchronized (Singleton.class) {
            if (instance == null) {
                instance = new Singleton();
            }
        }
    }
    return instance;
}

synchronized 代码块用于指定哪个对象的锁来保护代码块。多个线程只有在获得了同一个对象的锁时,才能同时执行该代码块。

synchronized (Singleton.class) 是使用 synchronized 关键字创建同步代码块的一种方式。在这个例子中,Singleton.class 被用作锁对象,这样就可以控制多个线程对指定代码块的访问,确保在同一时刻只有一个线程能够执行这个代码块。

1.3.1 单例线程池:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
​
public class ThreadPoolSingleton {
​
    private static volatile ThreadPoolSingleton instance;
    private ExecutorService executorService;
​
    private ThreadPoolSingleton() {
        // 私有构造函数,防止外部直接实例化
        // 在这里创建线程池,可以根据需求配置线程池的参数
        executorService = Executors.newFixedThreadPool(5);
    }
​
    public static ThreadPoolSingleton getInstance() {
        if (instance == null) {
            synchronized (ThreadPoolSingleton.class) {
                if (instance == null) {
                    instance = new ThreadPoolSingleton();
                }
            }
        }
        return instance;
    }
​
    public ExecutorService getExecutorService() {
        return executorService;
    }
​
    // 示例:提交任务到线程池
    public void submitTask(Runnable task) {
        executorService.submit(task);
    }
​
    // 示例:关闭线程池
    public void shutdownThreadPool() {
        executorService.shutdown();
    }
​
    public static void main(String[] args) {
        ThreadPoolSingleton threadPoolSingleton = ThreadPoolSingleton.getInstance();
​
        // 示例:提交任务到线程池
        threadPoolSingleton.submitTask(() -> {
            System.out.println("Executing task 1");
        });
​
        threadPoolSingleton.submitTask(() -> {
            System.out.println("Executing task 2");
        });
​
        // 示例:关闭线程池
        threadPoolSingleton.shutdownThreadPool();
    }
}

volatile 是 Java 中的关键字,主要用于确保多线程环境下变量的可见性,禁止指令重排序。具体来说,volatile 关键字有两个主要作用:

  1. 可见性(Visibility): 当一个变量被声明为 volatile 时,如果一个线程修改了这个变量的值,其他线程能够立即看到这个变量的最新值。这是因为每次读取 volatile 变量时,都会从主内存中重新读取最新值。

  2. 禁止指令重排序(Ordering): volatile 关键字禁止编译器和运行时环境对指令进行重排序,保证指令按照程序中的顺序执行。这样可以避免由于指令重排序导致的程序行为异常。

11.装饰者模式:

如果你需要拓展一个对象的行为,那么可以考虑使用装饰者模式。

装饰模式是一种结构型设计模式, 允许你通过将对象放入包含行为的特殊封装对象中来为原对象绑定新的行为。

为了给对象动态的附加额外的责任,对于拓展功能,除了子类化之外,装饰者模式提供了更加弹性的替代做法。

它具体的使用思路是不新建一个对象去继承原有的对象,而是新建一个含有原对象参数的新的对象,同时实现原来高层和底层之间的接口。

image-20240327143948641

真实世界的装饰者的例子:

image-20240327144146152

12.命令模式:

封装请求为对象。

命令模式是一种行为设计模式, 它可将请求转换为一个包含与请求相关的所有信息的独立对象。 该转换让你能根据不同的请求将方法参数化、 延迟请求执行或将其放入队列中, 且能实现可撤销操作。

image-20240327153826000

应用场景举例:

image-20240327154102121

13.外观模式:

外观模式(Facade Pattern)隐藏系统的复杂性,并向客户端提供了一个客户端可以访问系统的接口。这种类型的设计模式属于结构型模式,它向现有的系统添加一个接口,来隐藏系统的复杂性。

image-20240327155040256

14.模版方法模式:

模板方法模式是一种行为设计模式, 它在超类中定义了一个算法的框架, 允许子类在不修改结构的情况下重写算法的特定步骤。

遇到逻辑非常复杂,而又在部分地方需要改动的情况,可以考虑使用模版方法模式。

image-20240327155341315

14.1 好莱坞原则:

高层去调用低层,而不是低层调用高层。

image-20240327155540110

15.迭代器模式:

迭代器模式是一种行为设计模式, 让你能在不暴露集合底层表现形式 (列表、 栈和树等) 的情况下遍历集合中所有的元素。

当集合背后为复杂的数据结构,且你希望对客户端隐藏其复杂性时(出于使用便利性或安全性的考虑),或者希望能够遍历不同的甚至是无法预知的数据结构,可以使用迭代器模式。

image-20240327155906742

15.1 单一责任原则:

image-20240327160257379

16.组合模式:

组合模式是一种结构型设计模式, 你可以使用它将对象组合成树状结构, 并且能像使用独立对象一样使用它们。

image-20240327160538752

image-20240327160639875

17.代理模式:

代理模式是一种结构型设计模式, 让你能够提供对象的替代品或其占位符。 代理控制着对于原对象的访问, 并允许在将请求提交给对象前后进行一些处理。

理论上,可以视为一种特殊的装饰者模式。

image-20240327161801001

18.更好的与设计模式相处:

在实际工程中自己设计设计模式。

image-20240327162334276

设计成功的一种标志:

image-20240327162445906

18.1 设计模式的分类:

image-20240327162802253

18.2 在设计模式上思考:

image-20240327162958001

image-20240327163020266