设计模式汇总

2019-04-16  本文已影响0人  wildma

一、设计模式概括

设计模式是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。它可以提高代码的可重用性,增强系统的可维护性,以及解决一系列的复杂问题。一名软件工程师搭建的框架是否健壮、是否易维护,很大程度取决于你是否运用了设计模式。是否懂得将设计模式运用到实际项目中也是区分初级工程师与高级工程师的其中一个重要因素。

二、设计模式六大原则

1. 单一职责原则

2. 里氏替换原则

3. 依赖倒置原则

4. 接口隔离原则

5. 迪米特法则

6. 开闭原则

三、设计模式分类

由 GoF 合著的书籍《设计模式 - 可复用的面向对象软件元素》中一共提到了 23 种设计模式,这些模式可以分为三大类:

1. 单例模式

定义

确保一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。

也就是一个类在整个系统只能有一个实例,这样做的好处是防止创建多个重复对象消耗内存,而且操作 IO 和数据库等都是比较耗资源的,单例可以较少性能的开销。

简单示例
public class Singleton {
    private static Singleton instance = new Singleton;
    private Singleton () {
        
    }
    public static Singleton getInstance() {
        return instance;
    }
}

上面就是一个单例模式的简单使用示例,可以看到确实是自行实例化的,并且使用的是私有构造函数,目的是为了其他地方不能通过构造函数来创建该类的实例,只能通过公共的 getInstance() 方法获取该类的实例。

单例模式的多种实现方式

实现单例模式有多种方式,具体如下:

1. 饿汉式(线程安全)
public class Singleton {
    private static Singleton instance = new Singleton();

    private Singleton() {
    }

    public static Singleton getInstance() {
        return instance;
    }
}

小结:在类加载的时候就初始化实例,虽然避免了多线程并发操作导致的线程安全问题,但是会造成内存的浪费,因为还没有使用这个对象就把它加载到内存中了。

2. 懒汉式(线程不安全)
public class Singleton {
    private static Singleton instance;

    private Singleton() {
    }

    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

小结:只有第一次用到的时候才初始化实例,解决了饿汉式造成内存浪费的问题,但是多线程的时候是不安全的。例如有线程 A 与线程 B 同时执行,这时候就有可能 2 个线程都同时执行到 if (instance == null),这样就创建了 2 个实例了。所以这种方式只适用于单线程。

注:详细的多线程并发请见我的另一篇文章-->带你通俗易懂的理解——线程、多线程与线程池

3. 懒汉式(线程安全)
public class Singleton {
    private static Singleton instance;

    private Singleton() {
    }

    public static synchronized Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

小结:在 getInstance 方法上加了同步锁,这样有多个线程的时候会等前面的线程执行完了再执行当前线程,可以解决第二种懒汉式的线程安全问题。

4. 双重校验锁(线程安全)
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;
    }
}

小结:可以看到加了双重 if (instance == null) 的判断,并且同步锁放到了方法内部,新增的第一个判空可以避免每个线程过来都去执行同步锁,可以解决同步锁造成的效率低的问题。

但是,instance = new Singleton(); 并不是一个原子操作,这一行代码可以分成 3 个步骤:

  1. 给 instance 分配内存
  2. 初始化 instance,即执行 new Singleton();
  3. 将 instance 对象指向分配的内存空间

而且由于 JVM 具有指令重排的特性,也就是说无法保证上面的 3 个步骤是按 1>2>3 执行的,有可能是 1>3>2。例如线程 A 执行完第 1 步与第 3 步,而没有执行第 2 步,显然 instance 是不为空的,这个时候线程 B 刚好执行到 if (instance == null) ,发现不为空就直接返回 instance,但是由于一直没有执行到第 2 步,所以 instance 虽然不为空,但是是没有初始化完成的,所以调用一个没有初始化完成的实例肯定是有问题的。

所以代码中使用了 volatile 关键字,因为它可以解决指令重排的问题,但是只能在 JDK 1.5 之后生效。而且使用 volatile 关键字也会影响一些性能问题。

5. 静态内部类(线程安全)
public class Singleton {
    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }

    private Singleton() {
    }

    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}

小结:这种方式在 Singleton 类加载的时候并没有初始化实例,而是第一次调用 getInstance() 的时候才进行初始化,可以达到延迟加载对象的作用。并且 JVM 能确保 INSTANCE 只被实例化一次。也就是这种方式可以实现双重校验锁一样的效果,而且解决了使用 volatile 关键字只能在 JDK 1.5 之后生效和影响性能的问题。但是这种方式用的人还是比较少的。

6. 枚举(线程安全)
public enum Singleton {
    INSTANCE;

    public void testMethod() {
    }
}

小结:可以说使用枚举的方式实现单例是目前最完美的方式,这种方式可以防止序列化与反射造成的创建多个实例的问题,而前面的 5 种方式默认情况是无法解决这个问题的。可能使用枚举的唯一缺点是可读性不高。
关于序列化与反射造成的创建多个实例的问题可以看看这篇文章——>为什么要用枚举实现单例模式(避免反射、序列化问题)

优点
缺点
使用场景
选用哪种方式实现单例模式?

以上 6 种方式各有利弊(线程安全问题、性能问题、代码复杂度与可读性问题),所以需要根据自己的项目选择合适的方式。一般建议使用第三种线程安全的懒汉式(例如系统源码 LocalBroadcastManager 就是使用该种方式),如果涉及到序列化与反射则可以使用第六种枚举的方式。

2. 工厂方法模式

定义

定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。

实现

工厂方法模式有四要素,分别是抽象工厂、具体工厂、产品接口、具体产品。代码如下:

public abstract class Factory {
    public abstract <T extends Product> T createProduct(Class<T> clz);
}
public class ConcreteFactory extends Factory {
    @Override
    public <T extends Product> T createProduct(Class<T> clz) {
        Product product = null;
        String classname = clz.getName();
        try {
            product = (Product) Class.forName(classname).newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return (T) product;
    }
}
public interface Product {
    void method();
}
public class ConcreteProductA implements Product {
    @Override
    public void method() {
        System.out.println("具体产品 A");
    }
}
public class ConcreteProductB implements Product {
    @Override
    public void method() {
        System.out.println("具体产品 B");
    }
}
public class Client {

    public static void main(String[] args) {

        Factory factory = new ConcreteFactory();

        //创建产品 A
        Product productA = factory.createProduct(ConcreteProductA.class);
        productA.method();

        //创建产品 B
        Product productB = factory.createProduct(ConcreteProductB.class);
        productB.method();
    }
}

运行结果:

具体产品 A
具体产品 B

优点

  1. 客户端创建一个对象只需要知道对象的类名即可,不需要关心创建对象的细节。

  2. 新增一个产品(例如产品 C),只需要新增该产品类即可,不需要需改其他任何代码。符合设计模式六大原则中的 “开闭原则”。

使用场景
需要生成复杂对象或者需要搭建可扩展的框架时都可以使用工厂方法模式。但是对于简单对象,例如只需要通过 new 就可以完成创建的对象,无需使用工厂模式。使用工厂模式,就需要引入一个工厂类,对于简单对象反而会增加系统的复杂度。

扩展
如果一个模块只需要一个工厂类,这时候抽象工厂类的存在就没有必要了,去掉抽象工厂类就变成简单工厂模式了,也可以叫静态工厂模式。具体做法是去掉抽象工厂类,将具体工厂类中的方法改成静态方法。具体如下:

public class ConcreteFactory {

    public static <T extends Product> T createProduct(Class<T> clz) {
        Product product = null;
        String classname = clz.getName();
        try {
            product = (Product) Class.forName(classname).newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return (T) product;
    }
}
public class Client {

    public static void main(String[] args) {

        //创建产品 A
        Product productA = ConcreteFactory.createProduct(ConcreteProductA.class);
        productA.method();

        //创建产品 B
        Product productB = ConcreteFactory.createProduct(ConcreteProductB.class);
        productB.method();
    }
}

运行结果:

具体产品 A
具体产品 B

简单工厂模式的优点是使用更简单了,但是缺点也是存在的,即工厂类的扩展变困难了。但是该种模式在实际项目中使用的还是比较多的。

3. 抽象工厂模式

定义

为创建一组相关或相互依赖的对象提供一个接口,而且无须指定它们的具体类。

与工厂方法模式相比

工厂方法模式是创建一个对象,而抽象工厂模式是创建一组对象,而且这一组对象是相关或相互依赖的。

实现

  1. 创建 A 产品家族的接口
public interface IProductA {
    void method();
}
  1. 创建 A 产品家族的实现类,即具体产品 A1 与具体产品 A2
public class ConcreteProductA1 implements IProductA {
    @Override
    public void method() {
        System.out.println("具体产品 A1");
    }
}
public class ConcreteProductA2 implements IProductA {
    @Override
    public void method() {
        System.out.println("具体产品 A2");
    }
}
  1. 创建 B 产品家族的接口
public interface IProductB {
    void method();
}
  1. 创建 B 产品家族的实现类,即具体产品 B1 与具体产品 B2
public class ConcreteProductB1 implements IProductB {
    @Override
    public void method() {
        System.out.println("具体产品 B1");
    }
}
public class ConcreteProductB2 implements IProductB {
    @Override
    public void method() {
        System.out.println("具体产品 B2");
    }
}
  1. 创建一个抽象工厂类用来创建 A 产品家族与 B 产品家族
public abstract class Factory {
    public abstract IProductA createProductA();
    public abstract IProductB createProductB();
}
  1. 创建抽象工厂类的实现类,即具体工厂 1 与具体工厂 2
public class ConcreteFactory1 extends Factory {
    @Override
    public IProductA createProductA() {
        return new ConcreteProductA1();
    }

    @Override
    public IProductB createProductB() {
        return new ConcreteProductB1();
    }
}
public class ConcreteFactory2 extends Factory {
    @Override
    public IProductA createProductA() {
        return new ConcreteProductA2();
    }

    @Override
    public IProductB createProductB() {
        return new ConcreteProductB2();
    }
}
  1. 客户端使用
public class Client {

    public static void main(String[] args) {

        Factory concreteFactory1 = new ConcreteFactory1();
        Factory concreteFactory2 = new ConcreteFactory2();
        IProductA productA1 = concreteFactory1.createProductA();
        IProductA productA2 = concreteFactory2.createProductA();
        IProductB productB1 = concreteFactory1.createProductB();
        IProductB productB2 = concreteFactory2.createProductB();

        productA1.method();
        productA2.method();
        productB1.method();
        productB2.method();
    }
}

运行结果:

具体产品 A1
具体产品 A2
具体产品 B1
具体产品 B2

优点
客户端创建一组对象只需要知道对应的工厂类即可,不需要关心创建对象的细节。

缺点
产品家族的扩展困难,例如增加 C 产品家族,需要修改原有的代码,违反了开闭原则。

使用场景
创建一组相关或相互依赖的对象的时候可以使用抽象工厂模式。例如需要开发一个在 Android 和 IOS 上的应用,因为是同一个类型的应用,所以 UI 和逻辑都是差不多的,只是运行的操作系统不一样而已,这时候可以使用抽象工厂模式处理不同操作系统之间的交互。

4. 模板方法模式

定义

定义一个操作中的算法的框架,而将一些步骤延迟到子类中。使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。

组成部分

实现
这里通过写一个洗衣机洗衣服的程序来讲解模板方法模式。洗衣机洗衣服的步骤一般是洗涤、漂洗、脱水 3 个流程,但是用过洗衣机的都知道,现在的洗衣机都是可以选择洗衣功能的,例如可以选择快洗、标准洗、洗羽绒服、洗羊毛衫等。这里拿标准洗与洗羊毛衫作为例子来讲解,不管是标准洗还是洗羊毛衫,它们的步骤都是一样的,不同的是每个步骤的力度和速度不一样。例如洗羊毛衫旋转的速度需要很慢和小力,因为羊毛衫比较容易损坏,而标准洗则可以快速转动和大力洗,一般用来洗普通衣服。

  1. 创建抽象类,定义洗衣机洗衣服的流程。
public abstract class WashingMachine {

    //1. 洗涤:标准洗与洗羊毛衫力度与速度不一样,由子类去实现。
    abstract void wash();

    //2. 漂洗:标准洗与洗羊毛衫力度与速度不一样,由子类去实现。
    abstract void rinse();

    //3. 脱水:标准洗与洗羊毛衫力度与速度不一样,由子类去实现。
    abstract void dehydrate();

    //模板方法——洗衣服:标准洗与洗羊毛衫的步骤都是一样的,直接在这里实现。
    final void washClothes() {
        wash();
        rinse();
        dehydrate();
    }
}
  1. 创建实现类,即上面例子中的标准洗与洗羊毛衫。
public class StandardWashing extends WashingMachine {

    @Override
    void wash() {
        System.out.println("标准洗-洗涤");
    }

    @Override
    void rinse() {
        System.out.println("标准洗-漂洗");
    }

    @Override
    void dehydrate() {
        System.out.println("标准洗-脱水");
    }
}
public class WashSweater extends WashingMachine {

    @Override
    void wash() {
        System.out.println("洗羊毛衫-洗涤");
    }

    @Override
    void rinse() {
        System.out.println("洗羊毛衫-漂洗");
    }

    @Override
    void dehydrate() {
        System.out.println("洗羊毛衫-脱水");
    }
}
  1. 客户端使用
public class Client {

    public static void main(String[] args) {

        /*标准洗*/
        WashingMachine standardWashing = new StandardWashing();
        standardWashing.washClothes();

        /*洗羊毛衫*/
        WashingMachine washSweater = new WashSweater();
        washSweater.washClothes();
    }
}

运行结果:

    标准洗-洗涤
    标准洗-漂洗
    标准洗-脱水

    洗羊毛衫-洗涤
    洗羊毛衫-漂洗
    洗羊毛衫-脱水

OK,上面的例子就是典型的模板方法模式。如果你是 Android 开发者,你会发现很多开源项目的 BaseActivity 使用的就是模板方法模式,抽象类是 BaseActivity,模板方法是onCreate(),抽象方法是 initLayout() 、initView()、initPresenter(),实现类是继承 BaseActivity 的子类。

优点

缺点
每一个不同的实现都需要一个子类来实现,导致类的个数增加,使得系统更加庞大。

使用场景
多个子类有相同的方法,并且这些方法逻辑相同时可以使用该模式。

5. 建造者模式

定义

将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。

建造者模式中的 4 个角色

实现
这里通过写一个生产宝马汽车的例子来讲解建造者模式。生产宝马汽车需要很多零件,例如发动机、轮胎、变速器等等。而且每一个型号需要的零件都是不同的,例如宝马 X3 需要的发动机型号为 A1,宝马 X5 系需要的发动机型号为 A2。

public class Product {

    private String engine;//发动机
    private String tire;//轮胎
    private String transmission;//变速器

    //展示产品
    public void showProduct() {
        System.out.println(new StringBuffer().append(getEngine()).append(",")
                .append(getTire()).append(",").append(getTransmission()).toString());
    }

    public String getEngine() {
        return engine;
    }

    public void setEngine(String engine) {
        this.engine = engine;
    }

    public String getTire() {
        return tire;
    }

    public void setTire(String tire) {
        this.tire = tire;
    }

    public String getTransmission() {
        return transmission;
    }

    public void setTransmission(String transmission) {
        this.transmission = transmission;
    }
}
public abstract class Builder {

    //装发动机,具体由子类实现
    public abstract void buildEngine();

    //装轮胎,具体由子类实现
    public abstract void buildTire();

    //装变速器,具体由子类实现
    public abstract void buildTransmission();

    //获取产品
    public abstract Product getProduct();
}

宝马 X3:

public class ConcreteBuilderA extends Builder {

    private Product product = new Product();

    @Override
    public void buildEngine() {
        product.setEngine("装A1型号的发动机");
    }

    @Override
    public void buildTire() {
        product.setTire("装A1型号的轮胎");
    }

    @Override
    public void buildTransmission() {
        product.setTransmission("装A1型号的变速器");
    }

    @Override
    public Product getProduct() {
        return product;
    }
}

宝马 X5

public class ConcreteBuilderB extends Builder {

    private Product product = new Product();

    @Override
    public void buildEngine() {
        product.setEngine("装A2型号的发动机");
    }

    @Override
    public void buildTire() {
        product.setTire("装A2型号的轮胎");
    }

    @Override
    public void buildTransmission() {
        product.setTransmission("装A2型号的变速器");
    }

    @Override
    public Product getProduct() {
        return product;
    }
}
public class Director {

    private Builder builder;

    public Director(Builder builder) {
        this.builder = builder;
    }

    public void construct() {
        builder.buildEngine();
        builder.buildTire();
        builder.buildTransmission();
    }
}
public class Client {

    public static void main(String[] args) {

        /*生产宝马 X3*/
        Builder builder = new ConcreteBuilderA();
        Director director = new Director(builder);
        director.construct();
        builder.getProduct().showProduct();

        /*生产宝马 X5*/
        builder = new ConcreteBuilderB();
        director = new Director(builder);
        director.construct();
        builder.getProduct().showProduct();

    }
}

运行结果:

装A1型号的发动机,装A1型号的轮胎,装A1型号的变速器
装A2型号的发动机,装A2型号的轮胎,装A2型号的变速器

即第一个为生产宝马 X3,第二个为生产宝马 X5。

优点

使用场景

6. 代理模式

定义
为其他对象提供一种代理以控制对这个对象的访问。

实现
这里通过买家找代购购买东西的例子来讲解代理模式。

静态代理

静态代理需要在实现阶段就知道被代理的对象。

  1. 创建抽象对象接口,定义一个购买东西的方法。如下:
public interface Subject {

    void buy();
}
  1. 创建具体对象类(小明找代购购买港版 iPhone),实现抽象对象接口。如下:
public class RealSubject implements Subject {

    @Override
    public void buy() {
        System.out.println("购买港版 iPhone");
    }
}
  1. 创建代理对象类,实现抽象对象接口。如下:
public class Proxy implements Subject {

    @Override
    public void buy() {
        //创建被代理的对象
        RealSubject realSubject = new RealSubject();
        //购买东西
        realSubject.buy();
        //邮寄
        post();
    }

    public void post() {
        System.out.println("将买好的东西邮寄给买家");
    }
}
  1. 客户端使用
public class Client {

    public static void main(String[] args) {
        Subject proxy = new Proxy();
        proxy.buy();
    }
}

运行结果:

购买港版 iPhone
将买好的东西邮寄给买家

动态代理

动态代理是在实现阶段不用关心代理谁,而在运行阶段才指定代理哪一个对象。

实现动态代理只需要修改静态代理的代理类与客户端类即可,如下:

  1. 动态代理类
public class DynamicProxy implements InvocationHandler {

    private Object mObject;

    /**
     * 生成动态代理对象
     *
     * @param object 被代理对象
     */
    public Object newProxyInstance(Object object) {

        this.mObject = object;
        //获得 ClassLoader
        ClassLoader classLoader = object.getClass().getClassLoader();
        //获得接口数组
        Class<?>[] interfaces = object.getClass().getInterfaces();
        //生成动态代理对象
        return Proxy.newProxyInstance(object.getClass().getClassLoader(),
                object.getClass().getInterfaces(), this);
    }

    /**
     * InvocationHandler 接口需要重写的方法(这里会调用被代理的方法)
     *
     * @param proxy  动态代理对象
     * @param method 被代理对象的方法
     * @param args   指定被调用方法的参数
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //通过Java反射机制调用被代理对象的方法
        Object result = method.invoke(this.mObject, args);
        //邮寄
        post();
        return result;
    }

    public void post() {
        System.out.println("将买好的东西邮寄给买家");
    }
}
  1. 客户端使用
public class Client {

    public static void main(String[] args) {
        DynamicProxy DynamicProxy = new DynamicProxy();
        RealSubject realSubject = new RealSubject();
        Subject subject = (Subject) DynamicProxy.newProxyInstance(realSubject);
        //这里会先调用 DynamicProxy 类中的 invoke() 方法,然后再通过该方法中的反射机制来调用被代理对象(RealSubject)的 buy() 方法
        subject.buy();
    }
}

运行结果:

购买港版 iPhone
将买好的东西邮寄给买家

如果这时候小红也需要找这个代购购买一瓶神仙水,则只需要再增加一个具体对象类即可。如下:

创建具体对象类(小红找代购购买神仙水),实现抽象对象接口。

public class RealSubject2 implements Subject {

    @Override
    public void buy() {
        System.out.println("购买神仙水");
    }
}

可以看到这里不需要再修改代理类,即一个代理类可以代理多个对象,这就是动态代理的好处。缺点是需要通过反射机制间接调用被代理类的方法,相比静态代理降低了效率。还有就是要实现动态代理,则被代理类必须实现一个接口,增加了使用的局限性。

优点

使用场景

7. 装饰模式

定义
动态地给一个对象添加一些额外的职责。就增加功能来说,装饰模式相比生成子类更为灵活。

装饰模式中的 4 个角色:

实现
这里通过化妆的例子来讲解装饰模式。例如某大学的迎新晚会有个小品节目需要一个人演老爷爷,但是大学生一般都是很年轻的嘛,直接找个人来表演那肯定不行的,所以需要经过装饰,也就是需要给这个人先化妆。代码实现如下:

public interface Actor {
    /**
     * (演员)出场
     */
    void appearance();
}
public class ActorXiaoMing implements Actor {

    @Override
    public void appearance() {
        System.out.println("出场了");
    }
}
public class Decorator implements Actor {

    private Actor mActor = null;

    //通过构造函数传递被修饰者
    public Decorator(Actor actor) {
        this.mActor = actor;
    }

    //委托给被修饰者执行
    @Override
    public void appearance() {
        mActor.appearance();
    }
}
public class WhiteHairDecorator extends Decorator {

    //定义被修饰者
    public WhiteHairDecorator(Actor actor) {
        super(actor);
    }

    //定义自己的修饰方法(化妆)
    private void makeup() {
        System.out.println("白头发");
    }

    //重写父类的方法
    @Override
    public void appearance() {
        this.makeup();
        super.appearance();
    }
}
public class WhiteBeardDecorator extends Decorator {

    //定义被修饰者
    public WhiteBeardDecorator(Actor actor) {
        super(actor);
    }

    //定义自己的修饰方法(化妆)
    private void makeup() {
        System.out.println("白胡须");
    }

    //重写父类的方法
    @Override
    public void appearance() {
        this.makeup();
        super.appearance();
    }
}
public class Client {

    public static void main(String[] args) {
        //没化妆的小明
        Actor component = new ActorXiaoMing();
        //给小明画了白头发
        component = new WhiteHairDecorator(component);
        //给小明画了白胡须
        component = new WhiteBeardDecorator(component);
        //化完妆,要出场了
        component.appearance();
    }
}

运行结果:

白胡须
白头发
出场了

可以看到运行结果是一个白胡须、白头发的老爷爷出场,但是卸了妆还是一个黑胡须、黑头发的帅气小伙子出场,这就是装饰模式。

优点

缺点
多层的装饰是比较复杂的,会增加系统的复杂度。

使用场景

8. 适配器模式

定义
将一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口不匹配而无法在一起工作的两个类能够在一起工作。

适配器模式中的 3 个角色

实现
这里通过 Type-C 转 3.5 耳机口的例子来讲解适配器模式。目前市面上新手机的充电端口和耳机端口是共用一个 Type-C 端口,而市面上大多数耳机的插头还是圆形插头,需要 3.5 耳机口才能匹配,所以拿这种耳机的插头是不能插入新手机的。为了这种耳机能插入 Type-C 端口的手机,厂家就生产了一种转接线,即 Type-C 转 3.5 耳机口的转接线,这个转接线就是一种适配器。具体如图:

图中,1 为目标角色,2 为适配器,3 为源角色。

适配器模式可分为类适配器和对象适配器两种,所以这里分两种方式实现。

类适配器

类适配器中适配器与适配者之间是继承(或实现)关系。

public interface Headset {

    //耳机插头需要 3.5 耳机口
    void headset_3_5_port();
}
public class MobilePhone {

    //手机的 Type-C 端口
    public void typeCPort() {
        System.out.println("插入 Type-C 端口");
    }
}
class Adapter extends MobilePhone implements Headset {

    //headset_3_5_port() 方法中调用了 typeCPort() 方法,也就实现了将 Type-C 端口转成 3.5 耳机口。
    @Override
    public void headset_3_5_port() {
        this.typeCPort();
    }
}
public class Client {

    public static void main(String[] args) {

        Headset headset = new Adapter();

        //可以看到,表面是耳机插入了 3.5 耳机口
        //但是 headset_3_5_port() 方法内部调用的是 Phone 的 typeCPort() 方法
        //所以通过适配器,耳机实际插入了 Type-C 端口
        headset.headset_3_5_port();
    }
}

运行结果:

插入 Type-C 端口

对象适配器

对象适配器中适配器与适配者之间是关联关系。

public interface Headset {

    //耳机插头需要 3.5 耳机口
    void headset_3_5_port();
}
public class MobilePhone {

    //手机的 Type-C 端口
    public void typeCPort() {
        System.out.println("插入 Type-C 端口");
    }
}
class Adapter implements Headset {

    private MobilePhone mAdaptee;

    //通过构造函数传入需要适配的类
    public Adapter(MobilePhone adaptee) {
        this.mAdaptee = adaptee;
    }

    //headset_3_5_port() 方法中调用了 typeCPort() 方法,也就实现了将 Type-C 端口转成 3.5 耳机口。
    @Override
    public void headset_3_5_port() {
        this.mAdaptee.typeCPort();
    }
}
public class Client {

    public static void main(String[] args) {

        MobilePhone mobilePhone = new MobilePhone();
        Headset headset = new Adapter(mobilePhone);

        //可以看到,表面是耳机插入了 3.5 耳机口
        //但是 headset_3_5_port() 方法内部调用的是 Phone 的 typeCPort() 方法
        //所以通过适配器,耳机实际插入了 Type-C 端口
        headset.headset_3_5_port();
    }
}

运行结果:

插入 Type-C 端口

可以看到,相比类适配器,对象适配器代码中只修改了适配器和客户端这 2 个类,其中适配器改成通过构造函数传入需要适配的类,而不是通过继承。

优点

缺点
过多地使用适配器,会让系统非常零乱,不易整体进行把握。

使用场景
有动机的修改一个正常运行的接口,可以考虑使用适配器模式。例如系统扩展了,需要使用一个已有或新建立的类,但这个类又不符合系统的接口,可以使用适配器模式将这个类变换成符合系统的接口。

9. 观察者模式

定义
定义对象间一种一对多的依赖关系,使得每当一个对象改变状态,则所有依赖于它的对象都会得到通知并被自动更新。

观察者模式中的 4 个角色

实现
这里通过抢购商品的例子来讲解观察者模式。例如淘宝某店铺策划了一个双十一当天零点前 100 名半价的活动,这么优惠的活动我们当然要参与啦!这时候我们就会守在手机前面,等到零点马上开抢。这就是观察者模式,其中某个优惠商品是被观察者,买家是观察者。

自定义观察者模式

public interface Observer {

    //收到消息后的更新操作
    void update();
}
public class Buyer implements Observer {

    public void update() {
        System.out.println("我要开始买啦!");
    }
}
public abstract class Subject {

    //定义一个观察者数组
    private List<Observer> list = new ArrayList<Observer>();

    //增加一个观察者
    public void addObserver(Observer o) {
        this.list.add(o);
    }

    //删除一个观察者
    public void delObserver(Observer o) {
        this.list.remove(o);
    }

    //通知所有观察者
    public void notifyObservers() {
        for (Observer observer : list) {
            observer.update();
        }
    }
}
public class DiscountedGoods extends Subject {

    //活动开始
    public void activityBegin() {
        System.out.println("活动开始啦!");
        super.notifyObservers();//活动开始,通知观察者
    }
}
public class Client {

    public static void main(String[] args) {
        //创建被观察者
        DiscountedGoods discountedGoods = new DiscountedGoods();
        //创建观察者
        Observer observer = new Buyer();
        //观察者观察被观察者
        discountedGoods.addObserver(observer);
        //观察者开始某个业务了
        discountedGoods.activityBegin();
    }
}

运行结果:

活动开始啦!
我要开始买啦!

Java 中自带的观察者模式

其实 Java 语言中已经提供了自带的观察者模式, 分别是 java.util 包下的 Observable 类和 Observer 接口,他们分别对应我们上面例子中的抽象观察者(Observer)与抽象被观察者(Subject)。代码实现如下:

import java.util.Observable;
import java.util.Observer;
public class Buyer implements Observer {

    @Override
    public void update(Observable o, Object arg) {
        System.out.println("我要开始买啦!");
    }
}
import java.util.Observable;
public class DiscountedGoods extends Observable {

    //活动开始
    public void activityBegin() {
        System.out.println("活动开始啦!");
        super.setChanged();//标记被观察者已经改变
        super.notifyObservers();//活动开始,通知观察者
    }
}
import java.util.Observer;
public class Client {

    public static void main(String[] args) {
        //创建被观察者
        DiscountedGoods discountedGoods = new DiscountedGoods();
        //创建观察者
        Observer observer = new Buyer();
        //观察者观察被观察者
        discountedGoods.addObserver(observer);
        //观察者开始某个业务了
        discountedGoods.activityBegin();
    }
}

运行结果:

活动开始啦!
我要开始买啦!

可以看到,使用 Java 中自带的观察者模式实现可以少写 2 个类,分别是抽象观察者(Observer)与抽象被观察者(Subject),但是运行结果还是一样的。

优点

缺点

使用场景

10. 责任链模式

定义
使多个对象都有机会处理请求,从而避免了请求的发送者和接受者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有对象处理它为止。

责任链模式中的 2 个角色

实现
这里通过公司员工请假的例子来讲解责任链模式。例如,某公司的请假制度为:

  1. 请假小于等于 2 天,部门经理可以批准。

  2. 请假小于等于 5 天,总经理可以批准。

  3. 请假小于等于 15 天,董事长可以批准。

  4. 请假大于 15 天,不予批准。

这样 1、2、3 对应的人物就是责任链中的具体处理者。

public abstract class Leader {

    private Leader mLeader;

    /**
     * 设置下一个领导
     */
    public void setNextLeader(Leader leader) {
        this.mLeader = leader;
    }

    public Leader getLeader() {
        return mLeader;
    }

    /**
     * 处理请求的方法
     *
     * @param type 请假类型
     */
    public abstract String handleRequest(int type);
}
public class DepartmentManager extends Leader {

    @Override
    public String handleRequest(int type) {
        String result = "";
        if (LeaveType.DAY_2 == type) { //请假小于等于 2 天,是我来处理
            result = "部门经理批准了你的请假";
        } else { //不属于我处理
            if (getLeader() != null) { //还有其他领导,那就传给下一个处理吧
                result = getLeader().handleRequest(type);
            } else { //没有人能处理
                result = "抱歉,没有人能处理你的请假";
            }
        }
        return result;
    }
}
public class GeneralManager extends Leader {

    @Override
    public String handleRequest(int type) {
        String result = "";
        if (LeaveType.DAY_5 == type) { //请假小于等于 5 天,是我来处理
            result = "总经理批准了你的请假";
        } else { //不属于我处理
            if (getLeader() != null) { //还有其他领导,那就传给下一个处理吧
                result = getLeader().handleRequest(type);
            } else { //没有人能处理
                result = "抱歉,没有人能处理你的请假";
            }
        }
        return result;
    }
}
public class Chairman extends Leader {

    @Override
    public String handleRequest(int type) {
        String result = "";
        if (LeaveType.DAY_15 == type) { //请假小于等于 15 天,是我来处理
            result = "董事长批准了你的请假";
        } else { //不属于我处理
            if (getLeader() != null) { //还有其他领导,那就传给下一个处理吧
                result = getLeader().handleRequest(type);
            } else { //没有人能处理
                result = "抱歉,没有人能处理你的请假";
            }
        }
        return result;
    }
}
public interface LeaveType {

    int DAY_2     = 0; //请假小于等于 2 天
    int DAY_5     = 1; //请假小于等于 5 天
    int DAY_15    = 2; //请假小于等于 15 天
    int DAY_OTHER = 3; //请假大于 15 天
}
public class Client {

    public static void main(String[] args) {

        Leader departmentManager = new DepartmentManager();
        Leader generalManager = new GeneralManager();
        Leader chairman = new Chairman();

        departmentManager.setNextLeader(generalManager);
        generalManager.setNextLeader(chairman);

        /*张三请 2 天假*/
        System.out.println("张三需要请 2 天假");
        System.out.println(departmentManager.handleRequest(LeaveType.DAY_2));

        /*李四请 5 天假*/
        System.out.println("李四需要请 5 天假");
        System.out.println(departmentManager.handleRequest(LeaveType.DAY_5));

        /*王五请 15 天假*/
        System.out.println("王五需要请 15 天假");
        System.out.println(departmentManager.handleRequest(LeaveType.DAY_15));

        /*赵六请 20 天假*/
        System.out.println("赵六需要请 20 天假");
        System.out.println(departmentManager.handleRequest(LeaveType.DAY_OTHER));
    }
}

运行结果:

张三需要请 2 天假
部门经理批准了你的请假
李四需要请 5 天假
总经理批准了你的请假
王五需要请 15 天假
董事长批准了你的请假
赵六需要请 20 天假
抱歉,没有人能处理你的请假

优点

缺点

使用场景
有多个对象可以处理同一个请求,但是又不知道具体谁会处理的时候可以使用责任链模式。

四、设计模式相关 demo

Design Pattern Demo

参考资料

上一篇 下一篇

猜你喜欢

热点阅读