单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
1.1 饿汉式
特点:类加载时就初始化,线程安全
// 构造方法私有化
private Singleton() {
}
// 饿汉式创建单例对象
private static Singleton singleton = new Singleton();
public static Singleton getInstance() {
return singleton;
}
1.2 懒汉式
特点:第一次调用才初始化,避免内存浪费。
/*
* 懒汉式创建单例模式 由于懒汉式是非线程安全, 所以加上线程锁保证线程安全
*/
private static Singleton singleton;
public static synchronized Singleton getInstance() {
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}
1.3 双重检验锁(double check lock)(DCL)
特点:安全且在多线程情况下能保持高性能
private volatile static Singleton singleton;
private Singleton (){}
public static Singleton getInstance() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
1.4 静态内部类
特点:效果类似DCL,只适用于静态域
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton (){}
public static final Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
1.5 枚举
特点:自动支持序列化机制,绝对防止多次实例化
public enum Singleton {
INSTANCE;
}
1.6 破坏单例的几种方式与解决方法
1.6.1 反序列化
Singleton singleton = Singleton.getInstance();
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:/test.txt"));
oos.writeObject(singleton);
oos.flush();
oos.close();
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:/test.txt"));
Singleton singleton1 = (Singleton)ois.readObject();
ois.close();
System.out.println(singleton);//com.ruoyi.base.mapper.Singleton@50134894
System.out.println(singleton1);//com.ruoyi.base.mapper.Singleton@5ccd43c2
可以看到反序列化后,两个对象的地址不一样了,那么这就是违背了单例模式的原则了,解决方法只需要在单例类里加上一个readResolve()方法即可,原因就是在反序列化的过程中,会检测readResolve()方法是否存在,如果存在的话就会反射调用readResolve()这个方法。
private Object readResolve() {
return singleton;
}
//com.ruoyi.base.mapper.Singleton@50134894
//com.ruoyi.base.mapper.Singleton@50134894
1.6.2 反射
Singleton singleton = Singleton.getInstance();
Class singletonClass = Singleton.class;
Constructor constructor = singletonClass.getDeclaredConstructor();
constructor.setAccessible(true);
Singleton singleton1 = constructor.newInstance();
System.out.println(singleton);//com.ruoyi.base.mapper.Singleton@32a1bec0
System.out.println(singleton1);//com.ruoyi.base.mapper.Singleton@22927a81
同样可以看到,两个对象的地址不一样,这同样是违背了单例模式的原则,解决办法为使用一个布尔类型的标记变量标记一下即可,代码如下:
private static boolean singletonFlag = false;
private Singleton() {
if (singleton != null || singletonFlag) {
throw new RuntimeException("试图用反射破坏异常");
}
singletonFlag = true;
}
但是这种方法假如使用了反编译,获得了这个标记变量,同样可以破坏单例,代码如下:
Class singletonClass = Singleton.class;
Constructor constructor = singletonClass.getDeclaredConstructor();
constructor.setAccessible(true);
Singleton singleton = constructor.newInstance();
System.out.println(singleton); // com.ruoyi.base.mapper.Singleton@32a1bec0
Field singletonFlag = singletonClass.getDeclaredField("singletonFlag");
singletonFlag.setAccessible(true);
singletonFlag.setBoolean(singleton, false);
Singleton singleton1 = constructor.newInstance();
System.out.println(singleton1); // com.ruoyi.base.mapper.Singleton@5e8c92f4
如果想使单例不被破坏,那么应该使用枚举的方式去实现单例模式,枚举是不可以被反射破坏单例的。
1.7 容器式单例
当程序中的单例对象非常多的时候,则可以使用容器对所有单例对象进行管理,如下:
public class ContainerSingleton {
private ContainerSingleton() {}
private static Map singletonMap = new ConcurrentHashMap<>();
public static Object getInstance(Class clazz) throws Exception {
String className = clazz.getName();
// 当容器中不存在目标对象时则先生成对象再返回该对象
if (!singletonMap.containsKey(className)) {
Object instance = Class.forName(className).newInstance();
singletonMap.put(className, instance);
return instance;
}
// 否则就直接返回容器里的对象
return singletonMap.get(className);
}
public static void main(String[] args) throws Exception {
SafetyDangerLibrary instance1 = (SafetyDangerLibrary)ContainerSingleton.getInstance(SafetyDangerLibrary.class);
SafetyDangerLibrary instance2 = (SafetyDangerLibrary)ContainerSingleton.getInstance(SafetyDangerLibrary.class);
System.out.println(instance1 == instance2); // true
}
}
1.8 ThreadLocal单例
不保证整个应用全局唯一,但保证线程内部全局唯一,以空间换时间,且线程安全。
public class ThreadLocalSingleton {
private ThreadLocalSingleton(){}
private static final ThreadLocal threadLocalInstance = ThreadLocal.withInitial(() -> new ThreadLocalSingleton());
public static ThreadLocalSingleton getInstance(){
return threadLocalInstance.get();
}
public static void main(String[] args) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "-----" + ThreadLocalSingleton.getInstance());
System.out.println(Thread.currentThread().getName() + "-----" + ThreadLocalSingleton.getInstance());
}).start();
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "-----" + ThreadLocalSingleton.getInstance());
System.out.println(Thread.currentThread().getName() + "-----" + ThreadLocalSingleton.getInstance());
}).start();
// Thread-0-----com.ruoyi.library.domain.vo.ThreadLocalSingleton@53ac93b3
// Thread-1-----com.ruoyi.library.domain.vo.ThreadLocalSingleton@7fe11afc
// Thread-0-----com.ruoyi.library.domain.vo.ThreadLocalSingleton@53ac93b3
// Thread-1-----com.ruoyi.library.domain.vo.ThreadLocalSingleton@7fe11afc
}
}
可以看到上面线程0和1他们的对象是不一样的,但是线程内部,他们的对象是一样的,这就是线程内部保证唯一。
1.9 总结
适用场景:
优点:
缺点:
2.1 简单工厂模式
简单工厂模式不是23种设计模式之一,他可以理解为工厂模式的一种简单的特殊实现。
2.1.1 基础版
// 工厂类
public class CoffeeFactory {
public Coffee create(String type) {
if ("americano".equals(type)) {
return new Americano();
}
if ("mocha".equals(type)) {
return new Mocha();
}
if ("cappuccino".equals(type)) {
return new Cappuccino();
}
return null;
}
}
// 产品基类
public interface Coffee {
}
// 产品具体类,实现产品基类接口
public class Cappuccino implements Coffee {
}
基础版是最基本的简单工厂的写法,传一个参数过来,判断是什么类型的产品,就返回对应的产品类型。但是这里有一个问题,就是参数是字符串的形式,这就很容易会写错,比如少写一个字母,或者小写写成了大写,就会无法得到自己想要的产品类了,同时如果新加了产品,还得在工厂类的创建方法中继续加if,于是就有了升级版的写法。
2.1.2 升级版
// 使用反射创建对象
// 加一个static变为静态工厂
public static Coffee create(Class extends Coffee> clazz) throws Exception {
if (clazz != null) {
return clazz.newInstance();
}
return null;
}
升级版就很好的解决基础版的问题,在创建的时候在传参的时候不仅会有代码提示,保证不会写错,同时在新增产品的时候只需要新增产品类即可,也不需要再在工厂类的方法里面新增代码了。
2.1.3 总结
适用场景:
优点:
缺点:
2.2 工厂方法模式
工厂方法模式是指定义一个创建对象的接口,让实现这个接口的类来决定实例化哪个类,工厂方法让类的实例化推迟到子类中进行。
工厂方法模式主要有以下几种角色:
2.2.1 代码实现
// 抽象工厂
public interface CoffeeFactory {
Coffee create();
}
// 具体工厂
public class CappuccinoFactory implements CoffeeFactory {
@Override
public Coffee create() {
return new Cappuccino();
}
}
// 抽象产品
public interface Coffee {
}
// 具体产品
public class Cappuccino implements Coffee {
}
2.2.2 总结
适用场景:
优点:
缺点:
抽象工厂模式是指提供一个创建一系列相关或相互依赖对象的接口,无须指定他们具体的类。
工厂方法模式中考虑的是一类产品的生产,如电脑厂只生产电脑,电话厂只生产电话,这种工厂只生产同种类的产品,同种类产品称为同等级产品,也就是说,工厂方法模式只考虑生产同等级的产品,但是现实生活中许多工厂都是综合型工厂,能生产多等级(种类)的产品,如上面说的电脑和电话,本质上他们都属于电器,那么他们就能在电器厂里生产出来,而抽象工厂模式就将考虑多等级产品的生产,将同一个具体工厂所生产的位于不同等级的一组产品称为一个产品族,如上图所示纵轴是产品等级,也就是同一类产品;横轴是产品族,也就是同一品牌的产品,同一品牌的产品产自同一个工厂。
抽象工厂模式的主要角色如下:
3.1 代码实现
// 咖啡店 抽象工厂
public interface CoffeeShopFactory {
// 咖啡类
Coffee createCoffee();
// 甜点类
Dessert createDessert();
}
// 美式风格工厂
public class AmericanFactory implements CoffeeShopFactory {
@Override
public Coffee createCoffee() {
return new Americano();
}
@Override
public Dessert createDessert() {
return new Cheesecake();
}
}
// 意式风格工厂
public class ItalyFactory implements CoffeeShopFactory {
@Override
public Coffee createCoffee() {
return new Cappuccino();
}
@Override
public Dessert createDessert() {
return new Tiramisu();
}
}
3.2 总结
产品族:一系列相关的产品,整合到一起有关联性
产品等级:同一个继承体系
适用场景:
优点:
缺点:
原型模式是指原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。调用者不需要知道任何创建细节,不调用构造函数。
原型模式包含如下角色:
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student implements Cloneable {
private String name;
private String sex;
private Integer age;
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
public static void main(String[] args) throws Exception{
Student stu1 = new Student("张三", "男", 18);
Student stu2 = (Student)stu1.clone();
stu2.setName("李四");
System.out.println(stu1);// Student(name=张三, sex=男, age=18)
System.out.println(stu2);// Student(name=李四, sex=男, age=18)
}
}
可以看到,把一个学生复制过来,只是改了姓名而已,其他属性完全一样没有改变,需要注意的是,一定要在被拷贝的对象上实现Cloneable接口,否则会抛出CloneNotSupportedException异常。
4.1 浅克隆
创建一个新对象,新对象的属性和原来对象完全相同,对于非基本类型属性,仍指向原有属性所指向的对象的内存地址。
@Data
public class Clazz implements Cloneable {
private String name;
private Student student;
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student implements Serializable {
private String name;
private String sex;
private Integer age;
}
public static void main(String[] args) throws Exception{
Clazz clazz1 = new Clazz();
clazz1.setName("高三一班");
Student stu1 = new Student("张三", "男", 18);
clazz1.setStudent(stu1);
System.out.println(clazz1); // Clazz(name=高三一班, student=Student(name=张三, sex=男, age=18))
Clazz clazz2 = (Clazz)clazz1.clone();
Student stu2 = clazz2.getStudent();
stu2.setName("李四");
System.out.println(clazz1); // Clazz(name=高三一班, student=Student(name=李四, sex=男, age=18))
System.out.println(clazz2); // Clazz(name=高三一班, student=Student(name=李四, sex=男, age=18))
}
可以看到,当修改了stu2的姓名时,stu1的姓名同样也被修改了,这说明stu1和stu2是同一个对象,这就是浅克隆的特点,对具体原型类中的引用类型的属性进行引用的复制。同时,这也可能是浅克隆所带来的弊端,因为结合该例子的原意,显然是想在班级中新增一名叫李四的学生,而非让所有的学生都改名叫李四,于是我们这里就要使用深克隆。
4.2 深克隆
创建一个新对象,属性中引用的其他对象也会被克隆,不再指向原有对象地址。
@Data
public class Clazz implements Cloneable, Serializable {
private String name;
private Student student;
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
protected Object deepClone() throws IOException, ClassNotFoundException {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(this);
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
return ois.readObject();
}
}
public static void main(String[] args) throws Exception{
Clazz clazz1 = new Clazz();
clazz1.setName("高三一班");
Student stu1 = new Student("张三", "男", 18);
clazz1.setStudent(stu1);
Clazz clazz3 = (Clazz)clazz1.deepClone();
Student stu3 = clazz3.getStudent();
stu3.setName("王五");
System.out.println(clazz1); // Clazz(name=高三一班, student=Student(name=张三, sex=男, age=18))
System.out.println(clazz3); // Clazz(name=高三一班, student=Student(name=王五, sex=男, age=18))
}
可以看到,当修改了stu3的姓名时,stu1的姓名并没有被修改了,这说明stu3和stu1已经是不同的对象了,说明Clazz中的Student也被克隆了,不再指向原有对象地址,这就是深克隆。这里需要注意的是,Clazz类和Student类都需要实现Serializable接口,否则会抛出NotSerializableException异常。
4.3 克隆破坏单例与解决办法
PS:上面例子有的代码,这里便不重复写了,可以在上面的代码基础上添加以下代码
// Clazz类
private static Clazz clazz = new Clazz();
private Clazz(){}
public static Clazz getInstance() {return clazz;}
// 测试
public static void main(String[] args) throws Exception{
Clazz clazz1 = Clazz.getInstance();
Clazz clazz2 = (Clazz)clazz1.clone();
System.out.println(clazz1 == clazz2); // false
}
可以看到clazz1和clazz2并不相等,也就是说他们并不是同一个对象,也就是单例被破坏了。
解决办法也很简单,首先第一个就是不实现Cloneable接口即可,但是不实现Cloneable接口进行clone则会抛出CloneNotSupportedException异常。第二个方法就是重写clone()方法即可,如下:
@Override
protected Object clone() throws CloneNotSupportedException {
return clazz;
}
// 测试输出
System.out.println(clazz1 == clazz2) // true
可以看到,上面clazz1和clazz2是相等的,即单例没有被破坏。
另外我们知道,单例就是只有一个实例对象,如果重写了clone()方法保证单例的话,那么通过克隆出来的对象则不可以重新修改里面的属性,因为修改以后就会连同克隆对象一起被修改,所以是需要单例还是克隆,在实际应用中需要好好衡量。
4.4 总结
适用场景:
优点:
缺点:
建造者模式是将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。用户只需指定需要建造的类型就可以获得对象,建造过程及细节不需要了解。
建造者(Builder)模式包含如下角色:
5.1 常规写法
//产品类 电脑
@Data
public class Computer {
private String motherboard;
private String cpu;
private String memory;
private String disk;
private String gpu;
private String power;
private String heatSink;
private String chassis;
}
// 抽象 builder类(接口) 组装电脑
public interface ComputerBuilder {
Computer computer = new Computer();
void buildMotherboard();
void buildCpu();
void buildMemory();
void buildDisk();
void buildGpu();
void buildHeatSink();
void buildPower();
void buildChassis();
Computer build();
}
// 具体 builder类 华硕ROG全家桶电脑(手动狗头)
public class AsusComputerBuilder implements ComputerBuilder {
@Override
public void buildMotherboard() {
computer.setMotherboard("Extreme主板");
}
@Override
public void buildCpu() {
computer.setCpu("Inter 12900KS");
}
@Override
public void buildMemory() {
computer.setMemory("芝奇幻峰戟 16G*2");
}
@Override
public void buildDisk() {
computer.setDisk("三星980Pro 2T");
}
@Override
public void buildGpu() {
computer.setGpu("华硕3090Ti 水猛禽");
}
@Override
public void buildHeatSink() {
computer.setHeatSink("龙神二代一体式水冷");
}
@Override
public void buildPower() {
computer.setPower("雷神二代1200W");
}
@Override
public void buildChassis() {
computer.setChassis("太阳神机箱");
}
@Override
public Computer build() {
return computer;
}
}
// 指挥者类 指挥该组装什么电脑
@AllArgsConstructor
public class ComputerDirector {
private ComputerBuilder computerBuilder;
public Computer construct() {
computerBuilder.buildMotherboard();
computerBuilder.buildCpu();
computerBuilder.buildMemory();
computerBuilder.buildDisk();
computerBuilder.buildGpu();
computerBuilder.buildHeatSink();
computerBuilder.buildPower();
computerBuilder.buildChassis();
return computerBuilder.build();
}
}
// 测试
public static void main(String[] args) {
ComputerDirector computerDirector = new ComputerDirector(new AsusComputerBuilder());
// Computer(motherboard=Extreme主板, cpu=Inter 12900KS, memory=芝奇幻峰戟 16G*2, disk=三星980Pro 2T, gpu=华硕3090Ti 水猛禽, power=雷神二代1200W, heatSink=龙神二代一体式水冷, chassis=太阳神机箱)
System.out.println(computerDirector.construct());
}
上面示例是建造者模式的常规用法,指挥者类ComputerDirector在建造者模式中具有很重要的作用,它用于指导具体构建者如何构建产品,控制调用先后次序,并向调用者返回完整的产品类,但是有些情况下需要简化系统结构,可以把指挥者类和抽象建造者进行结合,于是就有了下面的简化写法。
5.2 简化写法
// 把指挥者类和抽象建造者合在一起的简化建造者类
public class SimpleComputerBuilder {
private Computer computer = new Computer();
public void buildMotherBoard(String motherBoard){
computer.setMotherboard(motherBoard);
}
public void buildCpu(String cpu){
computer.setCpu(cpu);
}
public void buildMemory(String memory){
computer.setMemory(memory);
}
public void buildDisk(String disk){
computer.setDisk(disk);
}
public void buildGpu(String gpu){
computer.setGpu(gpu);
}
public void buildPower(String power){
computer.setPower(power);
}
public void buildHeatSink(String heatSink){
computer.setHeatSink(heatSink);
}
public void buildChassis(String chassis){
computer.setChassis(chassis);
}
public Computer build(){
return computer;
}
}
// 测试
public static void main(String[] args) {
SimpleComputerBuilder simpleComputerBuilder = new SimpleComputerBuilder();
simpleComputerBuilder.buildMotherBoard("Extreme主板");
simpleComputerBuilder.buildCpu("Inter 12900K");
simpleComputerBuilder.buildMemory("芝奇幻峰戟 16G*2");
simpleComputerBuilder.buildDisk("三星980Pro 2T");
simpleComputerBuilder.buildGpu("华硕3090Ti 水猛禽");
simpleComputerBuilder.buildPower("雷神二代1200W");
simpleComputerBuilder.buildHeatSink("龙神二代一体式水冷");
simpleComputerBuilder.buildChassis("太阳神机箱");
// Computer(motherboard=Extreme主板, cpu=Inter 12900K, memory=芝奇幻峰戟 16G*2, disk=三星980Pro 2T, gpu=华硕3090Ti 水猛禽, power=雷神二代1200W, heatSink=龙神二代一体式水冷, chassis=太阳神机箱)
System.out.println(simpleComputerBuilder.build());
}
可以看到,对比常规写法,这样写确实简化了系统结构,但同时也加重了建造者类的职责,也不是太符合单一职责原则,如果construct() 过于复杂,建议还是封装到 Director 中。
5.3 链式写法
// 链式写法建造者类
public class SimpleComputerBuilder {
private Computer computer = new Computer();
public SimpleComputerBuilder buildMotherBoard(String motherBoard){
computer.setMotherboard(motherBoard);
return this;
}
public SimpleComputerBuilder buildCpu(String cpu){
computer.setCpu(cpu);
return this;
}
public SimpleComputerBuilder buildMemory(String memory){
computer.setMemory(memory);
return this;
}
public SimpleComputerBuilder buildDisk(String disk){
computer.setDisk(disk);
return this;
}
public SimpleComputerBuilder buildGpu(String gpu){
computer.setGpu(gpu);
return this;
}
public SimpleComputerBuilder buildPower(String power){
computer.setPower(power);
return this;
}
public SimpleComputerBuilder buildHeatSink(String heatSink){
computer.setHeatSink(heatSink);
return this;
}
public SimpleComputerBuilder buildChassis(String chassis){
computer.setChassis(chassis);
return this;
}
public Computer build(){
return computer;
}
}
// 测试
public static void main(String[] args) {
Computer asusComputer = new SimpleComputerBuilder().buildMotherBoard("Extreme主板")
.buildCpu("Inter 12900K")
.buildMemory("芝奇幻峰戟 16G*2")
.buildDisk("三星980Pro 2T")
.buildGpu("华硕3090Ti 水猛禽")
.buildPower("雷神二代1200W")
.buildHeatSink("龙神二代一体式水冷")
.buildChassis("太阳神机箱").build();
System.out.println(asusComputer);
}
可以看到,其实链式写法与普通写法的区别并不大,只是在建造者类组装部件的时候,同时将建造者类返回即可,使用链式写法使用起来更方便,某种程度上也可以提高开发效率。从软件设计上,对程序员的要求比较高。比较常见的mybatis-plus中的条件构造器就是使用的这种链式写法。
5.4 总结
适用场景:
优点:
缺点:
与工厂模式的区别:
与抽象工厂模式的区别:
代理模式是指为其他对象提供一种代理,以控制对这个对象的访问。代理对象在访问对象和目标对象之间起到中介作用。
Java中的代理按照代理类生成时机不同又分为静态代理和动态代理。静态代理代理类在编译期就生成,而动态代理代理类则是在Java运行时动态生成。动态代理又有JDK代理和CGLib代理两种。
代理(Proxy)模式分为三种角色:
1.1 静态代理
静态代理就是指我们在给一个类扩展功能的时候,我们需要去书写一个静态的类,相当于在之前的类上套了一层,这样我们就可以在不改变之前的类的前提下去对原有功能进行扩展,静态代理需要代理对象和目标对象实现一样的接口。
// 火车站接口,有卖票功能
public interface TrainStation {
void sellTickets();
}
// 广州火车站卖票
public class GuangzhouTrainStation implements TrainStation {
@Override
public void sellTickets() {
System.out.println("广州火车站卖票啦");
}
}
// 代售点卖票(代理类)
public class ProxyPoint implements TrainStation {
// 目标对象(代理火车站售票)
private GuangzhouTrainStation station = new GuangzhouTrainStation();
@Override
public void sellTickets() {
System.out.println("代售加收5%手续费");
station.sellTickets();
}
public static void main(String[] args) {
ProxyPoint proxyPoint = new ProxyPoint();
// 代售加收5%手续费
// 广州火车站卖票啦
proxyPoint.sellTickets();
}
}
// 测试
public static void main(String[] args) {
ProxyPoint proxyPoint = new ProxyPoint();
// 代售加收5%手续费
// 火车站卖票啦
proxyPoint.sellTickets();
}
可以从上面代码看到,我们访问的是ProxyPoint对象,也就是说ProxyPoint是作为访问对象和目标对象的中介的,同时也对sellTickets方法进行了增强(代理点收取加收5%手续费)。
静态代理的优点是实现简单,容易理解,只要确保目标对象和代理对象实现共同的接口或继承相同的父类就可以在不修改目标对象的前提下进行扩展。
而缺点也比较明显,那就是代理类和目标类必须有共同接口(父类),并且需要为每一个目标类维护一个代理类,当需要代理的类很多时会创建出大量代理类。一旦接口或父类的方法有变动,目标对象和代理对象都需要作出调整。
1.2 动态代理
代理类在代码运行时创建的代理称之为动态代理。动态代理中代理类并不是预先在Java代码中定义好的,而是运行时由JVM动态生成,并且可以代理多个目标对象。
1.2.1 jdk动态代理
JDK动态代理是Java JDK自带的一个动态代理实现, 位于java.lang.reflect包下。
// 火车站接口,有卖票功能
public interface TrainStation {
void sellTickets();
}
// 广州火车站卖票
public class GuangzhouTrainStation implements TrainStation {
@Override
public void sellTickets() {
System.out.println("广州火车站卖票啦");
}
}
// 深圳火车站卖票
public class ShenzhenTrainStation implements TrainStation {
@Override
public void sellTickets() {
System.out.println("深圳火车站卖票啦");
}
}
// 代售点卖票(代理类)
public class ProxyPoint implements InvocationHandler {
private TrainStation trainStation;
public TrainStation getProxyObject(TrainStation trainStation) {
this.trainStation = trainStation;
Class extends TrainStation> clazz = trainStation.getClass();
return (TrainStation) Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("代售火车票收取5%手续费");
return method.invoke(this.trainStation, args);
}
}
// 测试
public static void main(String[] args) {
ProxyPoint proxy = new ProxyPoint();
TrainStation guangzhouTrainStation = proxy.getProxyObject(new GuangzhouTrainStation());
// 代售火车票收取5%手续费
// 广州火车站卖票啦
guangzhouTrainStation.sellTickets();
TrainStation shenzhenTrainStation = proxy.getProxyObject(new ShenzhenTrainStation());
// 代售火车票收取5%手续费
// 深圳火车站卖票啦
shenzhenTrainStation.sellTickets();
}
优点:
缺点:
1.2.2 CGLIB动态代理
CGLIB是一个强大的、高性能的代码生成库。它可以在运行期扩展Java类和接口,其被广泛应用于AOP框架中(Spring、dynaop)中, 用以提供方法拦截。CGLIB比JDK动态代理更强的地方在于它不仅可以接管Java接口, 还可以接管普通类的方法。
cglib
cglib
${cglib-version}
// 代售点卖票(代理类)
public class ProxyPoint implements MethodInterceptor {
public TrainStation getProxyObject(Class extends TrainStation> trainStation) {
//创建Enhancer对象,类似于JDK动态代理的Proxy类,下一步就是设置几个参数
Enhancer enhancer =new Enhancer();
//设置父类的字节码对象
enhancer.setSuperclass(trainStation);
//设置回调函数
enhancer.setCallback(this);
//创建代理对象并返回
return (TrainStation) enhancer.create();
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("代售火车票收取5%手续费");
return methodProxy.invokeSuper(o, objects);
}
}
// 测试
public static void main(String[] args) {
ProxyPoint proxy = new ProxyPoint();
TrainStation guangzhouTrainStation = proxy.getProxyObject(GuangzhouTrainStation.class);
// 代售火车票收取5%手续费
// 广州火车站卖票啦
guangzhouTrainStation.sellTickets();
TrainStation shenzhenTrainStation = proxy.getProxyObject(ShenzhenTrainStation.class);
// 代售火车票收取5%手续费
// 深圳火车站卖票啦
shenzhenTrainStation.sellTickets();
}
1.3 总结
应用场景:
优点:
缺点:
两种动态代理的对比:
适配器模式,它的功能是将一个类的接口变成客户端所期望的另一种接口,从而使原本因接口不匹配而导致无法在一起工作的两个类能够一起工作。适配器模式分为类适配器模式和对象适配器模式,前者类之间的耦合度比后者高,且要求程序员了解现有组件库中的相关组件的内部结构,所以应用相对较少些。
适配器模式(Adapter)包含以下主要角色:
2.1 类适配器
类适配器是通过定义一个适配器类来实现当前系统的业务接口,同时又继承现有组件库中已经存在的组件来实现的,类图如下:
// 适配者 220V电压
public class AC220 {
public int output() {
System.out.println("输出220V交流电");
return 220;
}
}
// 目标 5V
public interface DC5 {
public int output5();
}
// 适配器类(电源适配器)
public class PowerAdapter extends AC220 implements DC5 {
@Override
public int output5() {
int output220 = super.output();
int output5 = output220 / 44;
System.out.println(output220 + "V适配转换成" + output5 + "V");
return output5;
}
}
// 测试
public static void main(String[] args) {
PowerAdapter powerAdapter = new PowerAdapter();
// 输出220V交流电
powerAdapter.output();
// 输出220V交流电
// 220V适配转换成5V
powerAdapter.output5();
}
通过上面代码例子可以看出,类适配器有一个很明显的缺点,就是违背了合成复用原则。结合上面的例子,假如我不是220V的电压了,是380V电压呢?那就要多建一个380V电压的适配器了。同理,由于Java是单继承的原因,如果不断的新增适配者,那么就要无限的新增适配器,于是就有了对象适配器。
2.2 对象适配器
对象适配器的实现方式是通过现有组件库中已经实现的组件引入适配器类中,该类同时实现当前系统的业务接口。
// 电源接口
public interface Power {
int output();
}
// 适配者 220V电压
public class AC220 implements Power {
@Override
public int output() {
System.out.println("输出220V交流电");
return 220;
}
}
// 目标 5V
public interface DC5 {
public int output5();
}
@AllArgsConstructor
public class PowerAdapter implements DC5 {
// 适配者
private Power power;
@Override
public int output5() {
int output220 = power.output();
int output5 = output220 / 44;
System.out.println(output220 + "V适配转换成" + output5 + "V");
return output5;
}
}
// 测试
public static void main(String[] args) {
DC5 powerAdapter = new PowerAdapter(new AC220());
// 输出220V交流电
// 220V适配转换成5V
powerAdapter.output5();
}
可以看到,上面代码中,只实现了目标接口,并没有继承适配者,而是将适配者类实现适配者接口,在适配器中引入适配者接口,当我们需要使用不同的适配者通过适配器进行转换时,就无需再新建适配器类了,如上面例子,假如我需要380V的电源转换成5V的,那么客户端只需要调用适配器时传入380V电源的类即可,就无需再新建一个380V电源的适配器了(PS:上述逻辑代码中output220 / 44请忽略,可以根据实际情况编写实际的通用逻辑代码)。
2.3 接口适配器
接口适配器主要是解决类臃肿的问题,我们可以把所有相近的适配模式的方法都放到同一个接口里面,去实现所有方法,当客户端需要哪个方法直接调用哪个方法即可。如上面例子所示,我们只是转换成了5V电压,那假如我要转换成12V,24V,30V...呢?那按照上面的写法就需要新建12V,24V,30V...的接口,这样就会导致类过于多了。那么我们就可以把5V,12V,24V,30V...这些转换方法,通通都写到一个接口里去,这样当我们需要转换哪种就直接调用哪种即可。
// 这里例子 输出不同直流电接口
public interface DC {
int output5();
int output12();
int output24();
int output30();
}
// 适配器类(电源适配器)
@AllArgsConstructor
public class PowerAdapter implements DC {
private Power power;
@Override
public int output5() {
// 具体实现逻辑
return 5;
}
@Override
public int output12() {
// 具体实现逻辑
return 12;
}
@Override
public int output24() {
// 具体实现逻辑
return 24;
}
@Override
public int output30() {
// 具体实现逻辑
return 30;
}
}
2.4 总结
适用场景:
优点:
缺点:
装饰模式,是指在不改变原有对象的基础上,将功能附加到对象上,提供了比继承更有弹性的替代方案(扩展原有对象的功能)
装饰(Decorator)模式中的角色:
3.1 继承方式
举一个简单的例子,假如现在有一碟炒饭,每个人的口味不一样,有些人喜欢加鸡蛋,有些人喜欢加鸡蛋火腿,有些人喜欢加鸡蛋火腿胡萝卜等,那么就会发现,如果采用继承的方式去实现这个例子,那么没加一个配料,都需要创建新的配料类去继承上一个旧的配料类,那么久而久之,就会产生很多类了,而且还不利于扩展,代码如下:
// 炒饭类
public class FriedRice {
String getDesc() {
return "炒饭";
}
Integer getPrice() {
return 5;
}
}
// 炒饭加鸡蛋类
public class FriedRiceAddEgg extends FriedRice{
String getDesc() {
return super.getDesc() + "+鸡蛋";
}
Integer getPrice() {
return super.getPrice() + 2;
}
}
// 炒饭加鸡蛋加火腿类
public class FriedRiceAddEggAndHam extends FriedRiceAddEgg {
String getDesc() {
return super.getDesc() + "+火腿";
}
Integer getPrice() {
return super.getPrice() + 3;
}
}
// 测试方法
public static void main(String[] args) {
FriedRice friedRice = new FriedRice();
System.out.println(friedRice.getDesc() + friedRice.getPrice() + "元");// 炒饭5元
FriedRice friedRiceAddEgg = new FriedRiceAddEgg();
System.out.println(friedRiceAddEgg.getDesc() + friedRiceAddEgg.getPrice() + "元"); // 炒饭+鸡蛋7元
FriedRice friedRiceAddEggAndHam = new FriedRiceAddEggAndHam();
System.out.println(friedRiceAddEggAndHam.getDesc() + friedRiceAddEggAndHam.getPrice() + "元");// 炒饭+鸡蛋+火腿10元
}
可以从上面看到,如果我们只需要炒饭加火腿,那么我们还需要创建一个FriedRiceAddHam类去继承FriedRice类,所以继承的方式扩展性非常不好,且需要定义非常多的子类,下面就可以用装饰器模式去改进它。
3.2 装饰器模式方式
// 炒饭类
public class FriedRice {
String getDesc() {
return "炒饭";
}
Integer getPrice() {
return 5;
}
}
// 配料表
public abstract class Ingredients extends FriedRice{
private FriedRice friedRice;
public Ingredients(FriedRice friedRice) {
this.friedRice = friedRice;
}
String getDesc() {
return this.friedRice.getDesc();
}
Integer getPrice() {
return this.friedRice.getPrice();
}
}
// 鸡蛋配料
public class Egg extends Ingredients {
public Egg(FriedRice friedRice) {
super(friedRice);
}
String getDesc() {
return super.getDesc() + "+鸡蛋";
}
Integer getPrice() {
return super.getPrice() + 2;
}
}
// 火腿配料
public class Ham extends Ingredients {
public Ham(FriedRice friedRice){
super(friedRice);
}
String getDesc() {
return super.getDesc() + "+火腿";
}
Integer getPrice() {
return super.getPrice() + 3;
}
}
// 测试方法
public static void main(String[] args) {
FriedRice friedRice = new FriedRice();
System.out.println(friedRice.getDesc() + friedRice.getPrice() + "元"); // 炒饭5元
friedRice = new Egg(friedRice);
System.out.println(friedRice.getDesc() + friedRice.getPrice() + "元"); // 炒饭+鸡蛋7元
friedRice = new Egg(friedRice);
System.out.println(friedRice.getDesc() + friedRice.getPrice() + "元");// 炒饭+鸡蛋+鸡蛋9元
friedRice = new Ham(friedRice);
System.out.println(friedRice.getDesc() + friedRice.getPrice() + "元");// 炒饭+鸡蛋+鸡蛋+火腿12元
}
可以看到,使用装饰器模式的方法实现,与普通的继承方法实现,最大的区别就是一种配料只有一个类,而且在加配料的时候,也可以直接想加多少就加多少,不需要说一个鸡蛋一个类,两个鸡蛋也要创建一个类,这样可以带来比继承更加灵活的扩展功能,使用也更加方便。
3.3 总结
装饰器模式与代理模式对比:
适用场景:
优点:
缺点:
桥接模式也称为桥梁模式、接口模式或者柄体(Handle and Body)模式,是将抽象部分与他的具体实现部分分离,使它们都可以独立地变化,通过组合的方式建立两个类之间的联系,而不是继承。
桥接(Bridge)模式包含以下主要角色:
4.1 代码实现
下面以一个多系统多视频格式文件播放为例子:
// 视频接口
public interface Video {
void decode(String fileName);
}
// MP4格式类
public class Mp4 implements Video{
@Override
public void decode(String fileName) {
System.out.println("MP4视频文件:"+ fileName);
}
}
// RMVB格式类
public class Rmvb implements Video{
@Override
public void decode(String fileName) {
System.out.println("rmvb文件:" + fileName);
}
}
// 操作系统抽象类
@AllArgsConstructor
public abstract class OperatingSystem {
Video video;
public abstract void play(String fileName);
}
// iOS系统
public class Ios extends OperatingSystem {
public Ios(Video video){
super(video);
}
@Override
public void play(String fileName) {
video.decode(fileName);
}
}
// windows系统
public class Windows extends OperatingSystem {
public Windows(Video video){
super(video);
}
@Override
public void play(String fileName) {
video.decode(fileName);
}
}
可以通过类图看到,视频类和操作系统类之间通过OperatingSystem类桥接关联起来。
4.2 总结
适用场景:
优点:
缺点:
外观模式又称门面模式,提供了一个统一的接口,用来访问子系统中的一群接口。
特征:门面模式定义了一个高层接口,让子系统更容易使用。
外观(Facade)模式包含以下主要角色:
5.1 代码实现
下面以一个智能音箱实现起床睡觉一键操作电器的场景,通过代码模拟一下这个场景:
public class Light {
public void on() {
System.out.println("开灯");
}
public void off() {
System.out.println("关灯");
}
}
public class Tv {
public void on() {
System.out.println("开电视");
}
public void off() {
System.out.println("关电视");
}
}
public class Fan {
public void on() {
System.out.println("开风扇");
}
public void off() {
System.out.println("关风扇");
}
}
public class SmartSpeaker {
private Light light;
private Tv tv;
private Fan fan;
public SmartSpeaker() {
light = new Light();
tv = new Tv();
fan = new Fan();
}
public void say(String order) {
if (order.contains("起床")) {
getUp();
} else if (order.contains("睡觉")) {
sleep();
} else {
System.out.println("我还听不懂你说的啥!");
}
}
public void getUp() {
System.out.println("起床");
light.on();
tv.on();
fan.off();
}
public void sleep() {
System.out.println("睡觉");
light.off();
tv.off();
fan.on();
}
}
public static void main(String[] args) {
SmartSpeaker smartSpeaker = new SmartSpeaker();
//睡觉
//关灯
//关电视
//开风扇
smartSpeaker.say("我要睡觉了!");
//起床
//开灯
//开电视
//关风扇
smartSpeaker.say("我起床了!");
//我还听不懂你说的啥!
smartSpeaker.say("Emmm");
}
5.2 总结
适用场景:
优点:
缺点:
组合模式也称为整体-部分(Part-Whole)模式,它的宗旨是通过将单个对象(叶子结点)和组合对象(树枝节点)用相同的接口进行表示。
作用:使客户端对单个对象和组合对象保持一致的方式处理。
6.1 代码实现
下面以一个添加菜单的例子通过代码实现:
// 菜单组件
public abstract class MenuComponent {
String name;
Integer level;
public void add(MenuComponent menuComponent) {
throw new UnsupportedOperationException("不支持添加操作!");
}
public void remove(MenuComponent menuComponent) {
throw new UnsupportedOperationException("不支持删除操作!");
}
public MenuComponent getChild(Integer i) {
throw new UnsupportedOperationException("不支持获取子菜单操作!");
}
public String getName() {
throw new UnsupportedOperationException("不支持获取名字操作!");
}
public void print() {
throw new UnsupportedOperationException("不支持打印操作!");
}
}
// 菜单类
public class Menu extends MenuComponent {
private List menuComponentList = new ArrayList<>();
public Menu(String name,int level){
this.level = level;
this.name = name;
}
@Override
public void add(MenuComponent menuComponent) {
menuComponentList.add(menuComponent);
}
@Override
public void remove(MenuComponent menuComponent) {
menuComponentList.remove(menuComponent);
}
@Override
public MenuComponent getChild(Integer i) {
return menuComponentList.get(i);
}
@Override
public void print() {
for (int i = 1; i < level; i++) {
System.out.print("--");
}
System.out.println(name);
for (MenuComponent menuComponent : menuComponentList) {
menuComponent.print();
}
}
}
// 子菜单类
public class MenuItem extends MenuComponent {
public MenuItem(String name,int level) {
this.name = name;
this.level = level;
}
@Override
public void print() {
for (int i = 1; i < level; i++) {
System.out.print("--");
}
System.out.println(name);
}
}
// 测试方法
public static void main(String[] args) {
//创建一级菜单
MenuComponent component = new Menu("系统管理",1);
MenuComponent menu1 = new Menu("用户管理",2);
menu1.add(new MenuItem("新增用户",3));
menu1.add(new MenuItem("修改用户",3));
menu1.add(new MenuItem("删除用户",3));
MenuComponent menu2 = new Menu("角色管理",2);
menu2.add(new MenuItem("新增角色",3));
menu2.add(new MenuItem("修改角色",3));
menu2.add(new MenuItem("删除角色",3));
menu2.add(new MenuItem("绑定用户",3));
//将二级菜单添加到一级菜单中
component.add(menu1);
component.add(menu2);
//打印菜单名称(如果有子菜单一块打印)
component.print();
}
// 测试结果
系统管理
--用户管理
----新增用户
----修改用户
----删除用户
--角色管理
----新增角色
----修改角色
----删除角色
----绑定用户
6.2 总结
适用场景:
优点:
缺点:
分类:
享元模式又称为轻量级模式,是对象池的一种实现,类似于线程池,线程池可以避免不停的创建和销毁多个对象,消耗性能。提供了减少对象数量从而改善应用所需的对象结构的方式。宗旨:共享细粒度对象,将多个对同一对象的访问集中起来。
享元(Flyweight )模式中存在以下两种状态:
享元模式的主要有以下角色:
7.1 代码实现
下面通过查询火车票的例子来用代码进行模拟实现:
// 抽象接口
public interface ITicket {
void show(String seat);
}
public class TrainTicket implements ITicket {
private String from;
private String to;
private Integer price;
public TrainTicket(String from, String to) {
this.from = from;
this.to = to;
}
@Override
public void show(String seat) {
this.price = new Random().nextInt(500);
System.out.println(from + "->" + to + ":" + seat + "价格:" + this.price);
}
}
// 工厂类
public class TicketFactory {
private static Map pool = new ConcurrentHashMap<>();
public static ITicket getTicket(String from, String to) {
String key = from + "->" + to;
if (pool.containsKey(key)) {
System.out.println("使用缓存获取火车票:" + key);
return pool.get(key);
}
System.out.println("使用数据库获取火车票:" + key);
ITicket ticket = new TrainTicket(from, to);
pool.put(key, ticket);
return ticket;
}
}
// 测试
public static void main(String[] args) {
ITicket ticket = getTicket("北京", "上海");
//使用数据库获取火车票:北京->上海
//北京->上海:二等座价格:20
ticket.show("二等座");
ITicket ticket1 = getTicket("北京", "上海");
//使用缓存获取火车票:北京->上海
//北京->上海:商务座价格:69
ticket1.show("商务座");
ITicket ticket2 = getTicket("上海", "北京");
//使用数据库获取火车票:上海->北京
//上海->北京:一等座价格:406
ticket2.show("一等座");
System.out.println(ticket == ticket1);//true
System.out.println(ticket == ticket2);//false
}
可以看到ticket和ticket2是使用数据库查询的,而ticket1是使用缓存查询的,同时ticket == ticket1返回的是true,ticket == ticket2返回的是false,证明ticket和ticket1是共享的对象。
7.2 总结
适用场景:
优点:
缺点:
模板方法模式通常又叫模板模式,是指定义一个算法的骨架,并允许之类为其中的一个或者多个步骤提供实现。模板方法模式使得子类可以在不改变算法结构的情况下,重新定义算法的某些步骤。
模板方法(Template Method)模式包含以下主要角色:
1.1 代码实现
下面以一个简单的请假流程来通过代码来实现:
public abstract class DayOffProcess {
// 请假模板
public final void dayOffProcess() {
// 领取申请表
this.pickUpForm();
// 填写申请信息
this.writeInfo();
// 签名
this.signUp();
// 提交到不同部门审批
this.summit();
// 行政部备案
this.filing();
}
private void filing() {
System.out.println("行政部备案");
}
protected abstract void summit();
protected abstract void signUp();
private void writeInfo() {
System.out.println("填写申请信息");
}
private void pickUpForm() {
System.out.println("领取申请表");
}
}
public class ZhangSan extends DayOffProcess {
@Override
protected void summit() {
System.out.println("张三签名");
}
@Override
protected void signUp() {
System.out.println("提交到技术部审批");
}
}
public class Lisi extends DayOffProcess {
@Override
protected void summit() {
System.out.println("李四签名");
}
@Override
protected void signUp() {
System.out.println("提交到市场部审批");
}
}
// 测试方法
public static void main(String[] args) {
DayOffProcess zhangsan = new ZhangSan();
//领取申请表
//填写申请信息
//提交到技术部审批
//张三签名
//行政部备案
zhangsan.dayOffProcess();
DayOffProcess lisi = new Lisi();
//领取申请表
//填写申请信息
//提交到市场部审批
//李四签名
//行政部备案
lisi.dayOffProcess();
}
1.2 总结
适用场景:
优点:
缺点:
策略模式又叫政策模式(Policy Pattern),它是将定义的算法家族分别封装起来,让它们之间可以互相替换,从而让算法的变化不会影响到使用算法的用户。可以避免多重分支的if......else和switch语句。
策略模式的主要角色如下:
2.1 普通案例(会员卡打折)
// 会员卡接口
public interface VipCard {
public void discount();
}
public class GoldCard implements VipCard {
@Override
public void discount() {
System.out.println("金卡打7折");
}
}
public class SilverCard implements VipCard {
@Override
public void discount() {
System.out.println("银卡打8折");
}
}
public class CopperCard implements VipCard {
@Override
public void discount() {
System.out.println("铜卡打9折");
}
}
public class Normal implements VipCard {
@Override
public void discount() {
System.out.println("普通会员没有折扣");
}
}
// 会员卡容器类
public class VipCardFactory {
private static Map map = new ConcurrentHashMap<>();
static {
map.put("gold", new GoldCard());
map.put("silver", new SilverCard());
map.put("copper", new CopperCard());
}
public static VipCard getVIPCard(String level) {
return map.get(level) != null ? map.get(level) : new Normal();
}
}
// 测试方法
public static void main(String[] args) {
//金卡打7折
VipCardFactory.getVIPCard("gold").discount();
//银卡打8折
VipCardFactory.getVIPCard("silver").discount();
//普通会员没有折扣
VipCardFactory.getVIPCard("other").discount();
}
用一个容器(Map)装起来,可以通过传进来的参数直接获取对应的策略,避免了if...else。
2.2 支付方式案例
// 支付方式抽象类
public abstract class Payment {
public String pay(String uid, double money) {
double balance = queryBalance(uid);
if (balance < money) {
return "支付失败!余额不足!欠" + (money - balance) + "元!";
}
return "支付成功!支付金额:" + money + "余额剩余:" + (balance - money);
}
protected abstract String getPaymentName();
protected abstract double queryBalance(String uid);
}
// 现金支付 默认方式
public class Cash extends Payment{
@Override
protected String getPaymentName() {
return "现金支付";
}
@Override
protected double queryBalance(String uid) {
return 1000;
}
}
// 支付宝类
public class AliPay extends Payment {
@Override
protected String getPaymentName() {
return "支付宝";
}
@Override
protected double queryBalance(String uid) {
return 500;
}
}
// 微信支付类
public class WeChatPay extends Payment {
@Override
protected String getPaymentName() {
return "微信支付";
}
@Override
protected double queryBalance(String uid) {
return 300;
}
}
// 支付方式容器策略类
public class PaymentStrategy {
private static Map map = new ConcurrentHashMap<>();
static {
map.put("WeChat", new WeChatPay());
map.put("Ali", new AliPay());
}
public static Payment getPayment(String payment) {
return map.get(payment) == null ? new Cash() : map.get(payment);
}
}
// 订单交易类
@AllArgsConstructor
public class Order {
private String uid;
private double amount;
public String pay() {
return pay("cash");
}
public String pay(String key) {
Payment payment = PaymentStrategy.getPayment(key);
System.out.println("欢迎使用" + payment.getPaymentName());
System.out.println("本次交易金额:" + this.amount + ",开始扣款...");
return payment.pay(this.uid, this.amount);
}
}
// 测试方法
public static void main(String[] args) {
Order order = new Order("20221014001", 500);
//欢迎使用微信支付
//本次交易金额:500.0,开始扣款...
//支付失败!余额不足!欠200.0元!
System.out.println(order.pay("WeChat"));
//欢迎使用支付宝
//本次交易金额:500.0,开始扣款...
//支付成功!支付金额:500.0余额剩余:0.0
System.out.println(order.pay("Ali"));
//欢迎使用现金支付
//本次交易金额:500.0,开始扣款...
//支付成功!支付金额:500.0余额剩余:500.0
System.out.println(order.pay());
}
2.3 总结
适用场景:
优点:
缺点:
命令模式是对命令的封装,每一个命令都是一个操作:请求的一方发出请求要求执行一个操作;接收的一方收到请求,并执行操作。命令模式解耦了请求方和接收方,请求方只需请求执行命令,不用关心命令是怎样被接收,怎样被操作以及是否被执行等。本质:解耦命令的请求与处理。
命令模式包含以下主要角色:
3.1 代码实现
下面以一个播放器的例子来进行代码实现:
// 播放器类
public class Player {
public void play() {
System.out.println("正常播放");
}
public void pause() {
System.out.println("暂停播放");
}
public void stop() {
System.out.println("停止播放");
}
}
// 命令接口
public interface IAction {
void excuse();
}
// 播放命令类
@AllArgsConstructor
public class PlayAction implements IAction {
private Player player;
@Override
public void excuse() {
this.player.play();
}
}
// 暂停命令类
@AllArgsConstructor
public class PauseAction implements IAction {
private Player player;
@Override
public void excuse() {
this.player.pause();
}
}
// 停止命令类
@AllArgsConstructor
public class StopAction implements IAction{
private Player player;
@Override
public void excuse() {
this.player.stop();
}
}
// 控制器
public class Controller {
public void excuse(IAction action) {
action.excuse();
}
}
// 测试方法
public static void main(String[] args) {
// 正常播放
new Controller().excuse(new PlayAction(new Player()));
// 暂停播放
new Controller().excuse(new PauseAction(new Player()));
// 停止播放
new Controller().excuse(new StopAction(new Player()));
}
3.2 总结
适用场景:
优点:
缺点:
职责链模式是将链中每一个节点看作是一个对象,每个节点处理的请求均不同,且内部自动维护一个下一节点对象。当一个请求从链式的首端发出时,会沿着链的路径依次传递给每一个节点对象,直至有对象处理这个请求为止。
职责链模式主要包含以下角色:
4.1 代码实现
下面以一个简单的登录校验流程来通过代码进行实现:
// 用户实体类
@Data
public class User {
private String username;
private String password;
private String role;
}
// handler抽象类
public abstract class Handler {
protected Handler next;
// 返回handler方便链式操作
public void next(Handler next) {
this.next = next;
}
// 流程开始的方法
public abstract void doHandler(User user);
}
// 校验用户名或者密码是否为空
public class ValidateHandler extends Handler {
@Override
public void doHandler(User user) {
if (StringUtils.isBlank(user.getUsername()) || StringUtils.isBlank(user.getPassword())) {
System.out.println("用户名或者密码为空!");
return;
}
System.out.println("校验通过");
next.doHandler(user);
}
}
// 登录校验,校验用户名是否匹配密码
public class LoginHandler extends Handler {
@Override
public void doHandler(User user) {
if (!"pyy52hz".equals(user.getUsername()) || !"123456".equals(user.getPassword())) {
System.out.println("用户名或者密码不正确!请检查!");
return;
}
user.setRole("admin");
System.out.println("登陆成功!角色为管理员!");
next.doHandler(user);
}
}
// 权限校验
public class AuthHandler extends Handler {
@Override
public void doHandler(User user) {
if (!"admin".equals(user.getRole())) {
System.out.println("无权限操作!");
return;
}
System.out.println("角色为管理员,可以进行下一步操作!");
}
}
// 登录流程
public class LoginService {
public void login(User user) {
Handler validateHandler = new ValidateHandler();
Handler loginHandler = new LoginHandler();
Handler authHandler = new AuthHandler();
validateHandler.next(loginHandler);
loginHandler.next(authHandler);
validateHandler.doHandler(user);
}
}
// 测试方法
public static void main(String[] args){
User user = new User();
//校验通过
//用户名或者密码不正确!请检查!
user.setUsername("pyy52hz");
user.setPassword("1234567");
LoginService loginService = new LoginService();
loginService.login(user);
//校验通过
//登陆成功!角色为管理员!
//角色为管理员,可以进行下一步操作!
user.setUsername("pyy52hz");
user.setPassword("123456");
loginService.login(user);
}
4.3 结合建造者模式
与基础版本区别主要是Handler类中新增一个Builder的内部类,以及流程类里改用链式写法,具体如下:
// handler抽象类
public abstract class Handler {
protected Handler next;
// 返回handler方便链式操作
public Handler next(Handler next) {
this.next = next;
return next;
}
// 流程开始的方法
public abstract void doHandler(User user);
static class Builder {
private Handler head;
private Handler tail;
public Builder addHandler(Handler handler) {
if (this.head == null) {
this.head = this.tail = handler;
return this;
}
this.tail.next(handler);
this.tail = handler;
return this;
}
public Handler build() {
return this.head;
}
}
}
public class LoginService {
public void login(User user) {
Handler.Builder builder = new Handler.Builder();
builder.addHandler(new ValidateHandler())
.addHandler(new LoginHandler())
.addHandler(new AuthHandler());
builder.build().doHandler(user);
}
}
4.4 总结
适用场景:
优点:
缺点:
状态模式也称为状态机模式(State Machine Pattern),是允许对象在内部状态发生改变时改变它的行为,对象看起来好像修改了它的类。
状态模式包含以下主要角色:
5.1 代码实现
// 电梯状态
public abstract class LiftState {
protected Context context;
public abstract void open();
public abstract void close();
public abstract void run();
public abstract void stop();
}
// 开门状态
public class OpenState extends LiftState {
@Override
public void open() {
System.out.println("电梯门打开了");
}
@Override
public void close() {
super.context.setLiftState(Context.CLOSE_STATE);
super.context.close();
}
@Override
public void run() {
}
@Override
public void stop() {
}
}
// 关门状态
public class CloseState extends LiftState {
@Override
public void open() {
super.context.setLiftState(Context.OPEN_STATE);
super.context.open();
}
@Override
public void close() {
System.out.println("电梯门关闭了!");
}
@Override
public void run() {
super.context.setLiftState(Context.RUN_STATE);
super.context.run();
}
@Override
public void stop() {
super.context.setLiftState(Context.STOP_STATE);
super.context.stop();
}
}
// 运行状态
public class RunState extends LiftState {
@Override
public void open() {
}
@Override
public void close() {
}
@Override
public void run() {
System.out.println("电梯正在运行...");
}
@Override
public void stop() {
super.context.setLiftState(Context.STOP_STATE);
super.context.stop();
}
}
// 停止状态
public class StopState extends LiftState {
@Override
public void open() {
super.context.setLiftState(Context.OPEN_STATE);
super.context.open();
}
@Override
public void close() {
super.context.setLiftState(Context.CLOSE_STATE);
super.context.close();
}
@Override
public void run() {
super.context.setLiftState(Context.RUN_STATE);
super.context.run();
}
@Override
public void stop() {
System.out.println("电梯停止了!");
}
}
// 上下文
public class Context {
private LiftState liftState;
public static final LiftState OPEN_STATE = new OpenState();
public static final LiftState CLOSE_STATE = new CloseState();
public static final LiftState RUN_STATE = new RunState();
public static final LiftState STOP_STATE = new StopState();
public void setLiftState(LiftState liftState) {
this.liftState = liftState;
this.liftState.setContext(this);
}
public void open() {
this.liftState.open();
}
public void close() {
this.liftState.close();
}
public void run() {
this.liftState.run();
}
public void stop() {
this.liftState.stop();
}
}
// 测试
public static void main(String[] args){
Context context = new Context();
context.setLiftState(new CloseState());
//电梯门打开了
//电梯门关闭了!
//电梯正在运行...
//电梯停止了!
context.open();
context.close();
context.run();
context.stop();
}
5.2 spring的状态机
// Todo
5.3 总结
适用场景:
优点:
缺点:
观察者模式,又叫发布-订阅(Publish/Subscribe)模式,模型-视图(Model/View)模式,源-监听器(Source/Listener)模式或从属者(Dependents)模式。定义一种一对多的依赖关系,一个主题对象可被多个观察者同时监听,使得每当主题对象状态变化时,所有依赖于它的对象都会得到通知并被自动更新。
6.1 代码实现
通过一个微信用户(观察者)订阅公众号(被观察者)接收公众号推送消息的例子来进行简单的代码实现:
// 抽象观察者接口
public interface Observer {
void update(String message);
}
// 微信用户类 具体的观察者
@AllArgsConstructor
public class WeixinUser implements Observer {
private String name;
@Override
public void update(String message) {
System.out.println(name + "接收到了消息(观察到了):" + message);
}
}
// 被观察者接口
public interface Observable {
// 新增用户(新增观察者)
void add(Observer observer);
// 移除用户,或者说用户取消订阅(移除观察者)
void del(Observer observer);
// 发布 推送消息
void notify(String message);
}
// 具体的被观察者(公众号)
public class Subject implements Observable {
// 观察者列表(订阅用户)
private List list = new ArrayList<>();
@Override
public void add(Observer observer) {
list.add(observer);
}
@Override
public void del(Observer observer) {
list.remove(observer);
}
// 给每一个观察者(订阅者)推送消息
@Override
public void notify(String message) {
list.forEach(observer -> observer.update(message));
}
}
// 测试
public static void main(String[] args){
Observable o = new Subject();
WeixinUser user1 = new WeixinUser("张三");
WeixinUser user2 = new WeixinUser("李四");
WeixinUser user3 = new WeixinUser("王五");
o.add(user1);
o.add(user2);
o.add(user3);
o.notify("薛之谦演唱会要来到广州啦!");
// 运行结果
// 张三接收到了消息(观察到了):薛之谦演唱会要来到广州啦!
// 李四接收到了消息(观察到了):薛之谦演唱会要来到广州啦!
// 王五接收到了消息(观察到了):薛之谦演唱会要来到广州啦!
}
6.2 JDK实现
在 Java 中,通过java.util.Observable类和 java.util.Observer接口定义了观察者模式,只要实现它们的子类就可以编写观察者模式实例。
6.2.1 Observable类
Observable类是抽象目标类(被观察者),它有一个Vector集合成员变量,用于保存所有要通知的观察者对象,下面是它最重要的 3 个方法:
void addObserver(Observer o) 方法:用于将新的观察者对象添加到集合中。
void notifyObservers(Object arg) 方法:调用集合中的所有观察者对象的update方法,通知它们数据发生改变。通常越晚加入集合的观察者越先得到通知。
void setChange() 方法:用来设置一个boolean类型的内部标志,注明目标对象发生了变化。当它为true时,notifyObservers() 才会通知观察者。
6.2.2 Observer 接口
Observer 接口是抽象观察者,它监视目标对象的变化,当目标对象发生变化时,观察者得到通知,并调用 update 方法,进行相应的工作。
6.2.3 代码实现
下面还是通过微信用户订阅公众号的例子进行代码实现,方便对比他们之间的区别:
// 具体的被观察者(公众号)
@Data
@AllArgsConstructor
public class Subject extends Observable {
// 公众号的名字
private String name;
// 公众号发布消息
public void notifyMessage(String message) {
System.out.println(this.name + "公众号发布消息:" + message + "请关注用户留意接收!");
super.setChanged();
super.notifyObservers(message);
}
}
@AllArgsConstructor
public class WeixinUser implements Observer {
private String name;
/**
* @param o 被观察者
* @param arg 被观察者带过来的参数,此例子中是公众号发布的消息
*/
@Override
public void update(Observable o, Object arg) {
System.out.println(name + "关注了公众号(被观察者):" + ((Subject)o).getName() + ",接收到消息:" + arg);
}
}
// 测试
public static void main(String[] args){
WeixinUser user1 = new WeixinUser("张三");
WeixinUser user2 = new WeixinUser("李四");
WeixinUser user3 = new WeixinUser("王五");
Subject subject = new Subject("演唱会消息发布");
subject.addObserver(user1);
subject.addObserver(user2);
subject.addObserver(user3);
subject.notifyMessage("薛之谦演唱会要来到广州啦!");
// 返回结果
// 演唱会消息发布公众号发布消息:薛之谦演唱会要来到广州啦!请关注用户留意接收!
// 王五关注了公众号(被观察者):演唱会消息发布,接收到消息:薛之谦演唱会要来到广州啦!
// 李四关注了公众号(被观察者):演唱会消息发布,接收到消息:薛之谦演唱会要来到广州啦!
// 张三关注了公众号(被观察者):演唱会消息发布,接收到消息:薛之谦演唱会要来到广州啦!
}
6.3 Google的Guava实现
EventBus 术语 | 解释 | 备注 |
事件(消息) | 可以向事件总线(EventBus)发布的对象 | 通常是一个类,不同的消息事件用不同的类来代替,消息内容就是类里面的属性 |
订阅 | 向事件总线注册监听者,以接受事件的行为 | EventBus.register(Object),参数就是监听者 |
监听者 | 提供一个处理方法,希望接受和处理事件的对象 | 通常也是一个类,里面有消息的处理方法 |
处理方法 | 监听者提供的公共方法,事件总线使用该方法向监听者发送事件;该方法应使用 Subscribe 注解 | 监听者里面添加一个 Subscribe 注解的方法,就可以认为是消息的处理方法 |
发布消息 | 通过事件总线向所有匹配的监听者提供事件 | EventBus.post(Object) |
@AllArgsConstructor
public class WeixinUser {
private String name;
@Subscribe
public void getMessage(Object arg) {
System.out.println(this.name + "接收到消息:" + arg);
}
// 测试
public static void main(String[] args){
// 消息总线
EventBus eventBus = new EventBus();
eventBus.register(new WeixinUser("张三"));
eventBus.register(new WeixinUser("李四"));
eventBus.post("薛之谦演唱会要来到广州啦!");
// 返回结果
// 张三接收到消息:薛之谦演唱会要来到广州啦!
// 李四接收到消息:薛之谦演唱会要来到广州啦!
}
}
6.4 总结
适用场景:
多层级嵌套使用,形成一种链式触发机制,使得事件具备跨域(跨越两种观察者类型)通知。
优点:
缺点:
中介者模式又称为调解者模式或调停者模式。用一个中介对象封装一系列的对象交互,中介者使各对象不需要显示地相互作用,从而使其耦合松散,而且可以独立地改变它们之间的交互。
核心:通过中介者解耦系统各层次对象的直接耦合,层次对象的对外依赖通信统统交由中介者转发。
中介者模式包含以下主要角色:
7.1 代码实现
通过一个租房例子简单实现下逻辑,房主通过中介公司发布自己的房子的信息,而租客则需要通过中介公司获取到房子的信息:
// 抽象同事类
@AllArgsConstructor
public class Person {
protected String name;
protected MediatorCompany mediatorCompany;
}
// 房主
public class HouseOwner extends Person {
public HouseOwner(String name, MediatorCompany mediatorCompany) {
super(name, mediatorCompany);
}
// 联络方法
public void connection(String message) {
mediatorCompany.connection(this, message);
}
// 获取消息
public void getMessage(String message) {
System.out.println("房主" + name + "获取到的信息:" + message);
}
}
// 租客
public class Tenant extends Person {
public Tenant(String name, MediatorCompany mediatorCompany) {
super(name, mediatorCompany);
}
public void connection(String message) {
mediatorCompany.connection(this, message);
}
public void getMessage(String message) {
System.out.println("租客" + name + "获取到的信息:" + message);
}
}
// 中介公司(中介者)
@Data
public class MediatorCompany {
private HouseOwner houseOwner;
private Tenant tenant;
public void connection(Person person, String message) {
// 房主需要通过中介获取租客信息
if (person.equals(houseOwner)) {
this.tenant.getMessage(message);
} else { // 反之租客通过中介获取房主信息
this.houseOwner.getMessage(message);
}
}
}
// 测试
public static void main(String[] args){
// 先创建三个角色,中介公司,房主,租客
MediatorCompany mediatorCompany = new MediatorCompany();
// 房主和租客都在同一家中介公司
HouseOwner houseOwner = new HouseOwner("张三", mediatorCompany);
Tenant tenant = new Tenant("李四", mediatorCompany);
// 中介公司获取房主和租客的信息
mediatorCompany.setHouseOwner(houseOwner);
mediatorCompany.setTenant(tenant);
// 房主和租客都在这家中介公司发布消息,获取到对应的消息
tenant.connection(tenant.name + "想租一房一厅!");
houseOwner.connection(houseOwner.name + "这里有!来看看呗!");
// 测试结果
// 房主张三获取到的信息:李四想租一房一厅!
// 租客李四获取到的信息:张三这里有!来看看呗!
}
7.2 总结
适用场景:
优点:
缺点:
迭代器模式又称为游标模式(Cursor Pattern),它提供一种顺序访问集合/容器对象元素的方法,而又无须暴露结合内部表示。
本质:抽离集合对象迭代行为到迭代器中,提供一致访问接口。
迭代器模式主要包含以下角色:
8.1 代码实现
// 迭代器接口
public interface Iterator {
Boolean hasNext();
T next();
}
// 迭代器接口实现类
public class IteratorImpl implements Iterator {
private List list;
private Integer cursor;
private T element;
public IteratorImpl(List list) {
this.list = list;
}
@Override
public Boolean hasNext() {
return cursor < list.size();
}
@Override
public T next() {
element = list.get(cursor);
cursor++;
return element;
}
}
// 容器接口
public interface Aggregate {
void add(T t);
void remove(T t);
Iterator iterator();
}
// 容器接口实现类
public class AggregateImpl implements Aggregate {
private List list = new ArrayList<>();
@Override
public void add(T t) {
list.add(t);
}
@Override
public void remove(T t) {
list.remove(t);
}
@Override
public Iterator iterator() {
return new IteratorImpl<>(list);
}
}
PS:具体测试的话可以自己写一个集合测试一下即可
8.2 总结
适用场景:
优点:
缺点:
访问者模式是一种将数据结构与数据操作分离的设计模式。是指封装一些作用于某种数据结构中的各元素的操作。
特征:可以在不改变数据结构的前提下定义作用于这些元素的新的操作。
访问者模式包含以下主要角色:
9.1 代码实现
// 访问者接口
public interface IVisitor {
void visit(Engineer engineer);
void visit(Pm pm);
}
// 具体的访问者类,访问者角色(CEO)
public class CeoVisitor implements IVisitor {
@Override
public void visit(Engineer engineer) {
System.out.println(engineer.getName() + "KPI为:" + engineer.getKpi());
}
@Override
public void visit(Pm pm) {
System.out.println(pm.getName() + "KPI为:" + pm.getKpi());
}
}
// 具体的访问者类,访问者角色(CTO)
public class CtoVisitor implements IVisitor {
@Override
public void visit(Engineer engineer) {
System.out.println(engineer.getName() + "工作内容:" + engineer.getCodeLine() + "行代码");
}
@Override
public void visit(Pm pm) {
System.out.println(pm.getName() + "工作内容:" + pm.getProject() + "个项目");
}
}
@Data
// 抽象元素(员工)
public abstract class Employee {
private String name;
private Integer kpi;
public Employee(String name) {
this.name = name;
this.kpi = new Random().nextInt(10);
}
public abstract void accept(IVisitor visitor);
}
// 具体元素(程序员)
public class Engineer extends Employee {
public Engineer(String name) {
super(name);
}
@Override
public void accept(IVisitor visitor) {
visitor.visit(this);
}
public Integer getCodeLine() {
return new Random().nextInt(10000);
}
}
// 具体元素(项目经理)
public class Pm extends Employee {
public Pm(String name) {
super(name);
}
@Override
public void accept(IVisitor visitor) {
visitor.visit(this);
}
public Integer getProject() {
return new Random().nextInt(10);
}
}
@AllArgsConstructor
public class Report {
private List employeeList;
public void showReport(IVisitor visitor) {
for (Employee employee : employeeList) {
employee.accept(visitor);
}
}
}
// 测试
public static void main(String[] args){
List employeeList = new ArrayList<>();
employeeList.add(new Engineer("工程师A"));
employeeList.add(new Engineer("工程师B"));
employeeList.add(new Engineer("项目经理A"));
employeeList.add(new Engineer("工程师C"));
employeeList.add(new Engineer("工程师D"));
employeeList.add(new Engineer("项目经理B"));
Report report = new Report(employeeList);
System.out.println("=============CEO==============");
report.showReport(new CeoVisitor());
System.out.println("=============CTO==============");
report.showReport(new CtoVisitor());
// =============CEO==============
// 工程师AKPI为:2
// 工程师BKPI为:4
// 项目经理AKPI为:4
// 工程师CKPI为:2
// 工程师DKPI为:0
// 项目经理BKPI为:0
// =============CTO==============
// 工程师A工作内容:5811行代码
// 工程师B工作内容:9930行代码
// 项目经理A工作内容:2163行代码
// 工程师C工作内容:4591行代码
// 工程师D工作内容:333行代码
// 项目经理B工作内容:3940行代码
}
9.2 伪动态双分派
访问者模式用到了一种伪动态双分派的技术。
9.2.1 分派
变量被声明时的类型叫做变量的静态类型,有些人又把静态类型叫做明显类型;而变量所引用的对象的真实类型又叫做变量的实际类型。比如Map map = new HashMap() ,map变量的静态类型是Map,实际类型是 HashMap 。根据对象的类型而对方法进行的选择,就是分派(Dispatch),分派(Dispatch)又分为两种,即静态分派和动态分派。
9.2.2 伪动态双分派
所谓双分派技术就是在选择一个方法的时候,不仅仅要根据消息接收者(receiver)的运行时区别,还要根据参数的运行时区别。
在上面代码中,客户端将IVisitor接口做为参数传递给Employee抽象类的变量调用的方法,这里完成第一次分派,这里是方法重写,所以是动态分派,也就是执行实际类型中的方法,同时也将自己this作为参数传递进去,这里就完成了第二次分派 ,这里的IVisitor接口中有多个重载的方法,而传递进行的是this,就是具体的实际类型的对象。
双分派实现动态绑定的本质,就是在重载方法委派的前面加上了继承体系中覆盖的环节,由于覆盖是动态的,所以重载就是动态的了。
9.3 总结
适用场景:
优点:
缺点:
备忘录模式又称为快照模式(Snapshot Pattern)或令牌模式(Token Pattern),是指在不破坏封装的前提下,捕获一个对象的内部状态,并在对象之外保存这个状态,这样以后就可将该对象恢复到原先保存的状态。
特征:“后悔药”
备忘录模式的主要角色如下:
备忘录有两个等效的接口:
10.1 “白箱”备忘录模式
下面就以游戏打怪为简单的例子进行代码实现(下面“黑箱”同这个例子):
备忘录角色对任何对象都提供一个宽接口,备忘录角色的内部所存储的状态就对所有对象公开。
// 游戏角色类
@Data
public class GameRole {
private Integer vit; // 生命力
private Integer atk; // 攻击力
private Integer def; // 防御力
// 初始化状态
public void init() {
this.vit = 100;
this.atk = 100;
this.def = 100;
}
// 战斗到0
public void fight() {
this.vit = 0;
this.atk = 0;
this.def = 0;
}
// 保存角色状态
public RoleStateMemento saveState() {
return new RoleStateMemento(this.vit, this.atk, this.def);
}
// 回复角色状态
public void recoverState(RoleStateMemento roleStateMemento) {
this.vit = roleStateMemento.getVit();
this.atk = roleStateMemento.getAtk();
this.def = roleStateMemento.getDef();
}
// 展示状态
public void showState() {
System.out.println("角色生命力:" + this.vit);
System.out.println("角色攻击力:" + this.atk);
System.out.println("角色防御力:" + this.def);
}
}
// 游戏状态存储类(备忘录类)
@Data
@AllArgsConstructor
public class RoleStateMemento {
private Integer vit; // 生命力
private Integer atk; // 攻击力
private Integer def; // 防御力
}
// 角色状态管理者类
@Data
public class RoleStateCaretaker {
private RoleStateMemento roleStateMemento;
}
// 测试结果
public static void main(String[] args){
System.out.println("===========打boss前状态===========");
GameRole gameRole = new GameRole();
gameRole.init();
gameRole.showState();
// 保存进度
RoleStateCaretaker roleStateCaretaker = new RoleStateCaretaker();
roleStateCaretaker.setRoleStateMemento(gameRole.saveState());
System.out.println("===========打boss后状态===========");
gameRole.fight();
gameRole.showState();
System.out.println("===========恢复状态===========");
gameRole.recoverState(roleStateCaretaker.getRoleStateMemento());
gameRole.showState();
// ===========打boss前状态===========
// 角色生命力:100
// 角色攻击力:100
// 角色防御力:100
// ===========打boss后状态===========
// 角色生命力:0
// 角色攻击力:0
// 角色防御力:0
// ===========恢复状态===========
// 角色生命力:100
// 角色攻击力:100
// 角色防御力:100
}
“白箱”备忘录模式是破坏封装性的,但是通过程序员自律,同样可以在一定程度上实现大部分的用意。
10.2 “黑箱”备忘录模式
备忘录角色对发起人对象提供了一个宽接口,而为其他对象提供一个窄接口,在Java语言中,实现双重接口的办法就是将备忘录类设计成发起人类的内部成员类。
将RoleStateMemento设为GameRole的内部类,从而将RoleStateMemento对象封装在GameRole 里面;在外面提供一个标识接口Memento给RoleStateCaretaker及其他对象使用。这样GameRole类看到的是RoleStateMemento所有的接口,而RoleStateCaretaker及其他对象看到的仅仅是标识接口Memento所暴露出来的接口,从而维护了封装型。
// 窄接口,标识接口
public interface Memento {
}
// 角色状态管理者类
@Data
public class RoleStateCaretaker {
private Memento memento;
}
// 游戏角色类
@Data
public class GameRole {
private Integer vit; // 生命力
private Integer atk; // 攻击力
private Integer def; // 防御力
// 初始化状态
public void init() {
this.vit = 100;
this.atk = 100;
this.def = 100;
}
// 战斗到0
public void fight() {
this.vit = 0;
this.atk = 0;
this.def = 0;
}
// 保存角色状态
public RoleStateMemento saveState() {
return new RoleStateMemento(this.vit, this.atk, this.def);
}
// 回复角色状态
public void recoverState(Memento memento) {
RoleStateMemento roleStateMemento = (RoleStateMemento) memento;
this.vit = roleStateMemento.getVit();
this.atk = roleStateMemento.getAtk();
this.def = roleStateMemento.getDef();
}
// 展示状态
public void showState() {
System.out.println("角色生命力:" + this.vit);
System.out.println("角色攻击力:" + this.atk);
System.out.println("角色防御力:" + this.def);
}
// 备忘录内部类
@Data
@AllArgsConstructor
private class RoleStateMemento implements Memento {
private Integer vit; // 生命力
private Integer atk; // 攻击力
private Integer def; // 防御力
}
}
// 测试结果
public static void main(String[] args){
System.out.println("===========打boss前状态===========");
GameRole gameRole = new GameRole();
gameRole.init();
gameRole.showState();
// 保存进度
RoleStateCaretaker roleStateCaretaker = new RoleStateCaretaker();
roleStateCaretaker.setMemento(gameRole.saveState());
System.out.println("===========打boss后状态===========");
gameRole.fight();
gameRole.showState();
System.out.println("===========恢复状态===========");
gameRole.recoverState(roleStateCaretaker.getMemento());
gameRole.showState();
// ===========打boss前状态===========
// 角色生命力:100
// 角色攻击力:100
// 角色防御力:100
// ===========打boss后状态===========
// 角色生命力:0
// 角色攻击力:0
// 角色防御力:0
// ===========恢复状态===========
// 角色生命力:100
// 角色攻击力:100
// 角色防御力:100
}
10.3 总结
适用场景:
优点:
缺点:
解释器模式给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。
特征:为了解释一种语言,而为语言创建的解释器。
解释器模式包含以下主要角色:
11.1 代码实现
下面以简单的加减乘除为例子实现解释器模式:
// 抽象角色 定义解释器
public interface Expression {
int interpret();
}
@AllArgsConstructor
public class NumberTerminal implements Expression {
private int number;
@Override
public int interpret() {
return this.number;
}
}
// 非终结表达式(抽象类)
@AllArgsConstructor
public abstract class NonTerminal implements Expression {
protected Expression left;
protected Expression right;
}
// 非终结表达式(加法)
public class PlusNonTerminal extends NonTerminal implements Expression {
public PlusNonTerminal(Expression left, Expression right) {
super(left, right);
}
@Override
public int interpret() {
return left.interpret() + right.interpret();
}
}
// 非终结表达式(减法)
public class MinusNonTerminal extends NonTerminal implements Expression {
public MinusNonTerminal(Expression left, Expression right) {
super(left, right);
}
@Override
public int interpret() {
return left.interpret() - right.interpret();
}
}
// 非终结表达式(乘法)
public class MclNonTerminal extends NonTerminal implements Expression {
public MclNonTerminal(Expression left, Expression right) {
super(left, right);
}
@Override
public int interpret() {
return left.interpret() * right.interpret();
}
}
// 非终结表达式(除法)
public class DivisionNonTerminal extends NonTerminal implements Expression {
public DivisionNonTerminal(Expression left, Expression right) {
super(left, right);
}
@Override
public int interpret() {
return left.interpret() / right.interpret();
}
}
// 计算器类(实现运算逻辑)
public class Cal {
private Expression left;
private Expression right;
private Integer result;
public Cal(String expression) {
this.parse(expression);
}
private Integer parse(String expression) {
// 获取表达式元素
String [] elements = expression.split(" ");
for (int i = 0; i < elements.length; i++) {
String element = elements[i];
// 判断是否是运算符号
if (OperatorUtils.isOperator(element)) {
// 运算符号的右边就是右终结符
right = new NumberTerminal(Integer.valueOf(elements[++i]));
//计算结果
result = OperatorUtils.getNonTerminal(left, right, element).interpret();
// 计算结果重新成为左终结符
left = new NumberTerminal(result);
} else {
left = new NumberTerminal(Integer.valueOf(element));
}
}
return result;
}
public Integer cal() {
return result;
}
}
// 操作工具类
public class OperatorUtils {
// 判断是不是非终结符
public static boolean isOperator(String symbol) {
return symbol.equals("+") || symbol.equals("-") || symbol.equals("*")|| symbol.equals("/");
}
// 简单工厂
public static NonTerminal getNonTerminal(Expression left, Expression right, String symbol) {
if (symbol.equals("+")) {
return new PlusNonTerminal(left, right);
} else if (symbol.equals("-")) {
return new MinusNonTerminal(left, right);
} else if (symbol.equals("*")) {
return new MclNonTerminal(left, right);
} else if (symbol.equals("/")) {
return new DivisionNonTerminal(left, right);
}
return null;
}
}
// 测试
// PS:此处进行的逻辑仅仅实现从左到右运算,并没有先乘除后加减的逻辑
public static void main(String[] args) {
System.out.println(new Cal("10 + 20 - 40 * 60").cal()); // -600
System.out.println(new Cal("20 + 50 - 60 * 2").cal()); // 20
}
11.2 Spring中的解释器模式
public static void main(String[] args) {
ExpressionParser expressionParser = new SpelExpressionParser();
org.springframework.expression.Expression expression = expressionParser.parseExpression("10 + 20 + 30 * 4");
Integer value = expression.getValue(Integer.class);
System.out.println(value); // 150
expression = expressionParser.parseExpression("(10+20+30)*4");
value = expression.getValue(Integer.class);
System.out.println(value); // 240
}
可以看到Spring中解释器写的是比较完善的,不仅有先乘除后加减和先括号进行运算的日常计算规则,而且对于空格也并没有要求,仅需要写出完整的表达式即可运算出来。
11.3 总结
适用场景:
优点:
缺点: