---
主题列表: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 设计模式
- 给出单例模式的 4 种写法
- 除了单例和工厂模式,你还知道哪些设计模式?
- 讲下装饰器模式大概怎样实现?
- 讲下策略模式大概怎样实现?
- 代理模式了解吗?JDK动态代理和 CGLib 动态代理有什么不同,具体是怎么应用?
- 适配器模式了解过吗?说下它和代理模式的异同。
- 怎么实现一个责任链模式?
设计模式是对工作中编码的高层次抽象的总结,最出名的当属 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 存所有需要执行的规则不同在:可以指定节点顺序。
模板方法模式
(全文完)