一、创建型模式
1.单例模式(Singleton Pattern)
单例模式(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;
}
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 反序列化
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<Singleton> singletonClass = Singleton.class;
Constructor<Singleton> 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<Singleton> singletonClass = Singleton.class;
Constructor<Singleton> 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<String, Object> 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
}
}
不保证整个应用全局唯一,但保证线程内部全局唯一,以空间换时间,且线程安全。
public class ThreadLocalSingleton {
private ThreadLocalSingleton(){}
private static final ThreadLocal<ThreadLocalSingleton> 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.工厂方法模式(Factory Method)
简单工厂模式不是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 工厂方法模式
工厂方法模式是指定义一个创建对象的接口,让实现这个接口的类来决定实例化哪个类,工厂方法让类的实例化推迟到子类中进行。
- 抽象工厂(Abstract Factory):提供了创建产品的接口,调用者通过它访问具体工厂的工厂方法来创建产品。
- 具体工厂(ConcreteFactory):主要是实现抽象工厂中的抽象方法,完成具体产品的创建。
- 抽象产品(Product):定义了产品的规范,描述了产品的主要特性和功能。
- 具体产品(ConcreteProduct):实现了抽象产品角色所定义的接口,由具体工厂来创建,它和具体工厂之间一一对应。
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.抽象工厂模式(Abstract Factory)
抽象工厂模式是指提供一个创建一系列相关或相互依赖对象的接口,无须指定他们具体的类。
工厂方法模式中考虑的是一类产品的生产,如电脑厂只生产电脑,电话厂只生产电话,这种工厂只生产同种类的产品,同种类产品称为同等级产品,也就是说,工厂方法模式只考虑生产同等级的产品,但是现实生活中许多工厂都是综合型工厂,能生产多等级(种类)的产品,如上面说的电脑和电话,本质上他们都属于电器,那么他们就能在电器厂里生产出来,而抽象工厂模式就将考虑多等级产品的生产,将同一个具体工厂所生产的位于不同等级的一组产品称为一个产品族,如上图所示纵轴是产品等级,也就是同一类产品;横轴是产品族,也就是同一品牌的产品,同一品牌的产品产自同一个工厂。
抽象工厂模式的主要角色如下:
- 抽象工厂(Abstract Factory):提供了创建产品的接口,它包含多个创建产品的方法,可以创建多个不同等级的产品。
- 具体工厂(Concrete Factory):主要是实现抽象工厂中的多个抽象方法,完成具体产品的创建。
- 抽象产品(Product):定义了产品的规范,描述了产品的主要特性和功能,抽象工厂模式有多个抽象产品。
- 具体产品(ConcreteProduct):实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂之间是多对一的关系。
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 总结
适用场景:
- 客户端(应用层)不依赖于产品类实例如何被创建和实现等细节。
- 强调一系列相关的产品对象(属于同一产品族)一起使用创建对象需要大量重复的代码。
- 提供一个产品类的库,所有的产品以同样的接口出现,从而使客户端不依赖于具体实现。
优点:
缺点:
4.原型模式(Prototype)
原型模式是指原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。调用者不需要知道任何创建细节,不调用构造函数。
- 抽象原型类:规定了具体原型对象必须实现的的 clone() 方法。
- 具体原型类:实现抽象原型类的 clone() 方法,它是可被复制的对象。
- 访问类:使用具体原型类中的 clone() 方法来复制新的对象。
@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 总结
适用场景:
优点:
缺点:
5.建造者模式(Builder)
建造者模式是将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。用户只需指定需要建造的类型就可以获得对象,建造过程及细节不需要了解。
- 抽象建造者类(Builder):这个接口规定要实现复杂对象的那些部分的创建,并不涉及具体的部件对象的创建。
- 具体建造者类(ConcreteBuilder):实现 Builder 接口,完成复杂产品的各个部件的具体创建方法。在构造过程完成后,提供产品的实例。
- 产品类(Product):要创建的复杂对象。
- 指挥者类(Director):调用具体建造者来创建复杂对象的各个部分,在指导者中不涉及具体产品的信息,只负责保证对象各部分完整创建或按某种顺序创建。
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 总结
适用场景:
优点:
缺点:
- 产生多余的Builder对象。
- 产品内部发生变化,建造者都要修改,成本较大。
与工厂模式的区别:
- 建造者模式更注重方法的调用顺序,工厂模式更注重创建对象。
- 创建对象的力度不同,建造者模式创建复杂的对象,由各种复杂的部件组成,工厂模式创建出来的都一样。
- 关注点不同,工厂模式只需要把对象创建出来就可以了,而建造者模式中不仅要创建出这个对象,还要知道这个对象由哪些部件组成。
- 建造者模式根据建造过程中的顺序不一样,最终的对象部件组成也不一样。
与抽象工厂模式的区别:
- 抽象工厂模式实现对产品族的创建,一个产品族是这样的一系列产品:具有不同分类维度的产品组合,采用抽象工厂模式则是不需要关心构建过程,只关心什么产品由什么工厂生产即可。
- 建造者模式则是要求按照指定的蓝图建造产品,它的主要目的是通过组装零配件而产生一个新产品。
- 建造者模式所有函数加到一起才能生成一个对象,抽象工厂一个函数生成一个对象
二、结构型模式
1.代理模式(Proxy Pattern)
代理模式是指为其他对象提供一种代理,以控制对这个对象的访问。代理对象在访问对象和目标对象之间起到中介作用。
Java中的代理按照代理类生成时机不同又分为静态代理和动态代理。静态代理代理类在编译期就生成,而动态代理代理类则是在Java运行时动态生成。动态代理又有JDK代理和CGLib代理两种。
代理(Proxy)模式分为三种角色:
- 抽象角色(Subject): 通过接口或抽象类声明真实角色和代理对象实现的业务方法。
- 真实角色(Real Subject): 实现了抽象角色中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象。
- 代理角色(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 TrainStation 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包 -->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>${cglib-version}</version>
</dependency>
// 代售点卖票(代理类)
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 总结
应用场景:
- 保护目标对象。
- 增强目标对象。
优点:
缺点:
两种动态代理的对比:
- JDK动态代理的特点:
- cglib的特点:
2.适配器模式(Adapter Class/Object)
适配器模式,它的功能是将一个类的接口变成客户端所期望的另一种接口,从而使原本因接口不匹配而导致无法在一起工作的两个类能够一起工作。适配器模式分为类适配器模式和对象适配器模式,前者类之间的耦合度比后者高,且要求程序员了解现有组件库中的相关组件的内部结构,所以应用相对较少些。
- 目标(Target)接口:当前系统业务所期待的接口,它可以是抽象类或接口。
- 适配者(Adaptee)类:它是被访问和适配的现存组件库中的组件接口。
- 适配器(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 总结
适用场景:
优点:
缺点:
3.装饰模式(Decorator Pattern)
装饰模式,是指在不改变原有对象的基础上,将功能附加到对象上,提供了比继承更有弹性的替代方案(扩展原有对象的功能)
- 抽象构件(Component)角色 :定义一个抽象接口以规范准备接收附加责任的对象。
- 具体构件(Concrete Component)角色 :实现抽象构件,通过装饰角色为其添加一些职责。
- 抽象装饰(Decorator)角色 : 继承或实现抽象构件,并包含具体构件的实例,可以通过其子类扩展具体构件的功能。
- 具体装饰(ConcreteDecorator)角色 :实现抽象装饰的相关方法,并给具体构件对象添加附加的责任。
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 总结
装饰器模式与代理模式对比:
- 装饰器模式就是一种特殊的代理模式。
- 装饰器模式强调自身的功能扩展,用自己说了算的透明扩展,可动态定制的扩展;代理模式强调代理过程的控制。
- 获取目标对象构建的地方不同,装饰者是从外界传递进来的,可以通过构造方法传递;静态代理是在代理类内部创建,以此来隐藏目标对象。
适用场景:
- 用于扩展一个类的功能或者给一个类添加附加职责。
- 动态的给一个对象添加功能,这些功能同样也可以再动态的撤销。
优点:
缺点:
- 会出现更多的代码,更多的类,增加程序的复杂性。
- 动态装饰时,多层装饰会更复杂。
4.桥接模式(Bridge Pattern)
桥接模式也称为桥梁模式、接口模式或者柄体(Handle and Body)模式,是将抽象部分与他的具体实现部分分离,使它们都可以独立地变化,通过组合的方式建立两个类之间的联系,而不是继承。
- 抽象化(Abstraction)角色 :定义抽象类,并包含一个对实现化对象的引用。
- 扩展抽象化(Refined Abstraction)角色 :是抽象化角色的子类,实现父类中的业务方法,并通过组合关系调用实现化角色中的业务方法。
- 实现化(Implementor)角色 :定义实现化角色的接口,供扩展抽象化角色调用。
- 具体实现化(Concrete Implementor)角色 :给出实现化角色接口的具体实现。
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 总结
适用场景:
优点:
缺点:
5.外观模式(Facade)
外观模式又称门面模式,提供了一个统一的接口,用来访问子系统中的一群接口。
外观(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 总结
适用场景:
- 对分层结构系统构建时,使用外观模式定义子系统中每层的入口点可以简化子系统之间的依赖关系。
- 当一个复杂系统的子系统很多时,外观模式可以为系统设计一个简单的接口供外界访问。
- 当客户端与多个子系统之间存在很大的联系时,引入外观模式可将它们分离,从而提高子系统的独立性和可移植性。
优点:
缺点:
6.组合模式(Composite Pattern)
组合模式也称为整体-部分(Part-Whole)模式,它的宗旨是通过将单个对象(叶子结点)和组合对象(树枝节点)用相同的接口进行表示。
- 组合模式主要包含三种角色:
抽象根节点(Component):定义系统各层次对象的共有方法和属性,可以预先定义一些默认行为和属性。 - 树枝节点(Composite):定义树枝节点的行为,存储子节点,组合树枝节点和叶子节点形成一个树形结构。
- 叶子节点(Leaf):叶子节点对象,其下再无分支,是系统层次遍历的最小单位。
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<MenuComponent> 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 总结
适用场景:
优点:
缺点:
- 限制类型时会较为复杂。
- 使设计变得更加抽象。
分类:
- 透明组合模式
- 安全组合模式
7.享元模式(Flyweight Pattern)
享元模式又称为轻量级模式,是对象池的一种实现,类似于线程池,线程池可以避免不停的创建和销毁多个对象,消耗性能。提供了减少对象数量从而改善应用所需的对象结构的方式。宗旨:共享细粒度对象,将多个对同一对象的访问集中起来。
享元(Flyweight )模式中存在以下两种状态:
享元模式的主要有以下角色:
- 抽象享元角色(Flyweight):通常是一个接口或抽象类,在抽象享元类中声明了具体享元类公共的方法,这些方法可以向外界提供享元对象的内部数据(内部状态),同时也可以通过这些方法来设置外部数据(外部状态)。
- 具体享元(Concrete Flyweight)角色 :它实现了抽象享元类,称为享元对象;在具体享元类中为内部状态提供了存储空间。通常我们可以结合单例模式来设计具体享元类,为每一个具体享元类提供唯一的享元对象。
- 非享元(Unsharable Flyweight)角色 :并不是所有的抽象享元类的子类都需要被共享,不能被共享的子类可设计为非共享具体享元类;当需要一个非共享具体享元类的对象时可以直接通过实例化创建。
- 享元工厂(Flyweight Factory)角色 :负责创建和管理享元角色。当客户对象请求一个享元对象时,享元工厂检査系统中是否存在符合要求的享元对象,如果存在则提供给客户;如果不存在的话,则创建一个新的享元对象。
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<String, ITicket> 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 总结
适用场景:
- 一个系统有大量相同或者相似的对象,造成内存的大量耗费。
- 对象的大部分状态都可以外部化,可以将这些外部状态传入对象中。
- 在使用享元模式时需要维护一个存储享元对象的享元池,而这需要耗费一定的系统资源,因此,应当在需要多次重复使用享元对象时才值得使用享元模式。
优点:
缺点:
三、行为型模式
1.模板方法模式(Template method pattern)
模板方法模式通常又叫模板模式,是指定义一个算法的骨架,并允许之类为其中的一个或者多个步骤提供实现。模板方法模式使得子类可以在不改变算法结构的情况下,重新定义算法的某些步骤。
模板方法(Template Method)模式包含以下主要角色:
- 抽象类(Abstract Class):负责给出一个算法的轮廓和骨架。它由一个模板方法和若干个基本方法构成。
- 具体子类(Concrete Class):实现抽象类中所定义的抽象方法和钩子方法,它们是一个顶级逻辑的组成步骤。
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 总结
适用场景:
优点:
- 利用模板方法将相同处理逻辑的代码放到抽象父类中,可以提高代码的复用性。
- 将不同的代码不同的子类中,通过对子类的扩展增加新的行为,提高代码的扩展性。
- 把不变的行为写在父类上,去除子类的重复代码,提供了一个很好的代码复用平台,符合开闭原则。
缺点:
2.策略模式(Strategy Pattern)
策略模式又叫政策模式(Policy Pattern),它是将定义的算法家族分别封装起来,让它们之间可以互相替换,从而让算法的变化不会影响到使用算法的用户。可以避免多重分支的if……else和switch语句。
- 抽象策略(Strategy)类:这是一个抽象角色,通常由一个接口或抽象类实现。此角色给出所有的具体策略类所需的接口。
- 具体策略(Concrete Strategy)类:实现了抽象策略定义的接口,提供具体的算法实现或行为。
- 环境(Context)类:持有一个策略类的引用,最终给客户端调用。
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<String, VipCard> 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<String, Payment> 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.命令模式(Command Pattern)
命令模式是对命令的封装,每一个命令都是一个操作:请求的一方发出请求要求执行一个操作;接收的一方收到请求,并执行操作。命令模式解耦了请求方和接收方,请求方只需请求执行命令,不用关心命令是怎样被接收,怎样被操作以及是否被执行等。本质:解耦命令的请求与处理。
命令模式包含以下主要角色:
- 抽象命令类(Command)角色: 定义命令的接口,声明执行的方法。
- 具体命令(Concrete Command)角色:具体的命令,实现命令接口;通常会持有接收者,并调用接收者的功能来完成命令要执行的操作。
- 实现者/接收者(Receiver)角色: 接收者,真正执行命令的对象。任何类都可能成为一个接收者,只要它能够实现命令要求实现的相应功能。
- 调用者/请求者(Invoker)角色: 要求命令对象执行请求,通常会持有命令对象,可以持有很多的命令对象。这个是客户端真正触发命令并要求命令执行相应操作的地方,也就是说相当于使用命令对象的入口。
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 总结
适用场景:
- 现实语义中具备“命令”的操作(如命令菜单,shell命令…)。
- 请求调用者和请求接收者需要解耦,使得调用者和接收者不直接交互。
- 需要抽象出等待执行的行为,比如撤销操作和恢复操作等。
- 需要支持命令宏(即命令组合操作)。
优点:
缺点:
4.职责链模式(chain of responsibility pattern)
职责链模式是将链中每一个节点看作是一个对象,每个节点处理的请求均不同,且内部自动维护一个下一节点对象。当一个请求从链式的首端发出时,会沿着链的路径依次传递给每一个节点对象,直至有对象处理这个请求为止。
职责链模式主要包含以下角色:
- 抽象处理者(Handler)角色:定义一个处理请求的接口,包含抽象处理方法和一个后继连接。
- 具体处理者(Concrete Handler)角色:实现抽象处理者的处理方法,判断能否处理本次请求,如果可以处理请求则处理,否则将该请求转给它的后继者。
- 客户类(Client)角色:创建处理链,并向链头的具体处理者对象提交请求,它不关心处理细节和请求的传递过程。
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<T> {
protected Handler next;
// 返回handler方便链式操作
public Handler next(Handler next) {
this.next = next;
return next;
}
// 流程开始的方法
public abstract void doHandler(User user);
static class Builder<T> {
private Handler<T> head;
private Handler<T> tail;
public Builder<T> addHandler(Handler<T> handler) {
if (this.head == null) {
this.head = this.tail = handler;
return this;
}
this.tail.next(handler);
this.tail = handler;
return this;
}
public Handler<T> 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 总结
适用场景:
- 多个对象可以处理同一请求,但具体由哪个对象处理则在运行时动态决定。
- 在不明确指定接收者的情况下,向多个对象中的一个提交一个请求。
- 可动态指定一组对象处理请求。
优点:
- 将请求与处理解耦。
- 请求处理者(节点对象)只需关注自己感兴趣的请求进行处理即可,对于不感兴趣的请求,直接转发给下一级节点对象。
- 具备链式传递处理请求功能,请求发送者无需知晓链路结构,只需等待请求处理结果。
- 链路结构灵活,可以通过改变链路结构动态地新增或删减责任。
- 易于扩展新的请求处理类(节点),符合开闭原则。
缺点:
5.状态模式(State Pattern)
状态模式也称为状态机模式(State Machine Pattern),是允许对象在内部状态发生改变时改变它的行为,对象看起来好像修改了它的类。
状态模式包含以下主要角色:
- 环境(Context)角色:也称为上下文,它定义了客户程序需要的接口,维护一个当前状态,并将与状态相关的操作委托给当前状态对象来处理。
- 抽象状态(State)角色:定义一个接口,用以封装环境对象中的特定状态所对应的行为。
- 具体状态(Concrete State)角色:实现抽象状态所对应的行为。
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();
}
// Todo
5.3 总结
适用场景:
优点:
- 结构清晰:将状态独立为类,消除了冗余的if…else或switch…case语句,使代码更加简洁,提高系统可维护性。
- 将状态转换显示化:通常的对象内部都是使用数值类型来定义状态,状态的切换是通过赋值进行表现,不够直观;而使用状态类,在切换状态时,是以不同的类进行表示,转换目的更加明确。
- 状态类职责明确且具备扩展性。
缺点:
- 类膨胀:如果一个事物具备很多状态,则会造成状态类太多。
- 状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱。
- 状态模式对开闭原则的支持并不太好,对于可以切换状态的状态模式,增加新的状态类需要修改那些负责状态转换的源代码,否则无法切换到新增状态,而且修改某个状态类的行为也需修改对应类的源代码。
6.观察者模式(Observer Mode)
观察者模式,又叫发布–订阅(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<Observer> 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.中介者模式(mediator pattern)
中介者模式又称为调解者模式或调停者模式。用一个中介对象封装一系列的对象交互,中介者使各对象不需要显示地相互作用,从而使其耦合松散,而且可以独立地改变它们之间的交互。
核心:通过中介者解耦系统各层次对象的直接耦合,层次对象的对外依赖通信统统交由中介者转发。
中介者模式包含以下主要角色:
- 抽象中介者(Mediator)角色:它是中介者的接口,提供了同事对象注册与转发同事对象信息的抽象方法。
- 具体中介者(ConcreteMediator)角色:实现中介者接口,定义一个 List 来管理同事对象,协调各个同事角色之间的交互关系,因此它依赖于同事角色。
- 抽象同事类(Colleague)角色:定义同事类的接口,保存中介者对象,提供同事对象交互的抽象方法,实现所有相互影响的同事类的公共功能。
- 具体同事类(Concrete Colleague)角色:是抽象同事类的实现者,当需要与其他同事对象交互时,由中介者对象负责后续的交互。
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 总结
适用场景:
优点:
- 减少类间的依赖,将多对多依赖转化成了一对多,降低了类间耦合。
- 类间各司其职,符合迪米特法则。
缺点:
- 中介者模式中将原本多个对象直接的相互依赖变成了中介者和多个同事类的依赖关系。当同事类越多时,中介者就会越臃肿,变得复杂且难以维护。
8.迭代器模式(Iterator Pattern)
迭代器模式又称为游标模式(Cursor Pattern),它提供一种顺序访问集合/容器对象元素的方法,而又无须暴露结合内部表示。
迭代器模式主要包含以下角色:
- 抽象聚合(Aggregate)角色:定义存储、添加、删除聚合元素以及创建迭代器对象的接口。
- 具体聚合(ConcreteAggregate)角色:实现抽象聚合类,返回一个具体迭代器的实例。
- 抽象迭代器(Iterator)角色:定义访问和遍历聚合元素的接口,通常包含 hasNext()、next() 等方法。
- 具体迭代器(Concretelterator)角色:实现抽象迭代器接口中所定义的方法,完成对聚合对象的遍历,记录遍历的当前位置。
8.1 代码实现
// 迭代器接口
public interface Iterator<T> {
Boolean hasNext();
T next();
}
// 迭代器接口实现类
public class IteratorImpl<T> implements Iterator<T> {
private List<T> list;
private Integer cursor;
private T element;
public IteratorImpl(List<T> 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<T> {
void add(T t);
void remove(T t);
Iterator<T> iterator();
}
// 容器接口实现类
public class AggregateImpl<T> implements Aggregate<T> {
private List<T> list = new ArrayList<>();
@Override
public void add(T t) {
list.add(t);
}
@Override
public void remove(T t) {
list.remove(t);
}
@Override
public Iterator<T> iterator() {
return new IteratorImpl<>(list);
}
}
8.2 总结
适用场景:
优点:
- 多态迭代:为不同的聚合结构提供一致的遍历接口,即一个迭代接口可以访问不同的聚集对象。
- 简化集合对象接口:迭代器模式将集合对象本身应该提供的元素迭代接口抽取到了迭代器中,使集合对象无须关心具体迭代行为。
- 元素迭代功能多样化:每个集合对象都可以提供一个或多个不同的迭代器,使的同种元素聚合结构可以有不同的迭代行为。
- 解耦迭代与集合:迭代器模式封装了具体的迭代算法,迭代算法的变化,不会影响到集合对象的架构。
缺点:
9.访问者模式(Visitor Pattern)
访问者模式是一种将数据结构与数据操作分离的设计模式。是指封装一些作用于某种数据结构中的各元素的操作。
特征:可以在不改变数据结构的前提下定义作用于这些元素的新的操作。
访问者模式包含以下主要角色:
- 抽象访问者(Visitor)角色:定义了对每一个元素 (Element) 访问的行为,它的参数就是可以访问的元素,它的方法个数理论上来讲与元素类个数(Element的实现类个数)是一样的,从这点不难看出,访问者模式要求元素类的个数不能改变。
- 具体访问者(ConcreteVisitor)角色:给出对每一个元素类访问时所产生的具体行为。
- 抽象元素(Element)角色:定义了一个接受访问者的方法( accept ),其意义是指,每一个元素都要可以被访问者访问。
- 具体元素(ConcreteElement)角色: 提供接受访问方法的具体实现,而这个具体的实现,通常情况下是使用访问者提供的访问该元素类的方法。
- 对象结构(Object Structure)角色:定义当中所提到的对象结构,对象结构是一个抽象表述,具体点可以理解为一个具有容器性质或者复合对象特性的类,它会含有一组元素( Element ),并且可以迭代这些元素,供访问者访问。
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<Employee> employeeList;
public void showReport(IVisitor visitor) {
for (Employee employee : employeeList) {
employee.accept(visitor);
}
}
}
// 测试
public static void main(String[] args){
List<Employee> employeeList = new ArrayList<>();
employeeList.add(new Engineer("工程师A"));
employeeList.add(new Engineer("工程师B"));
employeeList.add(new Pm("项目经理A"));
employeeList.add(new Engineer("工程师C"));
employeeList.add(new Engineer("工程师D"));
employeeList.add(new Pm("项目经理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工作内容:7个项目
// 工程师C工作内容:4591行代码
// 工程师D工作内容:333行代码
// 项目经理B工作内容:4个项目
}
9.2 伪动态双分派
9.2.1 分派
变量被声明时的类型叫做变量的静态类型,有些人又把静态类型叫做明显类型;而变量所引用的对象的真实类型又叫做变量的实际类型。比如Map map = new HashMap() ,map变量的静态类型是Map,实际类型是 HashMap 。根据对象的类型而对方法进行的选择,就是分派(Dispatch),分派(Dispatch)又分为两种,即静态分派和动态分派。
- 静态分派(Static Dispatch) 发生在编译时期,分派根据静态类型信息发生。静态分派对于我们来说并不陌生,方法重载就是静态分派。
- 动态分派(Dynamic Dispatch) 发生在运行时期,动态分派动态地置换掉某个方法。Java通过方法的重写支持动态分派。
9.2.2 伪动态双分派
所谓双分派技术就是在选择一个方法的时候,不仅仅要根据消息接收者(receiver)的运行时区别,还要根据参数的运行时区别。
在上面代码中,客户端将IVisitor接口做为参数传递给Employee抽象类的变量调用的方法,这里完成第一次分派,这里是方法重写,所以是动态分派,也就是执行实际类型中的方法,同时也将自己this作为参数传递进去,这里就完成了第二次分派 ,这里的IVisitor接口中有多个重载的方法,而传递进行的是this,就是具体的实际类型的对象。
双分派实现动态绑定的本质,就是在重载方法委派的前面加上了继承体系中覆盖的环节,由于覆盖是动态的,所以重载就是动态的了。
9.3 总结
适用场景:
优点:
- 解耦了数据结构与数据操作,使得操作集合可以独立变化。
- 扩展性好:可以通过扩展访问者角色,实现对数据集的不同操作。
- 元素具体类型并非单一,访问者均可操作。
- 各角色职责分离,符合单一职责原则。
缺点:
- 无法增加元素类型:若系统数据结构对象易于变化,经常有新的数据对象增加进来,则访问者类必须增加对应元素类型的操作,违背了开闭原则。
- 具体元素变更困难:具体元素增加属性,删除属性等操作会导致对应的访问者类需要进行相应的修改,尤其当有大量访问者类时,修改访问太大。
- 违背依赖倒置原则:为了达到“区别对待”,访问者依赖的是具体元素类型,而不是抽象。
10.备忘录模式(Memento Pattern)
备忘录模式又称为快照模式(Snapshot Pattern)或令牌模式(Token Pattern),是指在不破坏封装的前提下,捕获一个对象的内部状态,并在对象之外保存这个状态,这样以后就可将该对象恢复到原先保存的状态。
特征:“后悔药”
备忘录模式的主要角色如下:
- 发起人(Originator)角色:记录当前时刻的内部状态信息,提供创建备忘录和恢复备忘录数据的功能,实现其他业务功能,它可以访问备忘录里的所有信息。
- 备忘录(Memento)角色:负责存储发起人的内部状态,在需要的时候提供这些内部状态给发起人。
- 管理者(Caretaker)角色:对备忘录进行管理,提供保存与获取备忘录的功能,但其不能对备忘录的内容进行访问与修改。
备忘录有两个等效的接口:
- 窄接口:管理者(Caretaker)对象(和其他发起人对象之外的任何对象)看到的是备忘录的窄接口(narror Interface),这个窄接口只允许他把备忘录对象传给其他的对象。
- 宽接口:与管理者看到的窄接口相反,发起人对象可以看到一个宽接口(wide Interface),这个宽接口允许它读取所有的数据,以便根据这些数据恢复这个发起人对象的内部状态。
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.解释器模式(interpreter pattern)
解释器模式给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。
解释器模式包含以下主要角色:
- 抽象表达式(Abstract Expression)角色:定义解释器的接口,约定解释器的解释操作,主要包含解释方法 interpret()。
- 终结符表达式(Terminal Expression)角色:是抽象表达式的子类,用来实现文法中与终结符相关的操作,文法中的每一个终结符都有一个具体终结表达式与之相对应。
- 非终结符表达式(Nonterminal Expression)角色:也是抽象表达式的子类,用来实现文法中与非终结符相关的操作,文法中的每条规则都对应于一个非终结符表达式。
- 环境(Context)角色:通常包含各个解释器需要的数据或是公共的功能,一般用来传递被所有解释器共享的数据,后面的解释器可以从这里获取这些值。
- 客户端(Client):主要任务是将需要分析的句子或表达式转换成使用解释器对象描述的抽象语法树,然后调用解释器的解释方法,当然也可以通过环境角色间接访问解释器的解释方法。
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 总结
适用场景:
优点:
- 扩展性强:在解释器模式中由于语法是由很多类表示的,当语法规则更改时,只需修改相应的非终结符表达式即可;若扩展语法时,只需添加相应非终结符类即可。
- 增加了新的解释表达式的方式。
- 易于实现文法:解释器模式对应的文法应当是比较简单且易于实现的,过于复杂的语法并不适合使用解释器模式。
缺点:
- 语法规则较复杂时,会引起类膨胀。
- 执行效率比较低
原文地址:https://blog.csdn.net/pyy542718473/article/details/127248128
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.7code.cn/show_18893.html
如若内容造成侵权/违法违规/事实不符,请联系代码007邮箱:suwngjj01@126.com进行投诉反馈,一经查实,立即删除!