设计模式总结

214 阅读12分钟

常见的设计模式可以分为3类,分别是创建型模式,结构型模式,行为型模式。

创建型模式

单例模式

  • 单例模式确保一个类在全局(一个进程中)只有一个实例。
  • 使用场景例如Spring生成的Bean默认是单例的,Java中的线程池、数据库连接池默认是单例的(也要结合业务,不一定始终使用单例),还有日志记录器,系统配置参数等

单例模式的实现有懒汉式和饿汉式

  • 懒汉式可以实现延迟加载,但是线程不安全,可以使用双检加锁确保线程安全
public class Singleton {
    // 延迟加载单例对象
    private static volatile Singleton instance;

    // 将构造函数设为私有,禁止外部创建实例
    private Singleton() {}

    public static Singleton getInstance() {
        // 双检加锁保证线程安全
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}
  • 饿汉式在系统启动时就加载完成,天生线程安全.
public class Singleton {
    // 在类加载时就创建单例对象
    private static Singleton instance = new Singleton();
    
    // 将构造函数设为私有,禁止外部创建实例
    private Singleton() {}
    
    // 提供获取单例对象的方法
    public static Singleton getInstance() {
        return instance;
    }
}

实际业务常用饿汉式,因为使用时不用再去创建对象,速度快,且启动时如果有问题能够马上发现,而懒汉式不行。

单例模式缺点

  • 扩展性差,如果需要多个实例了就无法扩展,如果需要修改实例化逻辑也很麻烦
  • 隐藏了类之间的关系,因为对象的创建过程被隐藏起来了
  • 测试时可能需要手动执行实例的初始化逻辑
  • 单例不支持有参数的构造函数,因为没法调用构造函数,只能在getInstance方法中添加参数
  • 单例模式默认是在进程中唯一,但是业务可能需要它在线程中唯一(使用ThreadLocal),或在集群中唯一(使用分布式锁)

工厂模式

  • 工厂模式负责创建对象,为开发人员提供一种在不直接实例化对象的情况下创建对象的方法,将对象的创建和使用解耦。
  • 应用场景如Spring中的IOC容器底层是BeanFactory或ApplicationContext,实现对象的统一管理;JDBC中的DataSource工厂,MyBatis中的SQLSession工厂。如果需要根据不同条件创建不同对象,需要动态扩展对象的创建过程也可以使用工厂模式。
  • 工厂模式又可分为简单工厂模式,工厂方法模式,抽象工厂模式

简单工厂模式:根据传入的参数类型来参加对应的对象并返回

// 定义产品接口
public interface Product {
    void show();
}

// 具体产品类A
public class ProductA implements Product{
    @Override
    public void show() {
        System.out.println("我是产品A");
    }
}


// 具体产品类B
public class ProductB implements Product{
    @Override
    public void show() {
        System.out.println("我是产品B");
    }
}

// 工厂类
public class SimpleFactory {
    public static Product createProduct(String type) {
        if("A".equals(type)){
            return new ProductA();
        } else if ("B".equals(type)) {
            return new ProductB();
        } else {
          throw new IllegalArgumentException("没有这个类型");
        }
    }

    // 反射方式优化,这样的好处是后续再加产品类不用修改这里的代码
    public static Product createProduct(Class<? extends Product> clz){
        try {
            return clz.getDeclaredConstructor().newInstance();
        } catch (InstantiationException | NoSuchMethodException | IllegalAccessException |
                 InvocationTargetException e) {
            throw new RuntimeException(e);
        }
    }
}

// 客户端
public void test() {
    Product product1 = SimpleFactory.createProduct("A");
    Product product2 = SimpleFactory.createProduct(ProductB.class);
    product1.show();
    product2.show();
}

工厂方法模式:简单工厂模式的缺点是不符合开闭原则,当工厂方法需要经常变动时需要修改比较麻烦。而工厂方法模式对其作了优化,工厂方法模式是指定义一个创建对象的接口,但是具体实现让这个接口的实现类来决定,将类的实例化推迟到了子类中进行。在工厂方法模式中用户只需要关心所需要产品对应的工厂,无需关心创建的细节,也就是说一个产品对应一个工厂。

public interface ProductFactory {
    Product createProduct();
}

public class AFactory implements ProductFactory{
    @Override
    public Product createProduct() {
        return new ProductA();
    }
}

public class BFactory implements ProductFactory{
    @Override
    public Product createProduct() {
        return new ProductB();
    }
}

public void test() {
    AFactory aFactory = new AFactory();
    Product product = aFactory.createProduct();
    product.show();
}

抽象工厂模式:使用工厂方法模式时客户端只需要关心它需要使用哪个工厂即可,如果需要新增一个产品只需要新增对应的工厂即可,完全符合了开闭原则。但是如果有多个产品族,此时会发现类越来越多,不容易维护,此时就可以使用抽象工厂模式。抽象工厂模式是指提供一个创建一系列相关对象的接口,无需指定他们的具体类,强调的是一系列相关的产品对象(属于同一产品族)一起使用创建。

// 产品族抽象工厂,其中包括这个产品族中有哪些产品
public abstract class ProductsFactory {
    public abstract ProductA createProductA();
    public abstract ProductB createProductB();
}

public interface ProductA {
    void showA();
}

public interface ProductB {
    void showB();
}

public class FirstA implements ProductA{
    @Override
    public void showA() {
        System.out.println("我是First产品族的A产品");
    }
}

public class FirstB implements ProductB{
    @Override
    public void showB() {
        System.out.println("我是First产品族的B产品");
    }
}

public class FirstFactory extends ProductsFactory{
    @Override
    public ProductA createProductA() {
        return new FirstA();
    }

    @Override
    public ProductB createProductB() {
        return new FirstB();
    }
}

public class SecondA implements ProductA{
    @Override
    public void showA() {
        System.out.println("我是Second产品族的A产品");
    }
}

public class SecondB implements ProductB{
    @Override
    public void showB() {
        System.out.println("我是Second产品族的B产品");
    }
}

public class SecondFactory extends ProductsFactory{
    @Override
    public ProductA createProductA() {
        return new SecondA();
    }

    @Override
    public ProductB createProductB() {
        return new SecondB();
    }
}

public void test() {
    FirstFactory firstFactory = new FirstFactory();
    SecondFactory secondFactory = new SecondFactory();
    ProductA productFirstA = firstFactory.createProductA();
    ProductB productSecondB = secondFactory.createProductB();
    productFirstA.showA();
    productSecondB.showB();
}

建造者模式

  • 建造者模式通过将一个复杂对象的创建过程分解为多个简单步骤,并将这些步骤封装到一个Builder对象中,从而灵活地创建不同的对象。建造者模式可以使得对象的构建与表示分离,并且相同的构建过程可以构建不同的对象。
  • StringBuilder类就使用了建造者模式,它允许使用者逐步构建一个字符串。
public class User {
    private String name;
    private int age;

    // 私有构造器保证只能由建造者实例化对象
    private User(Builder builder) {
        this.name = builder.name;
        this.age = builder.age;
    }

    public String getName() {
        return name;
    }

    public static class Builder {
        private String name;
        private int age;
        public Builder setName(String name) {
            this.name = name;
            return this;        // 返回this方便链式调用
        }
        public Builder setAge(int age) {
            this.age = age;
            return this;
        }
        public User build() {
            return new User(this);
        }
    }
}

public void test() {
    User user = new User.Builder().setName("张三").setAge(18).build();
    System.out.println(user.getName());
}

原型模式

  • 原型模式是通过复制现有对象来创建新的对象。当对象的创建比较复杂耗时时,使用原型模式来避免重复的初始化过程。
  • 使用场景如Java线程池中的每个线程都是从原型线程中复制出来的,而不是每次创建新的线程。

结构型模式

适配器模式

  • 适配器模式可以将一个类的接口转换成客户端所期望的另一种接口。可以在不修改现有代码的情况下,将不兼容的类组合在一起。
  • 应用场景如SpringMVC中handler需要由handler适配器去执行,Java的IO流也使用了适配器模式,例如字节流和字符流的转换。
// 目标接口
public interface Target {
    void showA();
}

// 适配者
public class Adaptee {
    void showB(){
        System.out.println("showB");
    }
}

// 适配器
public class Adapter implements Target{
    private Adaptee adaptee;

    public Adapter(Adaptee adaptee) {
        this.adaptee = adaptee;
    }

    // 适配目标类与适配者
    @Override
    public void showA() {
        adaptee.showB();
    }
}

public void test() {
    Adaptee adaptee = new Adaptee();
    Adapter adapter = new Adapter(adaptee);
    adapter.showA();
}

装饰器模式

  • 装饰器模式允许在不修改现有对象的情况下,动态地添加新功能,它通过将一个对象包装在另一个对象中来扩展行为,从而提高了代码的灵活性和可重用性。
  • 当需要在不修改现有对象结构的前提下增加新的功能时或需要为多个对象添加相同功能时可以使用装饰器模式。
  • 应用场景如Java的IO流中在节点流上加处理流的设计。Spring中的AOP也用到了代理模式和装饰器模式。
public interface Shape {
    void show();
}

public class Circle implements Shape{
    @Override
    public void show() {
        System.out.println("Circle");
    }
}

abstract class AbstractDecorator implements Shape{
    protected Shape decoratedShape;

    public AbstractDecorator(Shape decoratedShape){
        this.decoratedShape = decoratedShape;
    }

    @Override
    public void show() {
        decoratedShape.show();
    }
}

public class ShapeDecorator extends AbstractDecorator{
    public ShapeDecorator(Shape decoratedShape) {
        super(decoratedShape);
    }

    public void show() {
        decoratedShape.show();
        setRedColor(decoratedShape);
    }

    private void setRedColor(Shape decoratedShape) {
        System.out.println("Color is red");
    }
}

public void test() {
    Circle circle = new Circle();
    ShapeDecorator shapeDecorator = new ShapeDecorator(circle);
    shapeDecorator.show();
}

代理模式

  • 代理模式为调用者提供一个代理以控制对目标类的访问。代理是一个具有与目标类相同的接口的对象,它可以拦截调用者对目标类的访问,并决定什么时候调用目标类对象或添加哪些逻辑。
  • 代理类主要目的是通过代理对象控制对原始对象的访问并提供一些额外功能。应用场景如Spring的AOP,远程RPC调用。
  • 代理模式和装饰器模式的区别:装饰器模式是为原有对象增加新功能,而代理模式是去拦截并控制调用者对目标对象的访问。
  • 代理模式分为静态代理和动态代理,静态代理是在编译时就确定了代理类和被代理类之间的关系,需要为每一个被代理类都编写一个对应的代理类。动态代理是在运行时动态生成代理对象,并根据反射机制调用被代理类的方法,动态代理的两种实现方式是JDK动态代理和CGLIB动态代理。
// 账户接口
public interface Account {
    void getMoney();
}

// 账户接口实现类
public class BankAccount implements Account{
    public BankAccount() {}
    @Override
    public void getMoney() {
       System.out.println("取钱成功");
    }
}

// 账户代理类
public class AccountProxy implements InvocationHandler {
    private BankAccount bankAccount;
    public AccountProxy(BankAccount bankAccount) {
        this.bankAccount = bankAccount;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("取钱之前操作");
        method.invoke(bankAccount, args);
        System.out.println("取钱之后操作");
        return null;
    }
}

public void test() {
    // 创建银行账户实例
    BankAccount bankAccount = new BankAccount();
    // 创建账户代理实例
    AccountProxy accountProxy = new AccountProxy(bankAccount);
    // 使用动态代理创建一个代理对象
    Account proxy = (Account) Proxy.newProxyInstance(
            Account.class.getClassLoader(),     // 代理类的类加载器
            new Class<?>[]{Account.class},      // 代理类需要实现的接口
            accountProxy                        // InvocationHandler实现,定义了代理逻辑
    );
    // 使用代理对象进行操作
    proxy.getMoney();
}

行为型模式

策略模式

  • 策略模式定义了一系列算法(策略),调用者可以灵活地决定使用哪个策略,策略之间可以互相替换。
  • 当一个对象具有多种行为或算法,并且需要在运行时动态选择其中一种时,就可以使用策略模式。当需要减少大量的if/else语句时也可使用策略模式。
  • 使用场景如:使用策略模式定义多种支付方式并让用户动态选择使用哪种方式支付,使用策略模式定义多种加密算法等,在电商系统中使用策略模式选择不同地优惠方案。
public interface CountStrategy {
    int count(int num);
}

public class AddStrategy implements CountStrategy {
    @Override
    public int count(int num) {
        return num+1;
    }
}

public class MultiStrategy implements CountStrategy {
    @Override
    public int count(int num) {
        return num*num;
    }
}

public class Counter {
    private int num;
    private CountStrategy countStrategy;
    public Counter(int num, CountStrategy countStrategy) {
        this.num = num;
        this.countStrategy = countStrategy;
    }
    public int count(){
        return countStrategy.count(num);
    }
}

public void test() {
    Counter counter1 = new Counter(10, new AddStrategy());
    System.out.println(counter1.count());
    Counter counter2 = new Counter(10, new MultiStrategy());
    System.out.println(counter2.count());
}

模板方法模式

  • 模板方法模式定义了一个算法的框架,并将一些步骤延迟到子类中实现,它使得子类在不改变算法结构的情况下修改算法的某些细节。很多框架类的设计都采用了模板方法模式,例如Spring中的JDBC Template,其中定义了一套执行SQL的流程,并由子类实现具体的SQL语句。

观察者模式

  • 观察者模式定义了对象之间的一种一对多的依赖关系。在这种模式中,一个对象发生变化时,所有依赖于它的对象都会得到通知并自动更新,即发布订阅模式。被观察者维护一个观察者列表,保存注册了的观察者对象,当被观察者发生变化时,它会遍历观察者列表,调用每个观察者的update方法。
  • 应用场景如事件处理机制,消息通知中间件。

责任链模式

  • 责任链模式允许多个对象来处理请求并将它们连成一条链,当请求到来时它会依次经过链上的对象,直到有一个对象能够处理请求为止。责任链模式主要目的是让多个对象都有机会处理同一个请求,如果可以处理则处理后传给下一个处理器,如果不能处理则往后传。
  • 责任链模式用于需要处理多个对象并且处理流程需要按照顺序的场景,或需要将请求拆分成多个部分分别处理最后再合并的场景。Spring框架的AOP用到了责任链模式,AOP中每个切面可以定义多个切点,并且可以定义切点的顺序,每个切点都可以对方法进行前置或后置处理。限流组件Sentinel也是基于责任链模式的。

状态模式

  • 状态模式是一种行为设计模式,它允许实体在不同的状态间流转并且不同的状态拥有不同的行为,例如电商系统中的订单有未支付状态和已支付状态,当用户支付事件触发订单状态流转,则订单对象的行为也做出相应变化,例如此时订单拥有了发货行为。
  • 在有着多种状态的流转比较复杂的业务场景中,一般会使用状态机,状态机是状态模式的一种实现,它能够明确业务的边界,严格控制状态的流转,使得代码更加稳定与清晰。Spring中提供了状态机的具体实现。
// 抽象状态类
public abstract class State {
    abstract void behavior();
}

public class StateA extends State {
    @Override
    void behavior() {
        System.out.println("具有行为A");
    }
}

public class StateB extends State {
    @Override
    void behavior() {
        System.out.println("具有行为B");
    }
}

public class StateContext {
    private State state;

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

public void test() {
    StateA stateA = new StateA();
    StateB stateB = new StateB();
    StateContext stateContext = new StateContext();
    stateContext.setState(stateA);
    stateContext.behavior();
    stateContext.setState(stateB);
    stateContext.behavior();
}