浅析设计模式

153 阅读19分钟

---

主题列表:juejin, github, smartblue, cyanosis, channing-cyan, fancy, hydrogen, condensed-night-purple, greenwillow, v-green, vue-pro, healer-readable, mk-cute, jzman, geek-black

贡献主题:github.com/xitu/juejin…

theme: juejin highlight:

我们必须时常改进、挑战及增加我们的知识,否则它将不复存在!

文章摘抄自原文 Javadoop 设计模式

  1. 给出单例模式的 4 种写法
  2. 除了单例和工厂模式,你还知道哪些设计模式?
  3. 讲下装饰器模式大概怎样实现?
  4. 讲下策略模式大概怎样实现?
  5. 代理模式了解吗?JDK动态代理和 CGLib 动态代理有什么不同,具体是怎么应用?
  6. 适配器模式了解过吗?说下它和代理模式的异同。
  7. 怎么实现一个责任链模式?

设计模式是对工作中编码的高层次抽象的总结,最出名的当属 Gang of Four(GOF) 的分类了,它们将设计模式分为 23 种。根据用途我们可以将设计模式分为创建型、结构型及行为型模式。

有一些重要的设计原则在开篇和大家分享下

  • 面向接口编程,而不是面向实现
  • 单一职责原则
  • 对修改关闭,对扩展开发。良好的代码不应被人说改就改,而应该是易于扩展的

创建型模式

创建型模式用途就是创建对象。

简单工厂模式

public class FoodFactory {

    public static Food makeFood(String name) {
        if (name.equals("noodle")) {
            Food noodle = new LanZhouNoodle();
            noodle.addSpicy("more");
            return noodle;
        } else if (name.equals("chicken")) {
            Food chicken = new HuangMenChicken();
            chicken.addCondiment("potato");
            return chicken;
        } else {
            return null;
        }
    }
}

简单工厂模式通常是:一个工厂类 xxxFactory,里边有一个静态方法,传入不同的参数,返回不同的派生自同一父类或者实现同一接口的实例对象。实例对象只有一个实现类。

工厂模式

引入工厂模式是因为我们往往需要两个或两个以上的工厂。同一实例接口存在多个不同的实现类,需要引入不同的工厂区分。

public interface FoodFactory {
    Food makeFood(String name);
}
public class ChineseFoodFactory implements FoodFactory {

    @Override
    public Food makeFood(String name) {
        if (name.equals("A")) {
            return new ChineseFoodA();
        } else if (name.equals("B")) {
            return new ChineseFoodB();
        } else {
            return null;
        }
    }
}
public class AmericanFoodFactory implements FoodFactory {

    @Override
    public Food makeFood(String name) {
        if (name.equals("A")) {
            return new AmericanFoodA();
        } else if (name.equals("B")) {
            return new AmericanFoodB();
        } else {
            return null;
        }
    }
}

其中 ChineseFoodA, ChineseFoodB, AmericanFoodA, AmericanFoodB 都派生自 Food。

客户端调用:

public class APP {
    public static void main(String[] args) {
        // 先选择一个具体的工厂
        FoodFactory factory = new ChineseFoodFactory();
        // 由第一步的工厂产生具体的对象,不同的工厂造出不一样的对象
        Food food = factory.makeFood("A");
    }
}

虽然都是调用 makeFood("A") 但是不同工厂生产出来的实例对象完全不一样。调用分两步走:

  • 第一步,我们要选取合适的工厂实现类
  • 第二步,和简单工厂一样传入不同的参数返回不同的实例对象

Javadoop 博主的图片:

抽象工厂模式

当设计到产品族时候,就需要引入抽象工厂模式。

一个经典的案例就是造电脑,我们使用工厂模式看怎么实现。

因为电脑有不同的组件构成,我们将 CPU 和主板进行抽象,分别由不同的工厂类创建对象,然后将 CPU 和主板组装在一起。

这个时候客户端调用是这样的:

// 得到 Intel 的 CPU
CPUFactory cpuFactory = new IntelCPUFactory();
CPU cpu = intelCPUFactory.makeCPU();

// 得到 AMD 的主板
MainBoardFactory mainBoardFactory = new AmdMainBoardFactory();
MainBoard mainBoard = mainBoardFactory.make();

// 组装 CPU 和主板
Computer computer = new Computer(cpu, mainBoard);

这种方式存在一个问题:Intel 家的 CPU 不兼容 AMD 的主板,这样代码就容易出错因为客户端并不知道工厂提供的实例不兼容,会出现随意的组合。抽象工厂就是为了解决这种随意组合造成的不兼容问题。

产品族的概念:代表组成某个产品的一系列附件的集合。

抽象工厂不再定义 CPU 工厂,主板工厂等附件工厂类,而是直接定义成品电脑工厂类。每个电脑工厂生产所有的设备,这肯定不会存在兼容问题。

这时客户端调用是选定一个大厂:

public static void main(String[] args) {
    // 第一步就要选定一个“大厂”
    ComputerFactory cf = new AmdFactory();
    // 从这个大厂造 CPU
    CPU cpu = cf.makeCPU();
    // 从这个大厂造主板
    MainBoard board = cf.makeMainBoard();
      // 从这个大厂造硬盘
      HardDisk hardDisk = cf.makeHardDisk();

    // 将同一个厂子出来的 CPU、主板、硬盘组装在一起
    Computer result = new Computer(cpu, board, hardDisk);
}

抽象工厂的劣势也是显而易见的,比如我们要在抽象类 ComputorFactory 加个显示器方法,就需要修改所有工厂实现类。这违背了对修改关闭,对扩展开发的原则。

单例模式

饿汉单例模式
public class Singleton {
    // 首先,将 new Singleton() 堵死
    private Singleton() {};
    // 创建私有静态实例,意味着这个类第一次使用的时候就会进行创建
    private static Singleton instance = new Singleton();

    public static Singleton getInstance() {
        return instance;
    }
    // 瞎写一个静态方法。这里想说的是,如果我们只是要调用 Singleton.getDate(...),
    // 本来是不想要生成 Singleton 实例的,不过没办法,已经生成了
    public static Date getDate(String mode) {return new Date();}
}

饿汉单例模式的缺点:当类中有除 getInstance 外的其他静态方法时,即便不想要生产单例对象,也会触发单例对象的生成。**一上来就创建单例对象,如果该对象一直没被使用到,会造成内存的浪费。**但在实际开发中,这种写法还是很少的:不需要其实例却把一个或几个会用到的静态方法放到这个类里。

饱汉单例模式
public class Singleton {
    // 首先,也是先堵死 new Singleton() 这条路
    private Singleton() {}
  
    // 和饿汉模式相比,这边不需要先实例化出来
  	// 注意这里的 volatile,它保证可见性以及禁止指令重排避免对象逸出
    private static volatile Singleton instance = null;

    public static Singleton getInstance() {
        if (instance == null) {
            // 加锁
            synchronized (Singleton.class) {
                // 这一次判断也是必须的,不然会有并发问题
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}
嵌套类单例模式

嵌套类最为经典,是在单例工厂类中嵌套一个单例对象持有类 InstanceHolder,通过 JVM 类初始化保证线程安全性。

public class Singleton {
  private static class SingletonHolder {
    public static Singleton INSTANCE = new Singleton();
  }
  public Singleton getInstance() {
    return SingletonHolder.INSTANCE; // 这里将触发 InstanceHolder 类被初始化
  }
}
枚举单例模式
public enum Singleton {
    /**单例对象*/
    INSTANCE;
}
// 使用时直接 Singleton.INSTANCE 就是一个单例对象

建造者模式

经常见到的 xxxBuilder 就是是用的建造者模式,表现是先 new 一个 Builder 然后可以链式的调用方法完善对象属性,最后再 build 得到我们需要的方法。常见的有 Guava Retry 组件的 RetryerBuilder

class User {
    // 下面是“一堆”的属性
    private String name;
    private String password;
    private String nickName;
    private int age;

    // 构造方法私有化,不然客户端就会直接调用构造方法了
    private User(String name, String password, String nickName, int age) {
        this.name = name;
        this.password = password;
        this.nickName = nickName;
        this.age = age;
    }
    // 静态方法,用于生成一个 Builder,这个不一定要有,不过写这个方法是一个很好的习惯,
    // 有些代码要求别人写 new User.UserBuilder().a()...build() 看上去就没那么好
    public static UserBuilder builder() {
        return new UserBuilder();
    }

    public static class UserBuilder {
        // 下面是和 User 一模一样的一堆属性
        private String  name;
        private String password;
        private String nickName;
        private int age;

        private UserBuilder() {
        }

        // 链式调用设置各个属性值,返回 this,即 UserBuilder
        public UserBuilder name(String name) {
            this.name = name;
            return this;
        }

        public UserBuilder password(String password) {
            this.password = password;
            return this;
        }

        public UserBuilder nickName(String nickName) {
            this.nickName = nickName;
            return this;
        }

        public UserBuilder age(int age) {
            this.age = age;
            return this;
        }

        // build() 方法负责将 UserBuilder 中设置好的属性“复制”到 User 中。
        // 当然,可以在 “复制” 之前做点检验
        public User build() {
            if (name == null || password == null) {
                throw new RuntimeException("用户名和密码必填");
            }
            if (age <= 0 || age >= 150) {
                throw new RuntimeException("年龄不合法");
            }
            // 还可以做赋予”默认值“的功能
              if (nickName == null) {
                nickName = name;
            }
            return new User(name, password, nickName, age);
        }
    }
}

核心是:Builder 对象里有所有的属性,build 方法时把所有属性赋值给要返回的对象。可以在 build 方法里进行参数必输校验或者赋默认值。推荐使用 lombok @Builder 注解

客户端调用:

public class APP {
    public static void main(String[] args) {
        User d = User.builder()
                .name("foo")
                .password("pAss12345")
                .age(25)
                .build();
    }
}

原型模式

了解即可。有一个原型实例,基于这个原型实例调用 clone 方法产生新的实例。调用 clone 方法时 Java 要求我们的类必须实现 Cloneable 接口,此接口没有定义任何方法,但如果不实现的话 Java 会报出 CloneNotSupportedException 异常。

protected native Object clone() throws CloneNotSupportedException;

Java 的克隆是浅克隆,如果是引用类型对象,克隆出来的对象和原对象将指向同一对象。通常实现深克隆的方法时将对象序列化然后再反序列化。

结构型模式

结构型模式旨在通过改变代码结构达到解耦的目的,代码更容易维护和扩展。

代理模式

代理模式是对实现类的封装,它不会完成实际的业务逻辑,只是外面套层皮方便对实现类方法做增强。代理类内部一定有一个实现类的实例,通过这个实例调用实际的方法

public interface FoodService {
    Food makeChicken();
    Food makeNoodle();
}

public class FoodServiceImpl implements FoodService {
    public Food makeChicken() {
        return new Chicken();
    }
    public Food makeNoodle() {
        return new Noodle();
    }
}

// 代理要表现得“就像是”真实实现类,所以需要实现 FoodService
public class FoodServiceProxy implements FoodService {

    // 内部一定要有一个真实的实现类,当然也可以通过构造方法注入
    private FoodService foodService = new FoodServiceImpl();

    public Food makeChicken() {
        System.out.println("我们马上要开始制作鸡肉了");
        // 如果我们定义这句为核心代码的话,那么,核心代码是真实实现类做的,
        // 代理只是在核心代码前后做些“无足轻重”的事情
        Food food = foodService.makeChicken();
        System.out.println("鸡肉制作完成啦,加点胡椒粉"); // 增强
        food.addCondiment("pepper");
        return food;
    }
    public Food makeNoodle() {
        System.out.println("准备制作拉面~");
        Food food = foodService.makeNoodle();
        System.out.println("制作完成啦")
        return food;
    }
}

客户端调用:

// 这里用代理类来实例化
FoodService foodService = new FoodServiceProxy();
foodService.makeChicken();

动态代理模式在 Spring AOP 中有两大应用:JDK 动态代理和 CGLib 动态代理。

  • JDK 动态代理是用的前提是被代理类要有接口,因为它需要根据接口使用反射机制动态的创建新类;
  • CGLib 使用的前提是被代理类及方法不能是 final 修饰的,因为 CGLib 是基于 ASM 操作字节码继承被代理类生成子类来实现代理的。

先看 JDK 动态代理:方法的增强类必须实现 InvocationHandler 接口,同时增强类还要组合一个被代理类的实例对象

public class HelloWordImpl implements HelloWorld {

    @Override
    public void sayHello() {
        System.out.println("Hello World!");
    }

    public static void main(String[] args) {
        HelloWorld helloWorld = new HelloWordImpl();
        LoggerHandler loggerHandler = new LoggerHandler(helloWorld);
        HelloWorld proxy = (HelloWorld) Proxy.newProxyInstance(
                Thread.currentThread().getContextClassLoader(),
                helloWorld.getClass().getInterfaces(),
                loggerHandler);
        proxy.sayHello();
    }
}

// 方法的增强必须实现 InvocationHandler
public class LoggerHandler implements InvocationHandler {

  	// target 是真实的被代理对象实例
    private Object target;

    public LoggerHandler(Object target) {
        this.target = target;
    }
		// 通过 invoke 方法指定方法增强逻辑
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Logger.startLog();
        Object result = method.invoke(target, args);
        Logger.endLog();
        return result;
    }
}

再看 CGLib 动态代理:CGLib 要创建一个增强器 Enhancer,增强类实现 MethodInterceptor 增强被代理方法

public class HelloWorld {

    void sayHello() {
        System.out.println("你好,世界!");
    }

    public static void main(String[] args) {
        // 创建一个增强器,用来在运行时生成类
        Enhancer eh = new Enhancer();
        // 设置要增强的目标类
        eh.setSuperclass(HelloWorld.class);
        // 设置 LogInterceptor
        eh.setCallback(new LogInterceptor());
        // 生成代理类
        HelloWorld hw = (HelloWorld) eh.create();
        hw.sayHello();
    }
}
// 增强类实现方法拦截器接口 MethodInterceptor,不需要持有被代理类的对象实例
public class LogInterceptor implements MethodInterceptor {

    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("开始执行方法!");
        Object result = proxy.invokeSuper(obj, args);
        System.out.println("方法执行完毕!");
        return result;
    }
}

适配器模式

说完代理模式接着看下适配器模式,它们代码结构很相似,小节最后会作区分。

适配器模式解决的是:有一个接口需要实现,但现有的对象都不满足,需要中间加一层适配器来转换。

适配器模式总体分三种:默认适配器模式、对象适配器模式、类适配器模式。

默认适配器模式

接口中有很多方法,但我们只想要实现其中某些方法,就可以加入适配器类 Adapter 实现接口给出空方法,子类再继承适配器类 Adapter 重写需要的方法。

我们用 Apache commons-io 包下的 FileAlterationListener 举例:接口定义了很多方法,但我们只想要实现 onFileCreate 方法。

public interface FileAlterationListener {
    void onStart(final FileAlterationObserver observer);
    void onDirectoryCreate(final File directory);
    void onDirectoryChange(final File directory);
    void onDirectoryDelete(final File directory);
    void onFileCreate(final File file);
    void onFileChange(final File file);
    void onFileDelete(final File file);
    void onStop(final FileAlterationObserver observer);
}

我们需要一个适配器实现接口,但所有的方法都是空方法,这样子类就可以继承适配器类重写所需的方法 onFileCreate。

public class FileMonitor extends FileAlterationListenerAdaptor {
    public void onFileCreate(final File file) {
        // 文件创建
        doSomething();
    }
}
对象适配器模式

看一个《Head First 设计模式》中的例子,看在没有鸭实现类的情况下,怎么将鸡适配成鸭。适配器类实现鸭接口,类内部持有一个鸡对象,适配器里的方法是由鸡对象实现

public interface Duck {
    public void quack(); // 鸭的呱呱叫
    public void fly(); // 飞
}

public interface Cock {
    public void gobble(); // 鸡的咕咕叫
    public void fly(); // 飞
}

public class WildCock implements Cock {
    public void gobble() {
        System.out.println("咕咕叫");
    }
    public void fly() {
        System.out.println("鸡也会飞哦");
    }
}
// 毫无疑问,首先,这个适配器肯定需要 implements Duck,这样才能当做鸭来用
public class CockAdapter implements Duck {

    Cock cock;
    // 构造方法中需要一个鸡的实例,此类就是将这只鸡适配成鸭来用
      public CockAdapter(Cock cock) {
        this.cock = cock;
    }

    // 实现鸭的呱呱叫方法
    @Override
      public void quack() {
        // 内部其实是一只鸡的咕咕叫
        cock.gobble();
    }

      @Override
      public void fly() {
        cock.fly();
    }
}

客户端调用:

public static void main(String[] args) {
    // 有一只野鸡
      Cock wildCock = new WildCock();
      // 成功将野鸡适配成鸭
      Duck duck = new CockAdapter(wildCock);
      ...
}
类适配器

适配器通过继承获得了所需的大部分方法,个别方法自己实现即可。

public interface Human {
    void eating();
    void breath();
    void work();
}
public class SupperManY {

    public void eating() {
        System.out.println("超人也吃饭");
    }

    public void breath() {
        System.out.println("超人也要呼吸");
    }
}
public class HumanAdapter extends SupperManY implements Human {

    @Override
    public void work() {
        System.out.println("Amazing Extending... 再见汉得");
    }

    public static void main(String[] args) {
        Human human = new HumanAdapter();
        human.breath();
        human.eating();
        human.work();
    }
}
适配器总结
  • 类适配和对象适配的异同

    类适配采用继承,对象适配采用组合,对象适配需要多一个组合对象。

  • 适配器模式和代理模式异同

    比较这两种模式,其实是比较对象适配器模式和代理模式,两者代码结构相似都需要持有一个实现类对象。但它们的目的不同,代理模式为的是对原方法做增强,对象适配器模式是做适配,适配一个没有实现类的接口实现。

桥梁模式

理解桥梁模式本质就是理解代码抽象和解耦。

首先需要定一个桥梁,它是一个接口定义接口方法。

// 桥梁接口
public interface DrawAPI {
   // 包含实现类所需的所有参数
   public void draw(int radius, int x, int y);
}
// 一系列实现类
public class RedPen implements DrawAPI {
    @Override
    public void draw(int radius, int x, int y) {
        System.out.println("用红色笔画图,radius:" + radius + ", x:" + x + ", y:" + y);
    }
}
public class GreenPen implements DrawAPI {
    @Override
    public void draw(int radius, int x, int y) {
        System.out.println("用绿色笔画图,radius:" + radius + ", x:" + x + ", y:" + y);
    }
}
public class BluePen implements DrawAPI {
    @Override
    public void draw(int radius, int x, int y) {
        System.out.println("用蓝色笔画图,radius:" + radius + ", x:" + x + ", y:" + y);
    }
}

定义一个抽象类,此类的实现都需要使用 DrawAPI

public abstract class Shape {
    protected DrawAPI drawAPI;
    protected Shape(DrawAPI drawAPI) {
        this.drawAPI = drawAPI;
    }
  	// 实现类都有着相同的需求:作画,作画离不开画笔
    public abstract void draw();
}
// 一系列实现类
// 圆形
public class Circle extends Shape {
    private int radius;
    public Circle(int radius, DrawAPI drawAPI) {
        super(drawAPI);
        this.radius = radius;
    }
    public void draw() {
        drawAPI.draw(radius, 0, 0);
    }
}
// 长方形
public class Rectangle extends Shape {
    private int x;
    private int y;
    public Rectangle(int x, int y, DrawAPI drawAPI) {
        super(drawAPI);
        this.x = x;
        this.y = y;
    }
    public void draw() {
        drawAPI.draw(0, x, y);
    }
}

客户端调用:

public static void main(String[] args) {
    Shape greenCircle = new Circle(10, new GreenPen());
    Shape redRectangle = new Rectangle(4, 8, new RedPen());
    greenCircle.draw();
    redRectangle.draw();
}

Javadoop 博主将所有的代码整合到一张图中,感谢。

前面说到,理解桥梁模式的关键就是看它的抽象与解耦;在这里将画笔抽象成一个接口,提供各色的实现类,将用哪种画笔作画从作画这件事情本身解耦出来,易于扩展避免一堆 if else,有点策略模式的意思。

装饰模式

首先看个简单的图了解下代码层次结构,**关键是定义原料和配料两个抽象基类,配料要继承自原料。*ConcreteComponet 是 Compoent 接口的实现类,而 Decorator 抽象类继承自 Component抽象类,它的实现类都可以作为 Componet 来使用,他们和 Component 的区别是,Decorator 是对 Compoent 接口实现类的装饰增强。

代理模式也可以实现对类的增强,为什么还要用装饰模式呢?

代理模式不容易实现多个功能的增强,当然可以代理包含代理的多层包装形式,但那样代码就复杂了;装饰模式提供横向扩展的类增强机制,多增强一个功能只需新建一个装饰类即可,易于扩展。

Javadoop 博主的 “快乐柠檬茶” 例子很贴切,“红茶,绿茶” 都是基类原料,而 “芒果,珍珠” 是配料是对原料的装饰增强。

  • 定义饮料抽象基类及实现类
// 首先定义饮料抽象基类,相当于上图的 Compoent
public abstract class Beverage {
      // 返回描述
      public abstract String getDescription();
      // 返回价格
      public abstract double cost();
}
// 饮料实现类 红茶、绿茶,相当于上图的 ConcreteComponet*
public class BlackTea extends Beverage {
      public String getDescription() {
        return "红茶";
    }
      public double cost() {
        return 10;
    }
}
public class GreenTea extends Beverage {
    public String getDescription() {
        return "绿茶";
    }
      public double cost() {
        return 11;
    }
}
  • 定义配料抽象基类,此类必须继承自 Beverage,并提供配料子类装饰饮料实现类。
// 调料
public abstract class Condiment extends Beverage {

}
public class Lemon extends Condiment {
    private Beverage bevarage;
    // 这里很关键,需要传入具体的饮料,如需要传入没有被装饰的红茶或绿茶,
    // 当然也可以传入已经装饰好的芒果绿茶,这样可以做芒果柠檬绿茶
    public Lemon(Beverage bevarage) {
        this.bevarage = bevarage;
    }
    public String getDescription() {
        // 装饰
        return bevarage.getDescription() + ", 加柠檬";
    }
    public double cost() {
        // 装饰
        return beverage.cost() + 2; // 加柠檬需要 2 元
    }
}

public class Mango extends Condiment {
    private Beverage bevarage;
    public Mango(Beverage bevarage) {
        this.bevarage = bevarage;
    }
    public String getDescription() {
        return bevarage.getDescription() + ", 加芒果";
    }
    public double cost() {
        return beverage.cost() + 3; // 加芒果需要 3 元
    }
}

看下客户端调用:

public static void main(String[] args) {
    // 首先,我们需要一个基础饮料,红茶、绿茶或咖啡
    Beverage beverage = new GreenTea();
    // 开始装饰
    beverage = new Lemon(beverage); // 先加一份柠檬
    beverage = new Mongo(beverage); // 再加一份芒果

    System.out.println(beverage.getDescription() + " 价格:¥" + beverage.cost());
    //"绿茶, 加柠檬, 加芒果 价格:¥16"
  	// 设置可以定制 “芒果-珍珠-双份柠檬-红茶”
	  Beverage another = new Mongo(new Pearl(new Lemon(new Lemon(new BlackTea()))));
}

门面模式

门面模式让客户端不再关注实例化时使用哪个实现类,提供统一的实例化类 Maker,提供不同实现类的实例化方法。

和工厂模式的作用类似都是提供接口不通实现类的对象,但门面模式为每个实现类提供不同的 getInstance 方法。

先定义一个门面:

public class ShapeMaker {
   private Shape circle;
   private Shape rectangle;
   private Shape square;

   public ShapeMaker() {
      circle = new Circle();
      rectangle = new Rectangle();
      square = new Square();
   }

  /**
   * 下面定义一堆方法,具体应该调用什么方法,由这个门面来决定
   */

   public void drawCircle(){
      circle.draw();
   }
   public void drawRectangle(){
      rectangle.draw();
   }
   public void drawSquare(){
      square.draw();
   }
}

客户端调用:

public static void main(String[] args) {
  ShapeMaker shapeMaker = new ShapeMaker();

  // 客户端调用现在更加清晰了
  shapeMaker.drawCircle();
  shapeMaker.drawRectangle();
  shapeMaker.drawSquare();        
}

行为型模式

策略模式

// 定义策略接口
public interface Strategy {
   public void draw(int radius, int x, int y);
}
// 定义具体的策略
public class RedPen implements Strategy {
   @Override
   public void draw(int radius, int x, int y) {
      System.out.println("用红色笔画图,radius:" + radius + ", x:" + x + ", y:" + y);
   }
}
public class GreenPen implements Strategy {
   @Override
   public void draw(int radius, int x, int y) {
      System.out.println("用绿色笔画图,radius:" + radius + ", x:" + x + ", y:" + y);
   }
}
public class BluePen implements Strategy {
   @Override
   public void draw(int radius, int x, int y) {
      System.out.println("用蓝色笔画图,radius:" + radius + ", x:" + x + ", y:" + y);
   }
}

使用策略的类 Context

public class Context {
   private Strategy strategy;

   public Context(Strategy strategy){
      this.strategy = strategy;
   }

   public int executeDraw(int radius, int x, int y){
      return strategy.draw(radius, x, y);
   }
}

客户端代码:

public static void main(String[] args) {
    Context context = new Context(new BluePen()); // 使用绿色笔来画
      context.executeDraw(10, 0, 0);
}

联系下桥梁模式,会发现它们的代码结构很相似。桥梁模式只是在左侧加了一层抽象类。

观察者模式

观察者模式就是定义两个操作:观察者订阅自己关心的主题和主题有数据变化后遍历通知观察者们。

首先定义主题,每个主题需要持有观察者列表的引用,在数据变更时通知各个观察者。

// 定义主题
public class Subject {
  	// 主题持有的观察者列表
    List<AbstractObserver> abstractObservers = new ArrayList<>();
  	// 模拟主题变化状态
    private int state;

    public int getState() {
        return state;
    }

    public void setState(int state) {
        this.state = state;
      	// 主题变化时通知所有的观察者
        this.notifyAllObservers();
    }
		// 注册观察者
    public void register(AbstractObserver abstractObserver) {
        abstractObservers.add(abstractObserver);
    }

    public void notifyAllObservers() {
        for (AbstractObserver abstractObserver : abstractObservers) {
            abstractObserver.update();
        }
    }
}

// 定义观察者,观察者持有一个主题
public abstract class AbstractObserver {
    public AbstractObserver(Subject subject) {
        this.subject = subject;
    }
    protected Subject subject;
  	// 更新动作
    public abstract void update();
}

定义两个观察者实现类,在构造函数中注册观察者到主题。

public class BinaryAbstractObserver extends AbstractObserver {
    public BinaryAbstractObserver(Subject subject) {
        super(subject);
      	// 注册观察者
        this.subject.register(this);
    }
    @Override
    public void update() {
        String result = Integer.toBinaryString(this.subject.getState());
        System.out.println("订阅的主题状态发生了变化,新的数据二进制是 => " + result);
    }
}

public class HexAbstractObserver extends AbstractObserver {
    public HexAbstractObserver(Subject subject) {
        super(subject);
        subject.register(this);
    }
    @Override
    public void update() {
        String result = Integer.toHexString(this.subject.getState());
        System.out.println("订阅的主题发生变化,新的数据16进制是 => " + result);
    }
		// 客户端调用
    public static void main(String[] args) {
        Subject subject = new Subject();
        new BinaryAbstractObserver(subject);
        new HexAbstractObserver(subject);
      	// 模拟状态变化
        subject.setState(10);
    }
}

Spring 的 ApplicationEventPublisher 和 @EventListener 就是基于观察者模式的,类似的还有 Guava EventBus 事件总线。

责任链模式

责任链通常是先建立一个单向链表,调用方只需要调用头部节点,后续会自动流转下去。如流程审批就是很好的应用场景,只要终端用户提交申请,走责任链,会按顺序校验审批。

首先要定义流程节点的抽象基类

public abstract class RuleHandler {
    // 后继节点
    protected RuleHandler successor;

    public abstract void apply(Context context);

    public void setSuccessor(RuleHandler successor) {
        this.successor = successor;
    }

    public RuleHandler getSuccessor() {
        return successor;
    }
}

再定义一系列节点。

校验用户所在地区是否可以参加活动:

public class LocationRuleHandler extends RuleHandler {
    public void apply(Context context) {
        boolean allowed = activityService.isSupportedLocation(context.getLocation);
        if (allowed) {
            if (this.getSuccessor() != null) {
                this.getSuccessor().apply(context);
            }
        } else {
            throw new RuntimeException("非常抱歉,您所在的地区无法参与本次活动");
        }
    }
}

校验奖品是否已被领完:

public class LimitRuleHandler extends RuleHandler {
    public void apply(Context context) {
        int remainedTimes = activityService.queryRemainedTimes(context); // 查询剩余奖品
        if (remainedTimes > 0) {
            if (this.getSuccessor() != null) {
                this.getSuccessor().apply(userInfo);
            }
        } else {
            throw new RuntimeException("您来得太晚了,奖品被领完了");
        }
    }
}

客户端调用:

public static void main(String[] args) {
    RuleHandler locationHandler = new LocationRuleHandler();
    RuleHandler limitHandler = new LimitRuleHandler();
		// 建立单向链表
    locationHandler.setSuccessor(limitHandler);

    locationHandler.apply(context);
}

和用 List 存所有需要执行的规则不同在:可以指定节点顺序。

模板方法模式

(全文完)