当前位置: 首页 > news >正文

简单大气网站模板拜博网站建设

简单大气网站模板,拜博网站建设,国家重大建设项目库填报网站,购物网站建设模板下载前言 七大设计原则 1、单一原则#xff1a;一个类只负责一个职责 2、开闭原则#xff1a;对修改关闭#xff0c;对扩展开放 3、里氏替换原则#xff1a;不要破坏继承关系 4、接口隔离原则#xff1a;暴露最小接口#xff0c;避免接口过于臃肿 5、依赖倒置原则#xff1…前言 七大设计原则 1、单一原则一个类只负责一个职责 2、开闭原则对修改关闭对扩展开放 3、里氏替换原则不要破坏继承关系 4、接口隔离原则暴露最小接口避免接口过于臃肿 5、依赖倒置原则面向抽象编程 6、迪米特 法则尽量不跟陌生人讲话 7、合成复用原则多使用组合、聚合、少用继承 一、创建型模式 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; }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 乐观锁机制CAS cas是一项乐观锁技术当多个线程尝试使用cas同时更新一个变量时只有其中一个线程能更新变量的值而其它线程都失败失败的线程并不会被挂起而是被告知竞争失败并可以再次尝试。 优点本质是基于忙等待算法依赖底层硬件的实现。没有线程切换和阻塞的额外消耗。 缺点一直执行不成功对cpu造成较大的开销。 private static final AtomicReferenceCasSingleton instance new AtomicReference();private CasSingleton(){}public static CasSingleton getInstance() {while (instance.get() null) {instance.compareAndSet(null,new CasSingleton());}return instance.get(); }1.7 容器式单例 当程序中的单例对象非常多的时候则可以使用容器对所有单例对象进行管理如下 public class ContainerSingleton {private ContainerSingleton() {}private static MapString, 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} }1.8 ThreadLocal单例 不保证整个应用全局唯一但保证线程内部全局唯一以空间换时间且线程安全。 public class ThreadLocalSingleton {private ThreadLocalSingleton(){}private static final ThreadLocalThreadLocalSingleton 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.ThreadLocalSingleton53ac93b3 // Thread-1-----com.ruoyi.library.domain.vo.ThreadLocalSingleton7fe11afc // Thread-0-----com.ruoyi.library.domain.vo.ThreadLocalSingleton53ac93b3 // Thread-1-----com.ruoyi.library.domain.vo.ThreadLocalSingleton7fe11afc} }可以看到上面线程0和1他们的对象是不一样的但是线程内部他们的对象是一样的这就是线程内部保证唯一。 ThreadLocal将多有的对象放在ThreadLocalMap中为每个线程都提供一个对象实际上是以空间换时间来实现线程隔离的。 1.9 破坏单例的几种方式与解决方法 1.9.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.Singleton50134894 System.out.println(singleton1);//com.ruoyi.base.mapper.Singleton5ccd43c2可以看到反序列化后两个对象的地址不一样了那么这就是违背了单例模式的原则了解决方法只需要在单例类里加上一个readResolve()方法即可原因就是在反序列化的过程中会检测readResolve()方法是否存在如果存在的话就会反射调用readResolve()这个方法。 private Object readResolve() {return singleton; } //com.ruoyi.base.mapper.Singleton50134894 //com.ruoyi.base.mapper.Singleton501348941.9.2 反射 Singleton singleton Singleton.getInstance(); ClassSingleton singletonClass Singleton.class; ConstructorSingleton constructor singletonClass.getDeclaredConstructor(); constructor.setAccessible(true); Singleton singleton1 constructor.newInstance(); System.out.println(singleton);//com.ruoyi.base.mapper.Singleton32a1bec0 System.out.println(singleton1);//com.ruoyi.base.mapper.Singleton22927a81同样可以看到两个对象的地址不一样这同样是违背了单例模式的原则解决办法为使用一个布尔类型的标记变量标记一下即可代码如下 饿汉式 // 饿汉式创建单例对象 private static Singleton singleton new Singleton();//第一要素必须私有构造函数才不能被随意New private Singleton(){if(instance ! null) {throw new IllegalArgumentException(单例已经创建);} }public static Singleton getInstance() {return singleton; }懒汉式同样方法无解 但是这种方法假如使用了反编译获得了这个变量同样可以破坏单例代码如下 ClassSingleton singletonClass Singleton.class; ConstructorSingleton constructor singletonClass.getDeclaredConstructor(); constructor.setAccessible(true); Singleton singleton constructor.newInstance(); System.out.println(singleton); // com.ruoyi.base.mapper.Singleton32a1bec0Field singletonFlag singletonClass.getDeclaredField(singleton); singletonFlag.setAccessible(true); singletonFlag.set(singleton, null); Singleton singleton1 constructor.newInstance(); System.out.println(singleton1); // com.ruoyi.base.mapper.Singleton5e8c92f4如果想使单例不被破坏那么应该使用枚举的方式去实现单例模式枚举是不可以被反射破坏单例的。 1.10 总结 适用场景 需要确保在任何情况下绝对只需要一个实例。如ServletContextServletConfigApplicationContextDBPoolThreadPool等。 优点 在内存中只有一个实例减少了内存开销。可以避免对资源的多重占用。设置全局访问点严格控制访问。 缺点 没有接口扩展困难。如果要扩展单例对象只有修改代码没有其它途径。 2.工厂方法模式Factory Method⭐ 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 工厂方法模式 工厂方法模式是指定义一个创建对象的接口让实现这个接口的类来决定实例化哪个类工厂方法让类的实例化推迟到子类中进行。 工厂方法模式主要有以下几种角色 抽象工厂Abstract Factory提供了创建产品的接口调用者通过它访问具体工厂的工厂方法来创建产品。具体工厂ConcreteFactory主要是实现抽象工厂中的抽象方法完成具体产品的创建。抽象产品Product定义了产品的规范描述了产品的主要特性和功能。具体产品ConcreteProduct实现了抽象产品角色所定义的接口由具体工厂来创建它和具体工厂之间一一对应。 2.2.1 代码实现 // 抽象工厂 public interface CoffeeFactory {Coffee create(); } // 具体工厂 public class CappuccinoFactory implements CoffeeFactory {Overridepublic 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 {Overridepublic Coffee createCoffee() {return new Americano();}Overridepublic Dessert createDessert() {return new Cheesecake();} } // 意式风格工厂 public class ItalyFactory implements CoffeeShopFactory {Overridepublic Coffee createCoffee() {return new Cappuccino();}Overridepublic Dessert createDessert() {return new Tiramisu();} }UML图 3.2 总结 产品族一系列相关的产品整合到一起有关联性 产品等级同一个继承体系 适用场景 客户端应用层不依赖于产品类实例如何被创建和实现等细节。强调一系列相关的产品对象属于同一产品族一起使用创建对象需要大量重复的代码。提供一个产品类的库所有的产品以同样的接口出现从而使客户端不依赖于具体实现。 优点 当一个产品族中的多个对象被设计成一起工作时它能保证客户端始终只使用同一个产品族中的对象。 缺点 当产品族中需要增加一个新的产品时所有的工厂类都需要进行修改。 4.原型模式Prototype 原型模式是指原型实例指定创建对象的种类并且通过拷贝这些原型创建新的对象。调用者不需要知道任何创建细节不调用构造函数。 原型模式包含如下角色 抽象原型类规定了具体原型对象必须实现的的 clone() 方法。具体原型类实现抽象原型类的 clone() 方法它是可被复制的对象。访问类使用具体原型类中的 clone() 方法来复制新的对象。 Data AllArgsConstructor NoArgsConstructor public class Student implements Cloneable {private String name;private String sex;private Integer age;Overrideprotected 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男, age18)System.out.println(stu2);// Student(name李四, sex男, age18)} }可以看到把一个学生复制过来只是改了姓名而已其他属性完全一样没有改变需要注意的是一定要在被拷贝的对象上实现Cloneable接口否则会抛出CloneNotSupportedException异常。 4.1 浅克隆 创建一个新对象新对象的属性和原来对象完全相同对于非基本类型属性仍指向原有属性所指向的对象的内存地址。 Data public class Clazz implements Cloneable {private String name;private Student student;Overrideprotected 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高三一班, studentStudent(name张三, sex男, age18))Clazz clazz2 (Clazz)clazz1.clone();Student stu2 clazz2.getStudent();stu2.setName(李四);System.out.println(clazz1); // Clazz(name高三一班, studentStudent(name李四, sex男, age18))System.out.println(clazz2); // Clazz(name高三一班, studentStudent(name李四, sex男, age18)) }可以看到当修改了stu2的姓名时stu1的姓名同样也被修改了这说明stu1和stu2是同一个对象这就是浅克隆的特点对具体原型类中的引用类型的属性进行引用的复制。同时这也可能是浅克隆所带来的弊端因为结合该例子的原意显然是想在班级中新增一名叫李四的学生而非让所有的学生都改名叫李四于是我们这里就要使用深克隆。 4.2 深克隆 创建一个新对象属性中引用的其他对象也会被克隆不再指向原有对象地址。 Data public class Clazz implements Cloneable, Serializable {private String name;private Student student;Overrideprotected 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高三一班, studentStudent(name张三, sex男, age18))System.out.println(clazz3); // Clazz(name高三一班, studentStudent(name王五, sex男, age18)) }可以看到当修改了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 总结 适用场景 类初始化消耗资源较多。new产生的一个对象需要非常繁琐的过程数据准备、访问权限等。构造函数比较复杂。循环体中生产大量对象时。 优点 性能优良Java自带的原型模式是基于内存二进制流的拷贝比直接new一个对象性能上提升了许多。可以使用深克隆方式保存对象的状态使用原型模式将对象复制一份并将其状态保存起来简化了创建的过程。 缺点 必须配备克隆或者可拷贝方法。当对已有类进行改造的时候需要修改代码违反了开闭原则。深克隆、浅克隆需要运用得当。 5.建造者模式Builder⭐ 建造者模式是将一个复杂对象的构建与它的表示分离使得同样的构建过程可以创建不同的表示。用户只需指定需要建造的类型就可以获得对象建造过程及细节不需要了解。 建造者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主板);}Overridepublic void buildCpu() {computer.setCpu(Inter 12900KS);}Overridepublic void buildMemory() {computer.setMemory(芝奇幻峰戟 16G*2);}Overridepublic void buildDisk() {computer.setDisk(三星980Pro 2T);}Overridepublic void buildGpu() {computer.setGpu(华硕3090Ti 水猛禽);}Overridepublic void buildHeatSink() {computer.setHeatSink(龙神二代一体式水冷);}Overridepublic void buildPower() {computer.setPower(雷神二代1200W);}Overridepublic void buildChassis() {computer.setChassis(太阳神机箱);}Overridepublic 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(motherboardExtreme主板, cpuInter 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(motherboardExtreme主板, cpuInter 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 {Overridepublic void sellTickets() {System.out.println(广州火车站卖票啦);} } // 代售点卖票代理类 public class ProxyPoint implements TrainStation {// 目标对象代理火车站售票private TrainStation station null;public ProxyPoint(TrainStation station){this.station station;}Overridepublic void sellTickets() {System.out.println(代售加收5%手续费);station.sellTickets();} }// 测试 public static void main(String[] args) {TrainStation station new GuangzhouTrainStation()ProxyPoint proxyPoint new ProxyPoint(station);// 代售加收5%手续费// 火车站卖票啦proxyPoint.sellTickets(); }可以从上面代码看到我们访问的是ProxyPoint对象也就是说ProxyPoint是作为访问对象和目标对象的中介的同时也对sellTickets方法进行了增强代理点收取加收5%手续费。 静态代理的优点是实现简单容易理解只要确保目标对象和代理对象实现共同的接口或继承相同的父类就可以在不修改目标对象的前提下进行扩展。 而缺点也比较明显那就是代理类和目标类必须有共同接口(父类)并且需要为每一个目标类维护一个代理类当需要代理的类很多时会创建出大量代理类。一旦接口或父类的方法有变动目标对象和代理对象都需要作出调整。 1.2 动态代理 代理类在代码运行时创建的代理称之为动态代理。动态代理中代理类并不是预先在Java代码中定义好的而是运行时由JVM动态生成并且可以代理多个目标对象。 1.2.1 jdk动态代理 JDK动态代理是Java JDK自带的一个动态代理实现 位于java.lang.reflect包下。 代理类实现InvocationHandler接口实现invok()方法, 对目标方法进行增强使用Proxy.newProxyInstance(classLoader, interfaces[], invocationHandler) // 火车站接口有卖票功能 public interface TrainStation {void sellTickets(); }// 广州火车站卖票 public class GuangzhouTrainStation implements TrainStation {Overridepublic void sellTickets() {System.out.println(广州火车站卖票啦);} } // 深圳火车站卖票 public class ShenzhenTrainStation implements TrainStation {Overridepublic 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);}Overridepublic 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());guangzhouTrainStation.sellTickets();// 代售火车票收取5%手续费// 广州火车站卖票啦TrainStation shenzhenTrainStation proxy.getProxyObject(new ShenzhenTrainStation());shenzhenTrainStation.sellTickets();// 代售火车票收取5%手续费// 深圳火车站卖票啦 }优点 使用简单、维护成本低。Java原生支持不需要任何依赖。解决了静态代理存在的多数问题。 缺点 由于使用反射性能会比较差。只支持接口实现不支持继承 不满足所有业务场景。 1.2.2 CGLIB动态代理 CGLIB是一个强大的、高性能的代码生成库。它可以在运行期扩展Java类和接口其被广泛应用于AOP框架中Spring、dynaop中 用以提供方法拦截。CGLIB比JDK动态代理更强的地方在于它不仅可以接管Java接口 还可以接管普通类的方法。 !-- 先引入cglib包 -- dependencygroupIdcglib/groupIdartifactIdcglib/artifactIdversion${cglib-version}/version /dependency代理类实现MethodInterceptor方法拦截器通过Enhancer设置要代理的目标对象以及方法拦截器Enhancer.create()生成代理对象 // 代售点卖票代理类 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();}Overridepublic 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);guangzhouTrainStation.sellTickets();// 代售火车票收取5%手续费// 广州火车站卖票啦TrainStation shenzhenTrainStation proxy.getProxyObject(ShenzhenTrainStation.class);shenzhenTrainStation.sellTickets();// 代售火车票收取5%手续费// 深圳火车站卖票啦 }1.3 总结 应用场景 保护目标对象。增强目标对象。 优点 代理模式能将代理对象与真实被调用的目标对象分离。一定程度上降低了系统的耦合程度易于扩展。代理可以起到保护目标对象的作用。增强目标对象的职责。 缺点 代理模式会造成系统设计中类的数目增加。在客户端和目标对象之间增加了一个代理对象请求处理速度变慢。增加了系统的复杂度。 两种动态代理的对比 JDK动态代理的特点 需要实现InvocationHandler接口 并实现invoke方法。被代理类需要实现接口 它不支持继承。JDK 动态代理类不需要事先定义好 而是在运行期间动态生成。JDK 动态代理不需要实现和被代理类一样的接口 所以可以绑定多个被代理类。主要实现原理为反射 它通过反射在运行期间动态生成代理类 并且通过反射调用被代理类的实际业务方法。 cglib的特点 cglib动态代理中使用的是FastClass机制。cglib生成字节码的底层原理是使用ASM字节码框架。cglib动态代理需创建3份字节码所以在第一次使用时会比较耗性能但是后续使用较JDK动态代理方式更高效适合单例bean场景。cglib由于是采用动态创建子类的方法对于final方法无法进行代理。 2.适配器模式Adapter Class/Object⭐ 适配器模式它的功能是将一个类的接口变成客户端所期望的另一种接口从而使原本因接口不匹配而导致无法在一起工作的两个类能够一起工作。适配器模式分为类适配器模式和对象适配器模式前者类之间的耦合度比后者高且要求程序员了解现有组件库中的相关组件的内部结构所以应用相对较少些。 适配器模式Adapter包含以下主要角色 目标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 {Overridepublic 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适配转换成5VpowerAdapter.output5(); }通过上面代码例子可以看出类适配器有一个很明显的缺点就是违背了合成复用原则。结合上面的例子假如我不是220V的电压了是380V电压呢那就要多建一个380V电压的适配器了。同理由于Java是单继承的原因如果不断的新增适配者那么就要无限的新增适配器于是就有了对象适配器。 2.2 对象适配器 对象适配器的实现方式是通过现有组件库中已经实现的组件引入适配器类中该类同时实现当前系统的业务接口。 // 电源接口 public interface Power {int output(); }// 适配者 220V电压 public class AC220 implements Power {Overridepublic int output() {System.out.println(输出220V交流电);return 220;} }// 目标 5V public interface DC5 {public int output5(); }AllArgsConstructor public class PowerAdapter implements DC5 {// 适配者private Power power;Overridepublic 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适配转换成5VpowerAdapter.output5(); }可以看到上面代码中只实现了目标接口并没有继承适配者而是将适配者类实现适配者接口在适配器中引入适配者接口当我们需要使用不同的适配者通过适配器进行转换时就无需再新建适配器类了如上面例子假如我需要380V的电源转换成5V的那么客户端只需要调用适配器时传入380V电源的类即可就无需再新建一个380V电源的适配器了PS上述逻辑代码中output220 / 44请忽略可以根据实际情况编写实际的通用逻辑代码。 2.3 接口适配器 接口适配器主要是解决类臃肿的问题我们可以把所有相近的适配模式的方法都放到同一个接口里面去实现所有方法当客户端需要哪个方法直接调用哪个方法即可。如上面例子所示我们只是转换成了5V电压那假如我要转换成12V24V30V…呢那按照上面的写法就需要新建12V24V30V…的接口这样就会导致类过于多了。那么我们就可以把5V12V24V30V…这些转换方法通通都写到一个接口里去这样当我们需要转换哪种就直接调用哪种即可。 // 这里例子 输出不同直流电接口 public interface DC {int output5();int output12();int output24();int output30(); }// 适配器类电源适配器 AllArgsConstructor public class PowerAdapter implements DC {private Power power;Overridepublic int output5() {// 具体实现逻辑return 5;}Overridepublic int output12() {// 具体实现逻辑return 12;}Overridepublic int output24() {// 具体实现逻辑return 24;}Overridepublic int output30() {// 具体实现逻辑return 30;} }2.4 总结 适用场景 已经存在的类它的方法和需求不匹配方法结构相同或相似的情况。使用第三方提供的组件但组件接口定义和自己要求的接口定义不同。 优点 能提高类的透明性和复用现有的类复用但不需要改变。目标类和适配器类解耦提高程序的扩展性。在很多业务场景中符合开闭原则。 缺点 适配器编写过程需要全面考虑可能会增加系统的复杂性。增加代码阅读难度降级代码可读性过多使用适配器会使系统代码变得凌乱。 3.装饰模式Decorator Pattern⭐ 装饰模式是指在不改变原有对象的基础上将功能附加到对象上提供了比继承更有弹性的替代方案扩展原有对象的功能 装饰Decorator模式中的角色 抽象构件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 andBody模式是将抽象部分与他的具体实现部分分离使它们都可以独立地变化通过组合的方式建立两个类之间的联系而不是继承。 桥接Bridge模式包含以下主要角色 实现化Implementor角色 定义实现化角色的接口供扩展抽象化角色调用。具体实现化Concrete Implementor角色 给出实现化角色接口的具体实现。抽象化Abstraction角色 定义抽象类并包含一个对实现化对象的引用。扩展抽象化Refined Abstraction角色 是抽象化角色的子类实现父类中的业务方法并通过组合关系调用实现化角色中的业务方法。 4.1 代码实现 下面以一个多系统多视频格式文件播放为例子 // 视频接口 public interface Video {void decode(String fileName); }// MP4格式类 public class Mp4 implements Video{Overridepublic void decode(String fileName) {System.out.println(MP4视频文件 fileName);} } // RMVB格式类 public class Rmvb implements Video{Overridepublic 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);}Overridepublic void play(String fileName) {video.decode(fileName);} } // windows系统 public class Windows extends OperatingSystem {public Windows(Video video){super(video);}Overridepublic void play(String fileName) {video.decode(fileName);} }类关系图 可以通过类图看到视频类和操作系统类之间通过OperatingSystem类桥接关联起来。 4.2 总结 适用场景 在抽象和具体实现之间需要增加更多的灵活性的场景。一个类存在两个或多个独立变化的维度而这两个或多个维度都需要独立进行扩展。不希望使用继承或因为多层继承导致系统类的个数剧增。 优点 分离抽象部分及其具体实现部分。提高了系统的扩展性。符合开闭原型。符合合成复用原则。 缺点 增加了系统的理解与设计难度。需要正确地识别系统中两个独立变化的维度。 5.外观模式Facade 外观模式又称门面模式提供了一个统一的接口用来访问子系统中的一群接口。 特征门面模式定义了一个高层接口让子系统更容易使用。 外观Facade模式包含以下主要角色 外观Facade角色为多个子系统对外提供一个共同的接口。子系统Sub System角色实现系统的部分功能客户可以通过外观角色访问它。 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 ListMenuComponent menuComponentList new ArrayList();public Menu(String name,int level){this.level level;this.name name;}Overridepublic void add(MenuComponent menuComponent) {menuComponentList.add(menuComponent);}Overridepublic void remove(MenuComponent menuComponent) {menuComponentList.remove(menuComponent);}Overridepublic MenuComponent getChild(Integer i) {return menuComponentList.get(i);}Overridepublic 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;}Overridepublic 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 总结 适用场景 希望客户端可以忽略组合对象与单个对象的差异时。对象层次具备整体和部分呈树形结构如树形菜单操作系统目录结构公司组织架构等。 优点 清楚地定义分层次的复杂对象表示对象的全部或部分层次。让客户端忽略了层次的差异方便对整个层次结构进行控制。简化客户端代码。符合开闭原则。 缺点 限制类型时会较为复杂。使设计变得更加抽象。 分类 透明组合模式 透明组合模式中抽象根节点角色中声明了所有用于管理成员对象的方法比如在示例中MenuComponent声明了add() 、 remove() 、getChild()方法这样做的好处是确保所有的构件类都有相同的接口。透明组合模式也是组合模式的标准形式。透明组合模式的缺点是不够安全因为叶子对象和容器对象在本质上是有区别的叶子对象不可能有下一个层次的对象即不可能包含成员对象因此为其提供 add()、remove() 等方法是没有意义的这在编译阶段不会出错但在运行阶段如果调用这些方法可能会出错如果没有提供相应的错误处理代码 安全组合模式 在安全组合模式中在抽象构件角色中没有声明任何用于管理成员对象的方法而是在树枝节点Menu类中声明并实现这些方法。安全组合模式的缺点是不够透明因为叶子构件和容器构件具有不同的方法且容器构件中那些用于管理成员对象的方法没有在抽象构件类中定义因此客户端不能完全针对抽象编程必须有区别地对待叶子构件和容器构件。 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;}Overridepublic void show(String seat) {this.price new Random().nextInt(500);System.out.println(from - to : seat 价格: this.price);} }// 工厂类 public class TicketFactory {private static MapString, 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(北京, 上海);//使用数据库获取火车票:北京-上海//北京-上海:二等座价格:20ticket.show(二等座);ITicket ticket1 getTicket(北京, 上海);//使用缓存获取火车票:北京-上海//北京-上海:商务座价格:69ticket1.show(商务座);ITicket ticket2 getTicket(上海, 北京);//使用数据库获取火车票:上海-北京//上海-北京:一等座价格:406ticket2.show(一等座);System.out.println(ticket ticket1);//trueSystem.out.println(ticket ticket2);//false }可以看到ticket和ticket2是使用数据库查询的而ticket1是使用缓存查询的同时ticket ticket1返回的是trueticket ticket2返回的是false证明ticket和ticket1是共享的对象。 7.2 总结 适用场景 一个系统有大量相同或者相似的对象造成内存的大量耗费。对象的大部分状态都可以外部化可以将这些外部状态传入对象中。在使用享元模式时需要维护一个存储享元对象的享元池而这需要耗费一定的系统资源因此应当在需要多次重复使用享元对象时才值得使用享元模式。 优点 减少对象的创建降低内存中对象的数量降低系统的内存提高效率。减少内存之外的其他资源占用。 缺点 关注内、外部状态。关注线程安全问题。使系统、程序的逻辑复杂化。 三、 行为型模式 1.模板方法模式Template method pattern 模板方法模式通常又叫模板模式是指定义一个算法的骨架并允许之类为其中的一个或者多个步骤提供实现。模板方法模式使得子类可以在不改变算法结构的情况下重新定义算法的某些步骤。 模板方法Template Method模式包含以下主要角色 抽象类Abstract Class负责给出一个算法的轮廓和骨架。它由一个模板方法和若干个基本方法构成。 模板方法定义了算法的骨架按某种顺序调用其包含的基本方法。基本方法是实现算法各个步骤的方法是模板方法的组成部分。基本方法又可以分为三种 抽象方法(Abstract Method) 一个抽象方法由抽象类声明、由其具体子类实现。具体方法(Concrete Method) 一个具体方法由一个抽象类或具体类声明并实现其子类可以进行覆盖也可以直接继承。钩子方法(Hook Method) 在抽象类中已经实现包括用于判断的逻辑方法和需要子类重写的空方法两种。一般钩子方法是用于判断的逻辑方法这类方法名一般为isXxx返回值类型为boolean类型。 具体子类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 {Overrideprotected void summit() {System.out.println(张三签名);}Overrideprotected void signUp() {System.out.println(提交到技术部审批);} }public class Lisi extends DayOffProcess {Overrideprotected void summit() {System.out.println(李四签名);}Overrideprotected 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⭐ 策略模式又叫政策模式PolicyPattern它是将定义的算法家族分别封装起来让它们之间可以互相替换从而让算法的变化不会影响到使用算法的用户。可以避免多重分支的if…else和switch语句。 策略模式的主要角色如下 抽象策略Strategy类这是一个抽象角色通常由一个接口或抽象类实现。此角色给出所有的具体策略类所需的接口。具体策略Concrete Strategy类实现了抽象策略定义的接口提供具体的算法实现或行为。环境Context类持有一个策略类的引用最终给客户端调用。 2.1 普通案例会员卡打折 // 会员卡接口 public interface VipCard {public void discount(); }public class GoldCard implements VipCard {Overridepublic void discount() {System.out.println(金卡打7折);} }public class SilverCard implements VipCard {Overridepublic void discount() {System.out.println(银卡打8折);} }public class CopperCard implements VipCard {Overridepublic void discount() {System.out.println(铜卡打9折);} }public class Normal implements VipCard {Overridepublic void discount() {System.out.println(普通会员没有折扣);} }// 会员卡容器类 public class VipCardFactory {private static MapString, 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{Overrideprotected String getPaymentName() {return 现金支付;}Overrideprotected double queryBalance(String uid) {return 1000;} }// 支付宝类 public class AliPay extends Payment {Overrideprotected String getPaymentName() {return 支付宝;}Overrideprotected double queryBalance(String uid) {return 500;} }// 微信支付类 public class WeChatPay extends Payment {Overrideprotected String getPaymentName() {return 微信支付;}Overrideprotected double queryBalance(String uid) {return 300;} }// 支付方式容器策略类 public class PaymentStrategy {private static MapString, 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.0System.out.println(order.pay(Ali));//欢迎使用现金支付//本次交易金额:500.0,开始扣款...//支付成功!支付金额:500.0余额剩余:500.0System.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;Overridepublic void excuse() {this.player.play();} }// 暂停命令类 AllArgsConstructor public class PauseAction implements IAction {private Player player;Overridepublic void excuse() {this.player.pause();} }// 停止命令类 AllArgsConstructor public class StopAction implements IAction{private Player player;Overridepublic 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 {Overridepublic 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 {Overridepublic 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 {Overridepublic 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.2 结合建造者模式 与基础版本区别主要是Handler类中新增一个Builder的内部类以及流程类里改用链式写法具体如下 // handler抽象类 public abstract class HandlerT {protected Handler next;// 返回handler方便链式操作public Handler next(Handler next) {this.next next;return next;}// 流程开始的方法public abstract void doHandler(User user);// Builderstatic class BuilderT {private HandlerT head;private HandlerT tail;public BuilderT addHandler(HandlerT handler) {if (this.head null) {this.head this.tail handler;return this;}this.tail.next(handler);this.tail handler;return this;}public HandlerT 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.3 总结 适用场景 多个对象可以处理同一请求但具体由哪个对象处理则在运行时动态决定。在不明确指定接收者的情况下向多个对象中的一个提交一个请求。可动态指定一组对象处理请求。spring cloud gateway的路由过滤器使用的就是责任链建造者模式 优点 将请求与处理解耦。请求处理者节点对象只需关注自己感兴趣的请求进行处理即可对于不感兴趣的请求直接转发给下一级节点对象。具备链式传递处理请求功能请求发送者无需知晓链路结构只需等待请求处理结果。链路结构灵活可以通过改变链路结构动态地新增或删减责任。易于扩展新的请求处理类节点符合开闭原则。 缺点 责任链太长或者处理时间过长会影响整体性能。如果节点对象存在循环引用时会造成死循环导致系统崩溃。 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 {Overridepublic void open() {System.out.println(电梯门打开了);}Overridepublic void close() {super.context.setLiftState(Context.CLOSE_STATE);super.context.close();}Overridepublic void run() {}Overridepublic void stop() {} }// 关门状态(具体状态) public class CloseState extends LiftState {Overridepublic void open() {super.context.setLiftState(Context.OPEN_STATE);super.context.open();}Overridepublic void close() {System.out.println(电梯门关闭了!);}Overridepublic void run() {super.context.setLiftState(Context.RUN_STATE);super.context.run();}Overridepublic void stop() {super.context.setLiftState(Context.STOP_STATE);super.context.stop();} }// 运行状态(具体状态) public class RunState extends LiftState {Overridepublic void open() {}Overridepublic void close() {}Overridepublic void run() {System.out.println(电梯正在运行...);}Overridepublic void stop() {super.context.setLiftState(Context.STOP_STATE);super.context.stop();} }// 停止状态(具体状态) public class StopState extends LiftState {Overridepublic void open() {super.context.setLiftState(Context.OPEN_STATE);super.context.open();}Overridepublic void close() {super.context.setLiftState(Context.CLOSE_STATE);super.context.close();}Overridepublic void run() {super.context.setLiftState(Context.RUN_STATE);super.context.run();}Overridepublic 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的状态机 优点 结构清晰将状态独立为类消除了冗余的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;Overridepublic 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 ListObserver list new ArrayList();Overridepublic void add(Observer observer) {list.add(observer);}Overridepublic void del(Observer observer) {list.remove(observer);}// 给每一个观察者(订阅者)推送消息Overridepublic 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 被观察者带过来的参数此例子中是公众号发布的消息*/Overridepublic 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;Subscribepublic 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 IteratorT {Boolean hasNext();T next(); }// 迭代器接口实现类 public class IteratorImplT implements IteratorT {private ListT list;private Integer cursor;private T element;public IteratorImpl(ListT list) {this.list list;}Overridepublic Boolean hasNext() {return cursor list.size();}Overridepublic T next() {element list.get(cursor);cursor;return element;} }// 容器接口 public interface AggregateT {void add(T t);void remove(T t);IteratorT iterator(); }// 容器接口实现类 public class AggregateImplT implements AggregateT {private ListT list new ArrayList();Overridepublic void add(T t) {list.add(t);}Overridepublic void remove(T t) {list.remove(t);}Overridepublic IteratorT iterator() {return new IteratorImpl(list);} }PS具体测试的话可以自己写一个集合测试一下即可 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 {Overridepublic void visit(Engineer engineer) {System.out.println(engineer.getName() KPI为: engineer.getKpi());}Overridepublic void visit(Pm pm) {System.out.println(pm.getName() KPI为: pm.getKpi());} } // 具体的访问者类,访问者角色(CTO) public class CtoVisitor implements IVisitor {Overridepublic void visit(Engineer engineer) {System.out.println(engineer.getName() 工作内容: engineer.getCodeLine() 行代码);}Overridepublic 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);}Overridepublic 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);}Overridepublic void accept(IVisitor visitor) {visitor.visit(this);}public Integer getProject() {return new Random().nextInt(10);} } AllArgsConstructor public class Report {private ListEmployee employeeList;public void showReport(IVisitor visitor) {for (Employee employee : employeeList) {employee.accept(visitor);}} }// 测试 public static void main(String[] args){ListEmployee 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)又分为两种即静态分派和动态分派。 静态分派(Static Dispatch) 发生在编译时期分派根据静态类型信息发生。静态分派对于我们来说并不陌生方法重载就是静态分派。动态分派(Dynamic Dispatch) 发生在运行时期动态分派动态地置换掉某个方法。Java通过方法的重写支持动态分派。 9.2.2 伪动态双分派 所谓双分派技术就是在选择一个方法的时候不仅仅要根据消息接收者receiver的运行时区别还要根据参数的运行时区别。 在上面代码中客户端将IVisitor接口做为参数传递给Employee抽象类的变量调用的方法这里完成第一次分派这里是方法重写所以是动态分派也就是执行实际类型中的方法同时也将自己this作为参数传递进去这里就完成了第二次分派 这里的IVisitor接口中有多个重载的方法而传递进行的是this就是具体的实际类型的对象。 双分派实现动态绑定的本质就是在重载方法委派的前面加上了继承体系中覆盖的环节由于覆盖是动态的所以重载就是动态的了。 9.3 总结 适用场景 数据结构稳定作用于数据结构的操作经常变化的场景。需要数据结构与数据操作分离的场景。需要对不同数据类型元素进行操作而不使用分支判断具体类型的场景。 优点 解耦了数据结构与数据操作使得操作集合可以独立变化。扩展性好可以通过扩展访问者角色实现对数据集的不同操作。元素具体类型并非单一访问者均可操作。各角色职责分离符合单一职责原则。 缺点 无法增加元素类型若系统数据结构对象易于变化经常有新的数据对象增加进来则访问者类必须增加对应元素类型的操作违背了开闭原则。具体元素变更困难具体元素增加属性删除属性等操作会导致对应的访问者类需要进行相应的修改尤其当有大量访问者类时修改访问太大。违背依赖倒置原则为了达到“区别对待”访问者依赖的是具体元素类型而不是抽象。 10.备忘录模式Memento Pattern 备忘录模式又称为快照模式Snapshot Pattern或令牌模式TokenPattern是指在不破坏封装的前提下捕获一个对象的内部状态并在对象之外保存这个状态这样以后就可将该对象恢复到原先保存的状态。 特征“后悔药” 备忘录模式的主要角色如下 发起人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;}// 战斗到0public 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;}// 战斗到0public 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);}// 备忘录内部类DataAllArgsConstructorprivate 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;Overridepublic 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);}Overridepublic int interpret() {return left.interpret() right.interpret();} } // 非终结表达式减法 public class MinusNonTerminal extends NonTerminal implements Expression {public MinusNonTerminal(Expression left, Expression right) {super(left, right);}Overridepublic int interpret() {return left.interpret() - right.interpret();} } // 非终结表达式乘法 public class MclNonTerminal extends NonTerminal implements Expression {public MclNonTerminal(Expression left, Expression right) {super(left, right);}Overridepublic int interpret() {return left.interpret() * right.interpret();} } // 非终结表达式除法 public class DivisionNonTerminal extends NonTerminal implements Expression {public DivisionNonTerminal(Expression left, Expression right) {super(left, right);}Overridepublic 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()); // -600System.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); // 150expression expressionParser.parseExpression((102030)*4);value expression.getValue(Integer.class);System.out.println(value); // 240 }可以看到Spring中解释器写的是比较完善的不仅有先乘除后加减和先括号进行运算的日常计算规则而且对于空格也并没有要求仅需要写出完整的表达式即可运算出来。 11.3 总结 适用场景 一些重复出现的问题可以用一种简单的语言来进行表述。一个简单语法需要解释的场景。 优点 扩展性强在解释器模式中由于语法是由很多类表示的当语法规则更改时只需修改相应的非终结符表达式即可若扩展语法时只需添加相应非终结符类即可。增加了新的解释表达式的方式。易于实现文法解释器模式对应的文法应当是比较简单且易于实现的过于复杂的语法并不适合使用解释器模式。 缺点 语法规则较复杂时会引起类膨胀。执行效率比较低
http://www.sadfv.cn/news/175713/

相关文章:

  • 建设网站哪家好咨询公司有哪些
  • 专业做商铺的网站中国排建设银行悦生活网站
  • 学院门户网站建设必要性西安网站制作有限公司
  • 网站的描述 都应该写 什么 优化百度竞价做网站
  • 重庆市园林建设有限公司网站不收费的网站
  • 合肥网站建设网站制作精准引流推广平台
  • 兰州网站建设兰州vs2008不能新建网站
  • 个人做网站建筑模版东莞网站建设技术支持
  • 百度抓取网站登录怎么在自己电脑上建设网站
  • 怎么做frontpage网站上海装修设计公司排名
  • 简单的企业网站cms在网上帮做图片的网站
  • 黑龙江专业建站虚拟主机网站建设
  • 建设电影播放网站社群营销案例
  • 做网站的相关术语广告公司广告语简洁
  • led设计网站建设wordpress列表分页
  • 丽水做企业网站的地方大连云app官方下载
  • 企业策划 企业网站建设 品牌设计自定义字段wordpress
  • 蚌埠网站建设专业公司上高做网站公司
  • 学习做网站难吗wordpress 位置
  • 上海网站建设网单位制作网站备案
  • 手机怎样设计网站建设广东省东莞阳光网
  • 下什么软件做网站济南网站托管
  • 南宁智慧园区网站建设网站建设程序流程
  • 做篮球视频网站公司网页设计文案
  • 不是营销型的网站logo灵感网站
  • 动易网站管理系统教程网站的备案号查询
  • 店铺网站建设策划书河北省建设厅网站工程师查询
  • 科技创业创新心得河南网站优化
  • 网站怎么注册一个网站如何赚钱
  • 健身房网站模板网站开发大体流程图