软件设计原则 开闭原则 对扩展开放,对修改关闭 适合用抽象类和接口实现
里氏代换原则
任何基类出现的地方,子类一定可以出现
子类可扩展但不能改变父类原有功能,就是尽量不重写父类方法
反例 假设有个长方形类,正方形类继承长方形类,同时自己内部重写了长和宽的 setter 方法,之后假设有个 resize 方法拓宽长方形,长方形实例没 问题,但是正方形实例 resize 方法就会同时拓宽长和宽,违反里氏代换原则解决方法 定义一个四边形接口,正方形类和长方形类都实现这个接口,这样长方形和正方形就没有继承关系了,符合里氏代换原则
依赖倒置原则
高层模块不应该依赖低层模块,两者都应该依赖抽象
抽象不应该依赖细节,细节应该依赖抽象(细节就是具体实现类)
反例 定义一个电脑类,电脑有 CPU,内存,硬盘等,电脑直接依赖希捷硬盘,英特尔 CPU,金士顿内存条,之后如果用别的牌子的设备就要对电脑类的 setter 方法里面的类进行修改解决方法 定义 CPU,内存,硬盘等接口,所有牌子的设备实现对应种类的接口,电脑类的 setter 方法传入接口,这样电脑类就只依赖抽象,不依赖具体实现类,符合依赖倒置原则
接口隔离原则
客户端不应该被迫依赖他不需要的接口,一个类对另一个类的依赖应该建立在最小的接口上
就假设一个类实现了接口,接口有方法 1 和方法 2,但这个类并不需要方法 2,所以应该给两个方法分别都写成接口,类需要哪个就实现哪个接口
反例 假设有个安全门接口,有三个功能防盗、防火、防水,A 类安全门功能都需要直接实现就行,但是 B 类安全门只需要防盗防火功能,他不需要实现防水,被迫依赖了解决方法 给三个功能全抽成接口,A 类安全门实现三个接口,B 类安全门只实现两个接口
迪米特法则(最少知识原则) 只与你的直接朋友交谈,不和陌生人说活 其含义是:如果两个软件实体无需直接通信,那么就不应当发生直接的相互调用,而是通过第三方转发
例子 假设有明星类、粉丝类、公司类 建立一个经纪人类。前三个类都是经纪人类的成员变量,分别有对这三个类的方法,就能降低耦合度
合成复用原则 指尽量使用组合、聚合的方式,其次才考虑继承 组合和聚合就是使用成员变量的方式 组合是强依赖,由整体对象作为部分创建出来,整体对象销毁时也随之销毁(独占) 聚合是弱依赖,由外部创建后引入进来(共享)
单一职责原则 一个类应该有且仅有一个引起它变化的原因,否则类应该被拆分
创建者模式 单例模式 该类负责创建自己的对象,同时确保只有单个对象被创建,并提供一个访问其唯一对象的方式
单例设计模式分类两种:
饿汉式:类加载的时候创建对象
懒汉式:类加载的时候不创建对象,而是第一次调用的时候创建对象
饿汉式单例实现方式
静态成员变量
private Singleton () {};// 私有构造方法,不写的话会用默认构造器,默认是 public 的
private static Singleton instance = new Singleton();
静态代码块
private Singleton () {};
private static Singleton instance;
static {instance = new Singleton();}
懒汉式实现方式 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 private Singleton () {};private static Singleton instance;public static synchronized Singleton getInstance () { if (instance == null ) { instance = new Singleton (); } return instance; }
双重检查锁方式 读不加锁提升性能 但是有可能出现空指针问题,因为 JVM 的优化和指令重排序 用 volatile 修饰单例,保证可见性和有序性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 private Singleton () {};private static volatile Singleton instance;public static Singleton getInstance () { if (instance != null ) { synchronized (Singleton.class) { if (instance == null ) { instance = new Singleton (); } } } return instance; }
静态内部类方式(常用) 由于 JVM 在加载外部类的过程中,是不会加载静态内部类的,只有内部类的属性/方法被调用时才会被加载,并初始化其静态属性。静态属性由于被static修饰,保证只实施例换一次,并严格保证实例化顺序在没加锁的情况下保证线程安全,并且没有任何性能影响和空间浪费
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 private Singleton () {};private static class SingletonHolder { private static final Singleton INSTANCE = new Singleton (); }public static class SingletonHolder { return SingletonHolder.INSTANCE; }
枚举方式(饿汉式) 枚举方式(最推荐)(属于饿汉式,在不考虑内存浪费的时候最推荐) 线程安全,而且只会加载一次,而且是所有单例实现里面唯一不会被破坏的
1 2 3 public enum Singleton { INSTANCE; }
单例的破坏 除了枚举方式,其余的都可以被破坏单例模式,就是通过其他方式创建多个对象
反序列化,就比如给单例用输出流写入一个文件,然后用输入流读取就能创建一个新对象
反射
1 2 3 4 5 6 7 8 9 10 Class clazz = Singleton.class;Constructor cons = clazz.getDeclaredConstructor(); cons.setAccessible(true );Singleton s1 = (Singleton) cons.newInstance();Singleton s2 = (Singleton) cons.newInstance(); system.out.println(s1 == s2);
解决破坏单例的问题
反序列化破坏的解决方法 在 Singleton 类中添加 readResolve()方法,在反序列化的时候被反射调用,如果定义了这个方法就返回这个方法的值没有就返回新 new 出来的对象
1 2 3 4 5 public Object readResolve () { return SingletonHolder.INSTANCE; }
反射破坏的解决方法 先在内部类里面设置布尔静态变量,然后在私有构造器方法里面添加同步代码块包裹的判断语句,判断这个变量,然后将其设置为 true。后续调用构造器时都会判断如果为 true 就抛出异常
1 2 3 4 5 6 7 8 9 10 11 12 13 private static boolean flag = false ;private Singleton () { synchronized (Singleton.class) { if (flag) { throw new RuntimeException ("不能创建多个对象" ); } flag = true ; } }
工厂模式 在 Java 中,万物皆对象,这些对象都需要创建,如果创建的时候直接 new 该对象,就会对该对象耦合严重,假如我们要更换对象,所有 new 对象的地方都需要修改一遍,这显然违背了软件设计的开闭原则。如果我们使用工厂来生产对象,我们就只和工厂打交道就可以了,彻底和对象解耦,如果要更换对象,直接在工厂里更换该对象即可,达到了与对象解耦的目的;所以说,工厂模式最大的优点就是:解耦。
简单工厂模式 简单工厂不是一种设计模式,不在 23 种设计模式中,反而像一种编程习惯,包含以下角色
抽象产品:定义了产品的规范,描述产品的主要特性和功能。
具体产品:实现或继承抽象产品的子类
具体工厂:提供了常见产品的方法,调用者通过该方法来获取产品
假设有个咖啡店类,他有个点咖啡方法会根据传进来的 type 生产对应咖啡的对象 改进:咖啡店类内部的点咖啡方法内使用工厂方法,这个工厂会根据传进 type 返回对应咖啡对象,以后如果有新的咖啡,只需要在工厂类中添加新的方法,返回新的咖啡对象,不需要修改咖啡店类,实现了咖啡店和咖啡的解耦
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 public class SimpleCoffeeFactory { public Coffee createCoffee (String type) { Coffee coffee = null ; if ("american" .equals(type)) { coffee = new AmericanCoffee (); } else if ("latte" .equals(type)) { coffee = new LatteCoffee (); } else { throw new RuntimeException ("没有该类型咖啡" ); } return coffee; } }public class CoffeeStore { public Coffee orderCoffee (String type) { SimpleCoffeeFactory factory = new SimpleCoffeeFactory (); return factory.createCoffee(type); } }
后期如果要增加新的咖啡种类,势必要修改简单工厂的代码,这其实违反了开闭原则,但是一个工厂类可以在多个类里面使用,就比如美团外卖也点咖啡,就只需要把工厂接过去,拓展 更容易
静态工厂 就是将简单工厂内部的生产方法设置为静态的,这样外部类就可以直接调用静态方法来获取对象了,不用创建工厂对象
1 2 3 4 5 6 public class CoffeeStore { public Coffee orderCoffee (String type) { return SimpleCoffeeFactory.createCoffee(type); } }
工厂方法模式 简单工厂的缺点,使用工厂模式可以完美解决,完全遵循开闭原则
概念:定义一个用于创建对象的接口,让子类决定实例化哪个产品类对象。工厂方法使一个产品类的实例化延迟到其工厂的子类。
结构:
抽象工厂:定义了创建产品的接口,调用者通过它访问具体工厂的工厂方法来创建产品。
具体工厂:实现抽象工厂接口,完成具体产品的创建。
抽象产品:定义了产品的规范,描述了产品的主要特性和功能。
具体产品:实现了抽象产品接口,由具体工厂来创建,它与具体工厂之间一一对应。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 public interface CoffeeFactory { Coffee createCoffee () ; }public class AmericanCoffeeFactory implements CoffeeFactory { public Coffee createCoffee () { return new AmericanCoffee (); } }public class CoffeeStore { private CoffeeFactory factory; public void setFactory (CoffeeFactory factory) { this .factory = factory; } public Coffee orderCoffee () { return factory.createCoffee(); } }
之后要添加新的咖啡种类只需要添加对应的工厂类,工厂类实现抽象工厂接口,返回对应的咖啡对象,咖啡店要生产什么咖啡也只需要引用对应工厂,不需要修改代码,符合开闭原则
抽象工厂模式 同一产品族(例如品牌,风味)的工厂可以合一,减少工厂类数量
意大利风味甜品工厂和美式风味甜品工厂都实现甜品抽象工厂接口,而不是每个甜品和咖啡都新建一个工厂类,避免类爆炸
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 public interface DessertFactory { Coffee createCoffee () ; Dessert createDessert () ; }public class AmericanDessertFactory implements DessertFactory { public Coffee createCoffee () { return new AmericanCoffee (); } public Dessert createDessert () { return new MatchaMousse (); } }public static void main (String[] args) { AmericanDessertFactory factory = new AmericanDessertFactory (); Coffee coffee = factory.createCoffee(); Dessert dessert = factory.createDessert(); }
优点:当一个产品族中的多个对象被设计成一起工作时,他能保证客户端始终只使用同一个产品族中的对象。
缺点:当产品族中需要增加一个新的产品时,所有的工厂类都需要进行修改。
模式扩展 简单工厂+配置文件解除耦合
在工厂类中加载配置文件中的全类名,并创建对象进行存储,客户端如果需要对象,直接进行获取即可。
第一步:创建配置文件 存储键值对,值是全类名
1 2 american=com.imooc.design.pattern.creational.factory.AmericanCoffee latte=com.imooc.design.pattern.creational.factory.LatteCoffee
第二步:改进工厂类 创建并存储存储配置类(coffee.properties)里面所有对应的咖啡子类对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 public class CoffeeFactory { private static Map<String, Coffee> map = new HashMap <>(); static { Properties p = new Properties (); InputStream is = CoffeeFactory.class.getClassLoader().getResourceAsStream("coffee.properties" ); try { p.load(is); Set<Object> keys = p.keySet(); for (Object key : Keys) { String className = p.getProperty((String) key); Class clazz = Class.forName(className); Coffee obj = (Coffee) clazz.newInstance(); map.put((String)key, obj); } catch (Exception e) { e.printStackTrace(); } } } public static Coffee createCoffee (String name) { return map.get(name); } }
调用工厂类
1 2 3 4 public static void main (String[] args) { Coffee coffee = CoffeeFactory.createCoffee("american" ); }
工厂模式的案例(Java 迭代器) 1 2 3 4 5 6 7 Iterator<String> it = list.iterator();while (it.hasNext()) { String element = it.next(); System.out.println(element); }
上面的对应关系 抽象工厂类:Collection 接口;他里面有一个抽象方法,返回 Iterator 接口对象 具体工厂类:ArrayList 类;他实现了 List 接口(List 实现 Collection 接口),重写了返回 Iterator 接口对象的抽象方法 抽象产品类:Iterator 接口;定义了 Iterator 的规范 具体产品类:ArrayList 类中的迭代器 Iterator 接口的实现类
原型模式 用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型相同的新对象
结构
抽象原型类:规定了具体原型对象必须实现的 clone() 方法(JDK 里面定义好了 Cloneable 接口)
具体原型类:实现抽象原型类的 clone() 方法,它是可被复制的对象
访问类:使用具体原型类中的 clone() 方法来复制新的对象
实现
浅克隆:创建一个新对象,新对象的属性和原来对象完全相同,对于非基本类型(引用类型)属性,仍指向原有属性所指向的对象的内存地址。
深克隆:创建一个新对象,属性中引用的其他对象也会被克隆,不在指向原有对象地址
Java 中的 Object 类中提供了 clone()方法来实现浅克隆。Cloneable 接口是抽象原型类,而实现了 Cloneable 接口的类是具体原型类。具体原型重写 Object 类的 clone() 方法。
Cloneable 是一个标记接口,内部没有任何方法,它的唯一作用是作为一个“标记”:告诉 JVM 或 Object.clone() 方法:“这个类是允许被克隆的”,因为当调用 super.clone()时 JVM 会检查当前对象的类是否实现了 Cloneable 接口,如果没有实现会抛出 CloneNotSupportedException 异常
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public class Realizetype implements Cloneable { @Override public Realizetype clone () throws CloneNotSupportedException { return (Realizetype) super .clone(); } }public static void main (String[] args) { Realizetype r1 = new Realizetype (); Realizetype r2 = r1.clone(); }
案例 用原型模式生成“三好学生”奖状,除了姓名不同其他的都相同,可以用原型模式复制多个奖状出来,然后修改名字
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 public class Citation implements Cloneable { private String name; public String getName () { return name; } public void setName (String name) { this .name = name; } @Override public Citation clone () throws CloneNotSupportedException { return (Citation) super .clone(); } }public static void main (String[] args) { Citation citation = new Citation (); Citation citation1 = citation.clone(); citation.setName("张三" ); citation1.setName("李四" ); }
使用场景
对象的创建非常复杂,可以用原型模式快捷地创建对象
性能和安全要求比较高
扩展(深克隆) 通过序列化和反序列化就能实现深克隆,所有类都要实现 Serializable 接口 给奖状类内部的 name 属性改成学生类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 public class Student implements Serializable { private String name; public String getName () { return name; } public void setName (String name) { this .name = name; } }public class Citation implements Cloneable , Serializable { private Student stu; public Student getStu () { return stu; } public void setStu (Student stu) { this .stu = stu; } }public static void main (String[] args) { Citation citation = new Citation (); Student stu = new Student (); stu.setName("张三" ); citation.setStu(stu); ObjectOutputStream oos = new ObjectOutputStream (new FileOutputStream ("d:/citation.obj" )); oos.writeObject(citation); oos.close(); ObjectInputStream ois = new ObjectInputStream (new FileInputStream ("d:/citation.obj" )); Citation citation1 = (Citation) ois.readObject(); ois.close(); citation1.getStu().setName("李四" ); }
建造者模式 将一个复杂对象的构建与表示分离,使得同样的构建过程可以创建不同的表示 分离了部件的构造(由 Builder 负责)和装配(由 Director 负责)。从而可以构造出复杂的对象
建造者模式结构 建造者(Builder)模式包含如下角色:
抽象建造者类 (Builder) :这个接口规定要实现复杂对象的那些部分的创建,并不涉及具体的部件对象的创建。
具体建造者类 (concreteBuilder) :实现 Builder 接口,完成复杂产品的各个部件的具体创建方法。在构造过程完成后,提供产品的实例。
产品类 (product) :要创建的复杂对象。
指挥者类 (Director) :调用具体建造者来创建复杂对象的各个部分在指导者中不涉及具体产品的信息,只负责保证对象各部分完整创建或按某种顺序创建。
实例 创建共享单车: 它包含车架车座等组件的生产,而车架又有碳纤维,铝合金等材质,车座有橡胶,真皮等材质 这里 Bike 是产品,包含车架,车座等组件;Builder 是抽象建造者,MobikeBuilder 和 OfoBuilder 是具体的建造者
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 public class Bike { private String frame; private String seat; public String getFrame () { return frame; } public String getSeat () { return seat; } public void setFrame (String frame) { this .frame = frame; } public void setSeat (String seat) { this .seat = seat; } }public abstract class Builder { protected Bike bike = new Bike (); public abstract void buildFrame () ; public abstract void buildSeat () ; public abstract Bike createBike () ; }public class MobileBuilder extends Builder { public void buildFrame () { bike.setFrame("碳纤维车架" ); } public void buildSeat () { bike.setSeat("真皮车座" ); } public Bike createBike () { return bike; } } public class Director { private Builder builder; public Director (Builder builder) { this .builder = builder; } public Bike construct () { builder.buildFrame(); builder.buildSeat(); return builder.createBike(); } } public static void main (String[] args) { Director director = new Director (new MobileBuilder ()); Bike bike = director.construct(); }
简化 将指挥者类和抽象建造者结合 虽然简化了结构,但是也加重了抽象建造者类的职责,不太符合单一职责原则,如果 construct()方法过于复杂,还是建议封装到 Director 类中
1 2 3 4 5 6 7 8 9 10 11 12 13 public abstract class Builder { public abstract void buildFrame () ; public abstract void buildSeat () ; public abstract Bike createBike () ; public Bike construct () { this .buildFrame(); this .buildSeat(); return this .createBike(); } }
建造者模式扩展 当传入参数很多时,可以用 builder 模式来提高可读性 用 lombok 的@Builder 注解可以快速生成 builder 类
通过类名 new 一个 Builder 对象(静态内部类),Builder 对象有和 Phone 类一样的属性,然后调用 Builder 对象的方法来填充属性,最后调用 build() 方法来使用 Phone 的构造方法,将 Builder 对象作为参数,把它的属性全都传给新的 Phone 对象,最后返回这个构造好的 Phone 对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 public class Phone { private String cpu; private String screen; private String memory; private String camera; private Phone (Builder builder) { this .cpu = builder.cpu; this .screen = builder.screen; this .memory = builder.memory; this .camera = builder.camera; } public static final class Builder { private String cpu; private String screen; private String memory; private String camera; public Builder cpu (String cpu) { this .cpu = cpu; return this ; } public Builder screen (String screen) { this .screen = screen; return this ; } public Builder memory (String memory) { this .memory = memory; return this ; } public Builder camera (String camera) { this .camera = camera; return this ; } public Phone build () { return new Phone (this ); } } }public static void main (String[] args) { Phone phone = new Phone .Builder() .cpu("Intel" ) .screen("OLED" ) .memory("16G" ) .camera("12M" ) .build(); }
结构型模式 描述如何将类或对象按某种布局组成更大的结构。它分为类结构型模式和对象结构型模式。前者用继承机制来组织接口和类,后者采用组合或聚合来组合对象。
组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象结构型模式比类结构型模式更灵活。
代理模式 由于某些原因,需要给某对象提供一个代理对象,以便控制对这个对象的访问。 这时访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介
Java 中的代理按照代理类生成的时机不同,分为静态代理和动态代理。静态代理代理类是编译期就生成,而动态代理代理类是 Java 运行时动态生成。动态代理又有 JDK 代理和 CGLIB 代理
代理(Proxy)模式结构:
抽象主题(Subject)类:通过接口或抽象类声明真实主题和代理对象实现的业务方法。
真实主题(Real Subject)类:实现了抽象主体中的具体业务,是代理对象所代表真实对象,是最终要引用的对象。
代理(Proxy)类:提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能。
静态代理(编译期就生成) 卖票接口(抽象主题类):含有卖票方法 火车站(具体主题类):实现卖票接口 代收点(代理类):火车站的代理对象,实现卖票接口,内部组合一个火车站对象,同时加上收取服务费的逻辑
这个例子中测试类直接访问的是 ProxyPoint 类对象,也就是说 ProxyPoint 作为访问对象和目标对象的总结。同时对卖票方法进行增强(收取服务费)
JDK 动态代理(运行时动态生成) Java 中提供了一个动态代理类 Proxy,Proxy 并不是我们上述所说的代理对象的类,而是提供了一个创建代理对象的静态方法(newProxyInstance 方法)来获取代理对象
基于上面的例子,创建一个 ProxyFactory 类,用于创建代理对象 ProxyFactory 类中组合一个火车站对象,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 public class ProxyFactory { private TrainStation station = new TrainStation (); public SellTickets getProxyObject () { SellTickets proxyObject = (SellTickets) Proxy.newProxyInstance( station.getClass().getClassLoader(), station.getClass().getInterfaces(), new InvocationHandler () { public Object invoke (Object proxy, Method method, Object[] args) throws Throwable { System.out.println("增强方法,收取服务费20元(jdk动态代理)" ); Object obj = method.invoke(station, args); return obj; } } ); return (SellTickets) proxyObject; } }public static void main (String[] args) { ProxyFactory factory = new ProxyFactory (); SellTickets proxyObject = factory.getProxyObject(); proxyObject.sell(); }
ProxyFactory 不是代理模式中所说的代理类,他只创建了代理对象,代理类是程序在运行过程中在内存中生成的类。
动态代理就是在运行时用反射获取已经有的类,进行修改后动态生成新的类,之后就操纵这个新类的对象
代理类实现了和被代理类相同的接口
根据多态特性,执行的是代理类($Proxy0)中的方法,返回的代理对象进行了接口类型的强转,所以只会执行重写的接口类的方法SellTickets proxyObject = factory.getProxyObject();
代理类中的 sell()方法中又调用了我们自己 new 的 InvocationHandler() 的 invoke 方法
invoke 方法通过反射执行了真实对象所属类(TrainStation)中的 sell()方法
CGLIB 动态代理 如果没有定义 SellTickets 接口,只定义了 TrainStation 类,很显然 JDK 代理是无法使用了,因为 JDK 代理要求必须定义接口,对接口进行代理。
CGLIB 是一个功能强大,高性能的代码生成包。它为没有实现接口的类提供代理,为 JDK 的动态代理提供了很好的补充。
改写上面的例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 public class TrainStation { public void sell () { System.out.println("火车站卖票" ); } }public class ProxyFactory implements MethodInterceptor { private TrainStation station = new TrainStation (); public TrainStation getProxyInstance () { Enhancer enhancer = new Enhancer (); enhancer.setSuperclass(TrainStation.class); enhancer.setCallback(this ); TrainStation proxyObject = (TrainStation) enhancer.create(); return proxyObject; } public Object intercept (Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { System.out.println("代售点收取服务费(CGLIB代理)" ); Object obj = method.invoke(station, args); return obj; } }public static void main (String[] args) { ProxyFactory proxyFactory = new ProxyFactory (); TrainStation proxyObject = proxyFactory.getProxyObject(); proxyObject.sell(); }
代理模式对比 JDK 和 CGLIB 代理的区别:
JDK 代理只能代理实现了接口的类,CGLIB 代理可以代理没有实现接口的类。
CGLIB 不能代理 final 修饰的类和方法。
JDK 代理效率比 CGLIB 高,所以有接口用 JDK 代理,没有接口用 CGLIB 代理。
动态代理和静态代理:
动态代理是接口中声明的所有方法都被转移到调用处理器一个集中的方法(InvocationHandler.invoke)中处理,当接口方法数量比较多的时候,我们可以灵活处理,而不需要像静态代理那样每一个方法都进行中转
如果接口增加一个方法,静态代理模式除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法,动态代理就不用,他是动态的在内存中生成代理类
代理模式优缺点 优点:
代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用
代理对象可以扩展目标对象的功能
代理模式能将客户端与目标对象分离开,在一定程度上降低了系统的耦合度
缺点:
代理模式使用场景
远程代理:为远程的对象创建一个代理,以便从本地访问该对象 本地服务通过网络请求远程服务。为了实现本地到远程的通信,我们需要实现网络通信,处理其中可能的异常。为良好的代码设计和可维护性,我们将网络通信部分隐藏起来,只暴露给本地服务一个接口,通过该接口即可访问远程服务提供的功能,而不必过多关心通信部分的细节。就是 RPC 思想。
防火墙代理 当你将浏览器配置成使用代理功能时,防火墙就将你的浏览器的请求转给互联网;当互联网返回响应时,代理服务器再把它转给你的浏览器。
保护代理 控制一个对象访问,如果需要,可以给不同的用户提供不用级别的权限
适配器模式 将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的哪些类能一起工作 适配器模式分为类适配器模式和对象适配器模式,前者之间的耦合度比后者高
适配器模式结构 适配器模式(Adapter)包含以下主要角色
目标(Target)接口:当前业务所期待的接口,它可以是抽象类或接口
适配者(Adaptee):它是被访问和适配的现存组件库中的组件接口
适配器(Adapter):它是一个转换器,通过继承或引用适配者的对象,把适配器接口转换成目标接口,让客户按目标接口的格式访问适配者
类适配器模式 实现方式:定义一个适配器类来实现当前系统的业务接口,同时继承现有组件库中已经存在的组件
【例子】读卡器 现有一台电脑只能读取 SD 卡,而要读取 TF 卡中的内容的话就需要使用到适配器模式。创建一个读卡器,将 TF 卡中的内容读取出来
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 public interface TFCard { String readTF () ; void writeTF (String msg) ; }public class TFCardImpl implements TFCard { public String readTF () { String msg = "从TF卡中读取数据" ; return msg; } public void writeTF (String msg) { System.out.println("向TF卡中写入数据:" + msg); } }public interface SDCard { String readSD () ; void writeSD (String msg) ; }public class SDCardImpl implements SDCard { public String readSD () { String msg = "一串数据数据" ; return msg; } public void writeSD (String msg) { System.out.println("向SD卡中写入数据:" + msg); } }public class Computer { public String readSD (SDCard sdCard) { if (sdCard == null ) { throw new NullPointerException ("sdCard is not null" ); } return sdCard.readSD(); } }public class SDAdapterTF extends TFCardImpl implements SDCard { public String readSD () { String msg = "通过适配器,读取TF卡中的数据" ; return readTF(); } public void writeSD (String msg) { System.out.println("通过适配器,向TF卡中写入数据:" + msg); writeTF(msg); } }public static void main (String[] args) { Computer computer = new Computer (); String msg = computer.readSD(new SDCardImpl ()); String msg1 = computer.readSD(new SDAdapterTF ()); }
类适配器违背了合成复用原则。类适配器是客户类有一个接口规范的情况下可用,房子不可用
对象适配器模式 实现方式:对象适配器模式可采用将现有组件库中已经实现的组件引入适配器类中,该类同时实现当前系统的业务接口
改写上面的例子:就是适配器类不在继承适配者类,而是聚合适配者类的接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 public class SDAdapterTF implements SDCard { private TFCard tfCard; public SDAdapterTF (TFCard tfCard) { this .tfCard = tfCard; } public String readSD () { String msg = "通过适配器,读取TF卡中的数据" ; return tfCard.readTF(); } public void writeSD (String msg) { System.out.println("通过适配器,向TF卡中写入数据:" + msg); tfCard.writeTF(msg); } }public static void main (String[] args) { Computer computer = new Computer (); SDAdapterTF sdAdapterTF = new SDAdapterTF (new TFCardImpl ()); String msg1 = computer.readSD(sdAdapterTF); }
适配器模式应用场景
以前开发的系统存在满足新系统功能需求的类,但接口和新系统的接口不一致
使用第三方提供的组件,但组件接口定义和自己要求的接口定义不同
JDK 源码中适配器模式的使用 Reader(字符流)、InputStream(字节流)的适配使用的是 InputStreamReader InputSreamReader 继承自 java.io 包中的 Reader,对他中的抽象的未实现的方法给出实现 如:
1 2 3 4 5 6 7 public int read () throws IOException { return sd.read(); }public int read (char [] cbuf, int off, int len) throws IOException { return sd.read(cbuf, off, len); }
上述代码中的 sd(StreamDecoder 类对象),在 Sun 的 JDK 实现中,实际的方法是对 sun.nio.cs.StreamDecoder 类的同名方法的调用封装 我的理解就是 StreamDecoder 类是适配器类,它继承了 Read 抽象类(目标接口)重写了对应方法,然后聚合 InputStreamReader 类,然后在重写的方法中封装了 InputStreamReader 类的方法
装饰者模式 指在不改变现有对象结构的情况下,动态的给对象增加一些职责(即增加其额外功能)
快餐店有炒面、炒饭这些快餐,可以额外附加鸡蛋、火腿、培根这些配菜,当然加配菜需要额外加钱,每个配菜的价钱通常不太一样,那么计算总价就会显得比较麻烦。|
装饰者模式的结构
抽象组件(Component):定义一个抽象接口以规范准备接受附加责任的对象
具体组件(ConcreteComponent):实现抽象构件,通过角色为其添加一些职责
抽象装饰(Decorator):继承或实现抽象构件,并包含具体构件的实例,可通过其子类扩展具体构建的功能
具体装饰(ConcreteDecorator):实现抽象装饰角色,并给具体构件对象添加附加职责
【案例】快餐
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 public abstract class FastFood { private float price; private String desc; public FastFood (float price, String desc) { this .price = price; this .desc = desc; } public abstract float cost () ; }public class FriedRice extends FastFood { public FriedRice () { super (10 , "炒饭" ); } public float cost () { return getPrice(); } }public class FriedNoodles extends FastFood { public FriedNoodles () { super (8 , "炒面" ); } public float cost () { return getPrice(); } }public abstract class Garnish extends FastFood { private FastFood fastFood; public Garnish (FastFood fastFood, float price, String desc) { super (price, desc); this .fastFood = fastFood; } }public class Egg extends Garnish { public Egg (FastFood fastFood) { super (fastFood, 1 , "鸡蛋" ) } public float cost () { return getPrice() + fastFood.cost(); } @Override public String getDesc () { return super .getDesc() + getFastFood().getDesc(); } }public static void main (String[] args) { FriedRice food = new FriedRice (); food = new Egg (food); food = new Egg (food); }
好处:
装饰者模式可以带来比继承更加灵活性的扩展功能,使用更加方便,可以通过组合不同的装饰者对象来获取具有不同行为状态的多样化的结果。装饰者模式比继承更具良好的扩展性,完美的遵循开闭原则。继承是静态的附加责任,装饰者则是动态的附加责任
装饰类和被装饰类可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类功能
装饰者模式应用场景
当不能采用继承的方式对系统进行扩充或者采用继承不利于系统扩展和维护时
不能采用继承的情况主要有两类:
第一类是系统中存在大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长;
第二类是因为类定义不能继承(如 final 类)
在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责
当对象的功能要求可以动态地添加,也可以再动态地撤销时。
JDK 源码中装饰者模式的使用 IO 流中的包装类使用到了装饰者模式。BufferedInputStream、BufferedOutStream、BufferedReader、BufferedWriter
BufferedWriter 继承自 Writer,BufferedWriter 又聚合了 FileWriter
1 2 3 4 5 6 7 8 9 10 11 12 13 public class Demo { public static void main (String[] args) { FileWriter fw = new FileWriter ("d:/a.txt" ); BufferedWriter bw = new BufferedWriter (fw); bw.write("hello world" ); bw.close(); } }
代理和装饰者的区别
相同点:
都要实现与目标相同的业务接口
在两个类中都要声明目标对象
都可以在不修改目标类的前提下增强目标方法
不同点:
目的不同: 装饰者是为了增强目标对象 静态代理是为了保护和隐藏目标对象
获取目标对象构建的地方不同 装饰者是由外界传进来的,可通过构造方法传递 静态代理师在代理类内部创建,以此来隐藏目标对象
桥接模式 现在有一个需求,需要创建不同的图形,并且每个图形都有可能会有不同的颜色。我们可以利用继承的方式来设计类的关系。 假如我们在增加一个形状或颜色就要创建更多的类,可能会类爆炸,此时可以考虑使用桥接模式。
定义:将抽象和实现分离,是他们可以独立变化。它使用组合关系代替继承关系 来实现,从而降低了抽象和实现这两个可变维度的耦合度
桥接 (Bridge) 模式包含以下主要角色:(不好理解,还是看案例吧)
抽象化 (Abstraction) 角色:定义抽象类,并包含一个对实现化对象的引用
扩展抽象化 (Refined Abstraction) 角色是抽象化角色的子类,实现父类中的业务方法,并通过组合关系调用实现化角色中的业务方法
实现化 (lmplementor) 角色:定义实现化角色的接口,供扩展抽象化角色调用
具体实现化 (concrete lmplementor) 角色:给出实现化角色接口的具体实现。
【案例】视频播放器 需要开发一个跨平台视频播放器,可以在不同操作系统平台(如 windows 、 Mac 、 Linux 等)上播族种格式的视频文件,常见的视频格式包括 MP4、AVI、WMV 等。该播放器包含了两个维度,适合使用桥接模式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 public interface VideoFile { void decode (String fileName) ; }public class AVIFile implements VideoFile { public void decode (String fileName) { System.out.println("avi文件:" + fileName); } }public class MP4File implements VideoFile { public void decode (String fileName) { System.out.println("mp4文件:" + fileName); } }public abstract class OpratingSystem { protected VideoFile videoFile; public OpratingSystem (VideoFile videoFile) { this .videoFile = videoFile; } public abstract void play (String fileName) ; }public class Windows extends OpratingSystem { public Windows (VideoFile videoFile) { super (videoFile); } public void play (String fileName) { videoFile.decode(fileName); } }public class Mac extends OpratingSystem { public Mac (VideoFile videoFile) { super (videoFile); } public void play (String fileName) { videoFile.decode(fileName); } }public static void main (String[] args) { OpratingSystem windows = new Windows (new AVIFile ()); windows.play("战狼3.avi" ); }
这个实现了操作系统维度 和视频格式维度 的解耦,之后扩展当前维度也不影响另一个维度
好处:
桥接模式提高了系统的可扩充性,在两个变化维度中任意扩展一个维度,都不需要修改原有系统。 如:如果现在还有一种视频文件类型 WMV,我们只需要再定义一个类实现 VideoFile 接口即可,其他类不需要发生变化
实现细节对客户透明
桥接模式使用场景
当一个类存在两个独立变化的维度,且这两个维度都需要进行扩展时。
当一个系统不希望使用继承或因为多层次继承导致系统类的个数急剧增加时。
当一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性时。避免在两个层次之间建立静态的继承联系,通过桥接模式可以使它们在抽象层建立一个关联关系。
外观模式 有些人可能炒过股票,但其实大部分人都不太懂,这种没有足够了解证券知识的情况下做股票是很容易亏钱的,刚开始炒股肯定都会想,如果有个懂行的帮帮手就好,其实基金就是个“好帮手”,支付宝里就有许多的基金,它将投资者分散的资金集中起来,交由专业的经理人进行管理,投资于股票、债券、外冫领域,而基全投资的收益归持有者所有,管理机构收取一定比例的托管管理费用。
定义:
又名门面模式,是一种通过多个复杂的子系统提供一个一致的接口,从而使这些子系统更加容易被访问的模式。该模式对外有一个统一的接口,外部应用程序不用关心内部子系统的具体细节,大大降低了程序的复杂度。外观模式是迪米特法则 的典型应用
外观模式结构
外观(Facade)角色:为多个子系统对外提供一个共同的接口
子系统(SubSystem)角色:实现子系统的功能,客户可以通过外观角色访问它
【例】智能家电控制 小明的爷爷已经 60 岁了,一个人在家生活:每次都需要打开灯、打开电视、打开空调;睡觉时关闭灯、关闭电视、关闭空调;操作起来都较麻烦。所以小明给爷爷买了智能音可以通过语音直接控制这些智能家电的开启和关闭。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 public class Light { public void on () { System.out.println("打开电灯" ); } public void off () { System.out.println("关闭电灯" ); } }public class Facade { private Light light; private TV tv; private AirConditioner ac; public Facade () { light = new Light (); tv = new TV (); ac = new AirConditioner (); } public void say (String message) { if (message.contains("打开" )) { on(); } else if (message.contains("关闭" )) { off(); } else { System.out.println("我听不懂" ); } } private void on () { light.on(); tv.on(); ac.on(); } private void off () { light.off(); tv.off(); ac.off(); } }public static void main (String[] args) { Facade facade = new Facade (); facade.say("打开家电" ); facade.say("关闭家电" ); }
外观模式的优缺点 优点:
降低了子系统与客户端之间的耦合度,使得子系统的变化不会影响调用它的客户类
对客户屏蔽了子系统组件,减少了客户处理的对象数目,并使得子系统使用起来更加容易
缺点:
外观模式使用场景
对分层结构系统构建时,使用外观模式定义子系统中每层的入口点可以简化子系统之间的依赖关系。
当一个复杂系统的子系统很多时,外观模式可以为系统设计一个简单的接口供外界访问。
当客户端与多个子系统之间存在很大的联系时,引入外观模式可将它们分离,从而提高子系统的独立性和可移植性。
组合模式 文件夹的那种树形结构,有两种角色:容器(文件夹)、叶子结点(文件)。文件就只能读写,文件夹就只存储文件,但是客户端希望能一致的处理容器对象和叶子结点对象,所以引入组合模式
定义: 又名部分整体模式,是用于把一组相似的对象当作一个单一的对象·组合模式依据树形结构来组合对象,用来表示部分以及整体层次。这种类型的讠十模式属于结构型模式,它创建了对象组的树形结构。
组合模式结构
抽象根节点 (Component) 定义系统各层次对象的共有方法和属性,可以预先定义一些默认行为和属性。
树枝节点 (Composite) 定义树枝节点的行为,存储子节点,组合树枝节点和叶子节点形成一个树形结构。
叶子节点 (Leaf) 叶子节点对象其下再无分支,是系统层次遍历的最小单位。
【例】软件菜单 如下图,我们在访问别的一些管理系统时,经常可以看到类似的菜单。一个菜单可以包含菜单项(菜单项是指不再包含其他内容的菜单条目),也可以包含带有其他菜单项的菜单,因此使用组合模式描述菜单就很恰当,我们的需求是针对一个菜单,打印出其包含的所有菜单以及菜单项的名称。
无论是菜单,还是菜单项,都继承菜单组件(抽象根节点),菜单里面又聚合了菜单组件列表
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 public abstract class MenuComponent { protected String name; protected int level; public void add (MenuComponent menuComponent) { throw new UnsupportedOperationException (); } public void remove (MenuComponent menuComponent) { throw new UnsupportedOperationException (); } public MenuComponent getChild (int index) { throw new UnsupportedOperationException (); } public String getName () { return name; } public abstract void print () ; }public class Menu extends MenuComponent { private List<MenuComponent> menuComponents = new ArrayList <>(); public Menu (String name, int level) { this .name = name; this .level = level; } @Override public void add (MenuComponent menuComponent) { menuComponents.add(menuComponent); } @Override public void remove (MenuComponent menuComponent) { menuComponents.remove(menuComponent); } @Override public MenuComponent getChild (int index) { return menuComponents.get(index); } @Override public void print () { for (int i = 0 ; i < level; i++) { System.out.print("--" ); } System.out.println(name); for (MenuComponent menuComponent : menuComponents) { 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 = 0 ; i < level; i++) { System.out.print("--" ); } System.out.println(name); } }public static void main (String[] args) { MenuComponent menu1 = new Menu ("菜单管理" , 2 ); menu1.add(new MenuItem ("添加菜单" , 3 )); 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 )); MenuComponent menuRoot = new Menu ("系统管理" , 1 ); menuRoot.add(menu1); menuRoot.add(menu2); menuRoot.print(); }
组合模式的分类
透明组合模式 组合模式的标准形式,抽象根节点中声明的所有方法,确保所有构件都有相同的接口。但是叶子对象其实和容器有本质区别,叶子对象是没有下一级的,所以继承的抽象根节点很多方法都没有意义,如果直接调用,并且没有错误处理,就会报错
安全组合模式 在安全组合模式中,抽象根节点中没有声明任何用于管理成员对象的方法,而是在树枝结点 Menu 类中声明并实现这些方法。缺点就是不够透明,客户端不能完全针对抽象编程,必须有区别地对待容器和叶子对象
组合模式的优点
组合模式可以清楚地定义分层次的复杂对象,表示对象的全部或部分层次,它让客户端忽略了层次的差异,方便对整个层次结构进行控
客户端可以一致地使用一个组合结构或其中单个对象,不必关心处理的是单个对象还是整个组合结构,简化了客户端代码。
在组合模式中增加新的树枝节点和叶子节点都很方便,无须对现有类库进行任何修改,符合”开闭原则”。
组合模式为树形结构的面向对象实现提供了一种灵活的解决方案,通过叶子节点和树枝节点的递归组合,可以形成复杂的树形结构,但对树形结构的控制却非常简单。
享元模式 定义:运用共享技有效地支持大量细粒度对象的复用。它通过共享已经存在的对象来大幅度减少需要创建的对象数量、避免大量相似对象的开销,从而提高系统资源的利用率。(享元,就是共享单元)
享元模式结构 享元(Flyweight)模式中存在以下两种状态:
内部状态,即不会随着环境的改变而改变的可共享部分
外部状态,指随环境改变而改变的的不可共享的部分。享元模式的实现要领就是区分应用中的这两种状态
抽象享元角色 (Flyweight) :通常是一个接口或抽象类,在抽象享元类中声明了具体享元类公共的方法,这些方法可以向外界提供享元对象的内部数据(内部状态),同时也可以通过这些方法来设置外部数据(外部状态)。
具体享元 (concrete Flyweight) 角色:它实现了抽象享元类,称为享元对象在具体享元类中为内部状态提供了诸空间。通常我们可以结合单例模式来十具体享元类,为每一个具体享元类提供唯一的享元对象。
非享元 (Unsharab1e Flyweight) 角色:并不是所有的抽象享元类的子类都需要被共享,不能被共享的子类可设计为非共享具体享元类;当需要一个非共享具体享元类的对象时可以直接通过实例化创建。
享元工厂 (Flyweight Factory) 角色:负责创建和管理享元角色。当客户对象请求一个享元对象时,享元工厂检查系统中是否存在符合要求的享元对象,如果存在则提供给客户;如果不存在的话,则创建一个新的享元对象。
【例】俄罗斯方块
如果每个不同的方块都是一个实例对象,那这些对象就要占用很多内存空间,下面用享元模式来实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 public abstract class AbstractBox { public abstract String getShape () ; public void display (String color) { System.out.println("方块形状:" + getShape() + ",方块颜色:" + color); } }public class IBox extends AbstractBox { public String getShape () { return "I" ; } }public class BoxFactory { private HashMap<String, AbstractBox> map; private BoxFactory () { map = new HashMap <String, AbstractBox>(); map.put("I" , new IBox ()); map.put("L" , new LBox ()); map.put("O" , new OBox ()); } private static BoxFactory factory = new BoxFactory (); public static BoxFactory getInstance () { return factory; } public AbstractBox getShape (String name) { return map.get(name); } }public static void main (String[] args) { AbstractBox box1 = BoxFactory.getInstance().getShape("I" ); box1.display("灰色" ); AbstractBox box2 = BoxFactory.getInstance().getShape("L" ); box2.display("蓝色" ); AbstractBox box3 = BoxFactory.getInstance().getShape("O" ); box3.display("绿色" ); AbstractBox box4 = BoxFactory.getInstance().getShape("I" ); box4.display("灰色" ); }
享元模式的优缺点和使用场景
优点
大减少内存中相似或相同对象数量,节约系统资源,提供系统性能
享元模式中的外部状态相对独立,且不影响内部状态
缺点
为了使对象可以共享,需要将享元对象的部分状态外部化,分离内部状态和外部状态,使程序逻辑复杂
使用场景
一个系统有大量相同或者相似的对象,造成内存的大量耗费
对象的大部分状态都可以外部化,可以将这些外部状态传入对象中
在使用享元模式时需要维护一个存储享元对象的享元池,而这需要耗费一定的系统资源,因此,应当在需要多次重复使用享元对象时才值得使用享元模式。
享元模式在 JDK 源码中的应用 Integer 类使用了享元模式
1 2 3 4 5 6 7 8 9 10 11 public static void main (String[] args) { Integer i1 = 127 ; Integer i2 = 127 ; System.out.println("i1和i2是否为同一个对象?" + i1 == i2); Integer i3 = 128 ; Integer i4 = 128 ; System.out.println("i3和i4是否为同一个对象?" + i3 == i4); }
Integer 默认先创建并缓存以 -128 ~ 127 之间数的 Integer 对象,当调用 valueof 时如果参数在 -128 ~ 127 之间,则计算下标并从缓存中返回,否则创建一个新的 Integer 对象 也就是说[-128,127]之间的 Integer 对象是复用的
行为型模式 行为型模式用于描述程序在运行时复杂的流程控制,即描述多个类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,它涉及算法与对象间职责的分配。
行为型模式分为类行为模式和对象行为模式,前者采用继承机制来在类间分派行,后者采用组合或聚合在对象间分配行为。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象行为模式比类行为模式具有更大的灵活性。
模版方法模式 在面向对象程序设计过程中,程序员常常会遇到这种情况:设计一个系统时知道了算法所需的关键步骤,而且确定了这些步骤的执行顺序,但某些步骤的具体实现还未知,或者说某些步骤的实现与具体的环境相关。
例如,去银行办理业务一般要经过以下 4 个流程:取号、排队、办理具体业务、对银行工作人员进行评分等,其中取号、排队和对银行工作人员进行评分的业务对每个客户是一样的,可以在父类中实现,但是办理具体业务却因人而异,它可能是存款、取款或者转账等,可以延迟到子类中实现。
定义: 定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤。
模版方法模式结构
模板方法 (Temp1ate Method) 模式包含以下主要角色:
抽象类 (Abstract Class) :负责给出一个算法的轮廓和骨架。它由一个模板方氵去和若干个基本方法构成。
模板方法:定义了算法的骨架,按某种顺序调用其包含的基本方法。
基本方法:是实现算法各个步骤的方法,是模板方法的组成部分。基本方法又可以分为三种
具体方法 (Concrete Method):一个具体方法由一个抽象类或具体类声明并实现,其子类可以进行盖也可以直接继承。
抽象方法 (Abstract Method):一个抽象方法由抽象类声明、由其具体子类实现。(推迟到子类实现)
钩子方法 (Hook Method) :在抽象类中已经实现,包括用于判断的逻辑方法和需要子类重写的空方法两种。一般钩子方法是用于判断的逻辑方法,这类方法名一般为 isXxx,返回值类型为 boolean 类型
具体子类 (concrete C1ass) :实现抽象类中所定义的抽象方法和钩子方法,它们是一个顶级逻辑的组成步骤。
模版方法模式案例实现 【例】 炒菜
炒菜的步骤是固定的,分为倒油、热油、倒素材、倒调料品、翻炒。 其中倒油、热油都是固定的,但倒素材、倒调料品、翻炒是变化的,变化的部分由子类实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 public abstract class Cooking { public final void cookProcess () { pourOil(); heatOil(); pourVegetable(); pourSauce(); fry(); } public void pourOil () { System.out.println("倒油" ); } public void heatOil () { System.out.println("热油" ); } }public class BaoCai extends Cooking { public void pourVegetable () { System.out.println("倒包菜" ); } public void pourSauce () { System.out.println("倒辣椒" ); } }public class CaiXin extends Cooking { public void pourVegetable () { System.out.println("倒菜心" ); } public void pourSauce () { System.out.println("倒蒜蓉" ); } }public static void main (String[] args) { BaoCai baoCai = new BaoCai (); baoCai.cookProcess(); }
模版方法模式优缺点 优点:
提高代码复用性将相同部分的代码放在抽象的父噩中,而将不同的代码放入不同的子类中。
实现了反向控制通过一个父类调用其子类的操作,通过对子类的具体实现扩展不同的行为,实现了反向控制,并符合“开闭原则”
缺点:
对每个不同的实现都需要定义一个子类,这会导致类的个数增加,系统更加庞大,设计也更加抽象
父类中的抽象方法由子类实现,子类执行的结果会影响父类的结果,这导致一种反向的控制结构,它提高了代码阅读的难度。
模版方法模式适用场景
算法的整体步骤很固定,但其中个别部分易变时,这时候可以使用模板去模式,将容易变的部分抽象出来,供子类实现。
需要通过子类来决定父类算法中某个步骤是否执行,实现子类对父类的反向控制。
模版方法模式 JDK 源码解析 InputStream 类中就用了模版方法模式。在 InputStream 类中定义了多个 read()方法 一个抽象的无参 read()方法,和两个不同参数的 read()方法 有参的 read()方法,就是模版方法,里面调用了抽象无参 read()方法 调用的抽象方法,实际是子类实现的 read()方法,实现了反向控制
策略模式 有多种旅游出行方式可以选择,比如:骑自行车、坐汽车、坐火车、坐飞机 程序员开发软件也有多用 IDE 可以选择:比如:IDEA、Eclipse、Visual Studio
定义: 该模式定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的变化不会影响使用算法的客户。策略模式属于对象行为模式,它通过对算法进行封装,把使用算法的责任和算法的实现分割开来,并委派给不同的对象对这些算法进行管理。
策略模式结构 策略模式包含以下主要角色:
抽象策略 (strategy) 类:这是一个抽象角色,通常由一个接口或抽象类实现。此角色给出所有的具体策略类所需的接口。
具体策略 (concrete strategy) 类:实现了抽象策略定义的接口,提供具体的算法实现或行为。
环境 (context) 类:持有一个策略类的引用,最终给客户端调用。
策略模式案例实现 【例】促销活动
一家百货公司在定年度的促销活动。针对不同的节日(春节、中秋节、圣诞节)推出不同的促销活动,由促销员将促销活动展示给客户。类图如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 public interface Strategy { void show () ; }public class StrategyA implements Strategy { public void show () { System.out.println("买一送一" ); } }public class StrategyB implements Strategy { public void show () { System.out.println("满300减100" ); } }public class StrategyC implements Strategy { public void show () { System.out.println("满1000加一元换购任意200以下商品" ); } }public class SalesMan { private Strategy strategy; public SalesMan (Strategy strategy) { this .strategy = strategy; } public void salesManShow () { strategy.show(); } }public static void main (String[] args) { SalesMan salesMan = new SalesMan (new StrategyA ()); salesMan.salesManShow(); salesMan.setStrategy(new StrategyB ()); salesMan.salesManShow(); salesMan.setStrategy(new StrategyC ()); salesMan.salesManShow(); }
策略模式优缺点 优点:
策略类之间可以自由切换由于策略类都实现同一个接口,所以使它们之间可以自由切换。
易于扩展,增加一个新的策略只需要添加一个具体的策略类即可,基本不需要改变原有的代码,符合开闭原原则
避免使用多重条件选择语句(if else) ,充分体现面向对象设计思想。
缺点:
客户端必须知道所有的策略类,并自行决定使用哪一个策略类
策略模式将造成产生很多策略类,可以通过使用享元模式在一定程度上减少对象的数量。
策略模式使用场景
一个系统需要动态地在几种算法中选择一种时,可将每个算法封装到策略类中。
一个类定义了多种行为,并且这些行为在这个类的操作中以多个条件语句的形式出现,可将每个条件分支移入它们各自的策略类中以代替这些条件语句。
系统中各算法彼此完全独立,且要求对客户隐藏具体算法的实现细节时。·系统要求使用算法的客户不应该知道其操作的数据时,可使用策略模式来隐藏与算法相关的数据结构。
多个类只区别在表现行为不同,可以使用策略模式,在运行时动态选择具体要执行的行为。
策略模式 JDK 源码解析 就比如 Arrays.sort()方法,里面可以传不同的比较器来实现不同的排序,这些不同的比较器就是不同的策略
命令模式 顾客把订单交给服务员,服务员拿了订单之后放在柜台喊一声订单来了,然后厨师根据订单准备餐点
定义:
将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开,这样两者之间通过命令对象进行沟通,这样方便将命对象进行存储、传递、调用、增加与管理
命令模式结构
抽象命令类 (command) 角色:定义命令的接口,声明执行的方法。
具体命令 (concrete command) 角色:具体的命令,实现命令接口;通常会持有接町者,并调用接收者的功能来完成命令要执行的操作。
实现者/接收者 (Receiver) 角色:接收者,真正执行命令的对象。任何类都可能成为一个接收者,只要它能够实现命令要求实现的相应功能。
调用者/请求者 (lnvoker) 角色:要求命令对象执行请求,通常会持有命令对象,可以持有很多的命令对象。这个是客户端真正触发命令并要求命令执行相应操作的地方,也就是说相当于使用命令对象的入口。
命令模式案例实现 【例】上面的点餐案例
服务员:调用者角色,由他来发起命令 厨师:接收者角色,执行命令 订单:命令中包含订单
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 public class Order { private int diningTable; private Map<String, Integer> foodDir = new HashMap <String, Integer>(); }public class Chef { public void makeFood (String name, int num) { System.out.println("开始做:" + name + ",数量:" + num); } }public interface Command { void execute () ; }public class OrderCommand implements Command { private Chef chef; private Order order; public OrderCommand (Chef chef, Order order) { this .chef = chef; this .order = order; } public void execute () { System.out.println(order.getDiningTable() + "桌的订单:" ); Map<String, Integer> foodDir = order.getFoodDir(); Set<String> keySet = foodDir.keySet(); for (String foodName : keySet) { receiver.makeFood(foodName, foodDir.get(foodName)); } System.out.println(order.getDiningTable() + "桌的订单处理完毕" ); } }public class Waiter { private List<Command> commands = new ArrayList <Command>(); public void setCommand (Command command) { commands.add(command); } public void orderUp () { System.out.println("订单来了..." ); for (Command command : commands) { if (command != null ) { command.execute(); } } } }public static void main (String[] args) { Order order1 = new Order (); order1.setDiningTable(1 ); order1.getFoodDir().put("西红柿鸡蛋面" , 1 ); order1.getFoodDir().put("小杯可乐" , 2 ); Order order2 = new Order (); order2.setDiningTable(2 ); order2.getFoodDir().put("尖椒肉丝盖饭" , 1 ); order2.getFoodDir().put("小杯雪碧" , 2 ); Chef chef = new Chef (); OrderCommand cmd1 = new OrderCommand (chef, order1); OederCommand cmd2 = new OrderCommand (chef, order2); Waitor invoke = new Waiter (); invoke.setCommand(cmd1); invoke.setCommand(cmd2); invoke.orderUp(); }
命令模式优缺点 优点:
降低系统的合度。命令模式能将调用操作的对象与实现该操作的对象解耦。
增加或删除命非常方便。采用命令模式增加与删除命令不会影响其他类,它满足、“开闭原则”,对扩展比较灵活。
可以实现宏命令。命令模式可以与组合模式结合,将多个命令装配成一个组合命令,即宏命令。
方便实现 Undo 和 Redo 操作。命令模式可以与后面介绍的备忘录模式结合,实现命令的撤销与恢复。
缺点:
使用命令模式可能会导致某些系统有过多的具体命令类。
系统结构更加复杂。
命令模式使用场景
系统需要将请求调用者和请求接收者解耦,使得调用者和接收者不直接交互
系统需要在不同的时间指定请求、将请求排队和执行请求
系统需要支持命令的撤销 (Undo) 操作和恢复 (Redo) 操作
命令模式 JDK 源码解析 Runable 是一个典型命令模式,Runnable 担当了命令的角色,Thread 充当的是调用者,start 方法就是其执行方法
责任链模式 在现实生活中,常常会出现这样的事例:一个请求有多个对象可以处理,但每个对象的处理条件或权限不同。例如,公司员工请假,可批假的领导有部门负责人、副总经理、总经理等,但每个领导能批准的天数不同,员工须根据自己要请假的天数去找不同的领导签名,也就是说员工必须记住每个领导的姓名、电话和地址等信息,这增加了难度,这样的例子还有很多,如找领导出差报销、生活中的”击鼓传花”游戏等。
定义: 又名职责链模式,为了避免请求发送者与多个请求处理者耦合在一起,将所有请求的处理者通过前一对象记住其下一个对象的引用而连成一条链;当有请求发生时,可将请求沿着这条链传递,直到有对象处理它为止。
责任链模式结构
抽象处理者 (Handler) 角色:定义一个处理请求的接口,包含抽象处理方法和一个后继连接。
具体处理者 (concrete Handler) 角色:实现抽象处理者的处理方法,判断能否处理本次请求,如果可以处理请求则处理,否则将该请求转给它的后继者。
客户类 (Client) 角色:创建处理链,并向链头的具体处理者对象提交请求,它不关心处理细节和请求的传递过程。
责任链模式案例实现 现需要开发一个请假流程控制系统。请假一天以下的假只需要小组长同意即可;请假 1 天到 3 天的假还需要部门经理同意;请求 3 天到 7 天还需要总经理同意才行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 public class LeaveRequest { private String name; private int num; private String content; }public abstract class Handler { protected final static int NUM_ONE = 1 ; protected final static int NUM_THREE = 3 ; protected final static int NUM_SEVEN = 7 ; private int numStart; private int numEnd; private Handler nextHandler; public Handler (int numStart, int numEnd) { this .numStart = numStart; this .numEnd = numEnd; } public void setNextHandler (Handler nextHandler) { this .nextHandler = nextHandler; } protected abstract void handleLeave (LeaveRequest leave) ; public final void submit (LeaveRequest leave) { this .handleLeave(leave); if (this .nextHandler != null && leave.getNum() > this .numEnd) { this .nextHandler.submit(leave); } else { System.out.println("流程结束!" ); } } }public class GroupLeader extends Handler { public GroupLeader () { super (0 , Handler.NUM_ONE); } protected void handleLeave (LeaveRequest leave) { System.out.println(leave.getName() + "请假" + leave.getNum() + "天,请假理由是:" + leave.getContent()); System.out.println("小组长审批:同意" ); } }public class Manager extends Handler { public Manager () { super (Handler.NUM_ONE, Handler.NUM_THREE); } protected void handleLeave (LeaveRequest leave) { System.out.println(leave.getName() + "请假" + leave.getNum() + "天,请假理由是:" + leave.getContent()); System.out.println("部门经理审批:同意" ); } }public class Manager extends Handler { public Manager () { super (Handler.NUM_THREE, Handler.NUM_SEVEN); } protected void handleLeave (LeaveRequest leave) { System.out.println(leave.getName() + "请假" + leave.getNum() + "天,请假理由是:" + leave.getContent()); System.out.println("总经理审批:同意" ); } }public static void main (String[] args) { LeaveRequest leave = new LeaveRequest ("小李" , 1 , "身体不健康" ); GroupLeader groupLeader = new GroupLeader (); Manager manager = new Manager (); GeneralManager generalManager = new GeneralManager (); groupLeader.setNextHandler(manager); manager.setNextHandler(groupLeader); groupLeader.submit(leave); }
责任链模式优缺点 优点:
降低了对象之间的耦合度 该模式降低了请求发送者和接收者的耦合度
增强了系统的可扩展性可以根据需要增加新的请理类,满足开闭原则
增强了给对象指派职责的灵活性当工作流程发生变化,可以动态地改变链内的成员或者修改它们的次序,也可动态地新增或者删除责任。
责任链简化了对象之间的连接一个对象只需保持一个指向其后继者的引用,不需保持其他所有处理者的引用,这避免了使用众多的 if…else 语句。
责任分担每个类只需要处理自己该处理的工作,不能处理的传递给下一个对象完成,明确各类的责任范围,符合类的单一职责原则。
缺点:
不能保证每个请求一定被处理。由于一个请求没有明确的接收者,所以不能保证它一定会被处理,该请求可能一直传到链的末端都得不到处理。
对比较长的职责链,请求的处理可能涉及多个处理对象,系统性譖受到一定影响。
职责链建立的合理性要靠客户端来保证,增加了客户端的复杂性,可能会由于职责链的错误设置而导致系统出错,如可能会造成循环调用。
责任链模式源码解析 在 JavaWeb 应用开发中,FilterChain 类就是一种职责链(过滤器)模式的典型应用
就是不同的 Filter 对象组成职责链,每一个进行自己的逻辑,然后传递给下一个 Filter 对象,这样,当请求进来的时候,就会按照顺序依次执行,最后返回给用户。
每个过滤器都可以进行前置处理和后置处理
状态模式 【反例】通过按钮控制一个电梯的状态,一个电梯有开门状态,关门状态,停止状态,运行状态。每一种状态改变,都有可能要根据其他状态来更新处理。例如,如果电梯门现在处于运行时状态,就不能进行开门操作,而如果电梯门是停止状态,就可以执行开门操作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 public interface ILift { int OPENING_STATE = 1 ; int CLOSING_STATE = 2 ; int RUNNING_STATE = 3 ; int STOPPING_STATE = 4 ; void setState (int state) ; void open () ; void close () ; void run () ; void stop () ; }public class Lift implements ILift { private int state; public void setState (int state) { this .state = state; } public void open () { switch (state) { case OPENING_STATE: break ; case CLOSING_STATE: System.out.println("电梯门开启" ); setState(OPENING_STATE); break ; case STOPPING_STATE: System.out.println("电梯门开启" ); setState(OPENING_STATE); break ; case RUNNING_STATE: break ; } } public void close () { } public void run () { } public void stop () { } }public static void main (String[] args) { Lift lift = new Lift (); lift.setState(Lift.OPENING_STATE); lift.open(); lift.close(); lift.run(); lift.stop(); }
问题分析:
使用了的大量的 switch…case,可读性差
拓展性差,加个状态都要改
状态模式定义 对有状态的对象,把复杂的”判断逻辑”提取到不同的状态对象中,允许状态对象在其内部状态发生改变时改变其行为。
状态模式结构
环境 (Context) 角色:也称为上下文,它定义了客户程序需要的接口,维护一个当前状态,并将与状态相关的操作委托给当前状态对
抽象状态 (State) 角色:定义一个接口,用以封装环境对象中的特定状态所对应的行为。
具体状态 (concrete State) 角色:实现抽象状态所对应的行为。
状态模式案例实现 对上面的电梯案例使用状态模式进行改进
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 public abstract class LiftState { protected Context context; public void setContext (Context context) { this .context = context; } public abstract void open () ; public abstract void close () ; public abstract void run () ; public abstract void stop () ; }public class OpeningState extends LiftState { public void open () { System.out.println("电梯门开启" ); } public void close () { super .context.setLiftState(Context.CLOSING_STATE); super .context.close(); } public void run () { } public void stop () { } }public class Context { public final static OpeningState OPENING_STATE = new OpeningState (); public final static ClosingState CLOSING_STATE = new ClosingState (); public final static RunningState RUNNING_STATE = new RunningState (); public final static StoppingState STOPPING_STATE = new StoppingState (); private LiftState liftState; public LiftState getLiftState () { return liftState; } 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 boid main (String[] args) { Context context = new Context (); context.setLiftState(Context.RUNNING_STATE); context.open(); context.close(); context.run(); context.stop(); }
就是把对应状态的行为放在状态类里,把对象的行为放在环境角色(context)里,环境角色里会聚合状态对象
状态模式优缺点 优点:
将所有与某个有关的行为放到一个类中,并且可以方便地增加新的状态,只需要改变对象状态即可改变对象的行为。
允许状态转换逻辑与状态对象合成一体,而不是某一个巨大的条件语句块。
缺点:
状态模式的使用必然会增加系统类和对象的个数。
状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱。
状态模式对”开闭原则”的支持并不太好
状态模式使用场景
当一个对象的行为取决于它的状态,并且它必须在运行时根据状态改变它的行为时,就可以考虑使用状态模式。
一个操作中含有庞大的分支结构并且这些分支决定于对象的状态时
观察者模式 定义: 又称为发布-订阅(Publish/Subscribe)模式,它定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态变化时,会通知所有的观察者对象,使他们能够自动更新自己
观察者模式结构
抽象主题(抽象被观察者),抽象主题角色把所有观察者对象保存在一个集合里,每个主题都可以有任意数量的观察者,抽象主题提供一个接口,可以增加和删除观察者对象。
具体主题(具体被观察者),该角色将有关状态存入具体观察者对象,在具体主题的内部状态发生改变时,给所有注册过的观察者发送通知。
抽象观察者,是观察者的抽象类,它定义了一个更新接口,使得在得到主题更改通知时更新自己。·
具体观察者,实现抽象观察者定义的更新接口,以便在得到主题更改通知时更新自身的状态。
观察者模式案例实现 【例】微信公众号 在使用微信公众号时,大家都会有这样的体验,当你关注的公众号中有新内容更新的话,它就会推送给关注公众号的微信用户端。我们使用观察者模式来模拟这样的场景,微信用户就是观察者,微信公众号是被观察者,有多个的微信用户关注了程序猿这个公众号。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 public interface Subject { void attach (Observer observer) ; void detach (Observer observer) ; void notify (String message) ; }public interface Observer { void update (String message) ; }public class SubscriptionSubject implements Subject { private List<Observer> weiXinUserList = new ArrayList <>(); public void attach (Observer observer) { weiXinUserList.add(observer); } public void detach (Observer observer) { weiXinUserList.remove(observer); } public void notify (String message) { for (Observer observer : weiXinUserList) { observer.update(message); } } } public class WeiXinUser implements Observer { private String name; public WeiXinUser (String name) { this .name = name; } public void update (String message) { System.out.println(name + " 收到微信推送:" + message); } } public static void main (String[] args) { Subject subject = new SubscriptionSubject (); subject.attach(new WeiXinUser ("小王" )); subject.attach(new WeiXinUser ("小张" )); subject.attach(new WeiXinUser ("小李" )); subject.notify("程序猿,你关注的公众号有更新" ); }
观察者模式优缺点 优点:
降低了目标与观察者之间的耦合关系,两者之间是抽象耦合关系
被观察者发送通知所有注册的观察者都会收到信息〔可以实现广播机制
缺点:
如果观察者非常多的话,那么所有的观察者收到被观察者发送的通知会耗时
如果被观察者有循环依赖的话,那么被观察者发 i 关涌知会使观察者循环调用,会导致系统崩溃
观察者模式使用场景
对象之间存在一对多的关系,一个对象的状态发生改变时,需要同时改变其他对象
当一个抽象模型有两个方面,其中一个方面依赖于另一方面时
观察者模式 JDK 中提供的实现 在 java 中,通过 java.util.Observable 类和 java.util.Observer 接口定义了观察者模式,只要实现它们的子类就可以编写 Java 观察者模式实例。
【例】警察抓小偷 警察抓小偷也可以使用观察者模式来实现,警察是观察者,小偷是被观察者。代码如下 小偷是一个被观察者,所以需要继承 Observable 类
小偷类,被观察者
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public class Thief extends Observable { private String name; public Thief (String name) { this .name = name; } public void setName (String name) { this .name = name; } public void steal () { System.out.println("我偷了东西,来个人抓我" ); super .setChanged(); super .notifyObservers(); } }
警察类,观察者,实现 Observer 接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public class Policemen implements Observer { private String name; public Policemen (String name) { this .name = name; } public void setName (String name) { this .name = name; } public void getName () { return name; } @Override public void update (Observable o, Object arg) { System.out.println("警察:" + ((Thief)o).getName() + "被抓住了" ); } }
客户端代码
1 2 3 4 5 6 7 8 9 10 public static void main (String[] args) { Thief t = new Thief ("小偷" ); Policemen p = new Policemen ("警察" ); t.addObserver(p); t.steal(); }
中介者模式 假设有 123456 对象如果他们都是互相关联的就不利于维护,这时候可以每个对象都和中介者对象关联
定义: 又叫调停模式,定义一个中介角色来封装一系列对象之间的交互,使原有对象之间的耦合松散,并且可以独立地改变他们之间的交互
中介者模式结构
抽象中介者 (Mediator) 角色:它是中介者的接口,提供了同事对象注旺与转发同事对象信息的抽象方法。
具体中介者 (ConcreteMediator) 角色:实现中介者接口,定义一个 List 来管理同事对象,协调各个同事角色之间的交互关系,因此它依赖于同事角色。
抽象同事类 (Colleague) 角色:定义同事类的接口,保存中介者对象,提供同事对象交互的抽象方法,实现所有相互影响的同事类的公共功能。
具体同事类 (Concrete Colleague) 角色:是抽象同事类的实现者,当需要与其他同事对象交互时,由中介者对象负责后续交互。
中介者模式案例实现 【例】租房 现在租房基本都甬过房屋中介,房主将房屋托管给房屋中介,而租房者从房屋中介获取房屋信息。房屋中介充当租房者与房屋所有者之间的中介者。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 public abstract class Mediator { public abstract void connect (String message,Person person) ; }public abstract class Person { protected String name; protected Mediator mediator; public Person (String name,Mediator mediator) { this .name = name; this .mediator = mediator; } }public class Tenant extends Person { public Tenant (String name, Mediator mediator) { super (name, mediator); } public void constact (String message) { mediator.connect(message,this ); } public void getMessage (String message) { System.out.println("租房者" + name + "获取信息:" + message); } }public class HouseOwner extends Person { public HouseOwner (String name, Mediator mediator) { super (name, mediator); } public void constact (String message) { mediator.connect(message,this ); } public void getMessage (String message) { System.out.println("房东" + name + "获取信息:" + message); } }public class MediatorStructure extends Mediator { private HouseOwner houseOwner; private Tenant tenant; public void connect (String message,Person person) { if (person == houseOwner) { tenant.getMessage(message); } else { houseOwner.getMessage(message); } } }public static void main (String[] args) { MediatorStructure mediator = new MediatorStructure (); Tenant tenant = new Tenant ("张三" ,mediator); HouseOwner houseOwner = new HouseOwner ("李四" ,mediator); mediator.setHouseOwner(houseOwner); mediator.setTenant(tenant); tenant.constact("我要租房" ); houseOwner.constact("我有房,你租吗" ); }
中介者模式优缺点 优点:
松散耦合中介者模式通过把多个同事对象之间的交互封装到中介者对象里面,从而使得同事对象之间松散耦合,基本上可以做到互补依赖。这样一来,同事对象就可独立地变化和复用,而不再像以前那样“牵一处而动全身“了。
集中控制交互多个同事对象的交互,被封装在中介者对象里面集中管理,使得这些交互行为发生变化的时候,只需要修改中介者对象就可以了,当然如果是已经做好的系统,那么就扩展中介者对象,而各个同事类不需要做修改。
一对多关联转变为一对一的关联没有使用中介者模式的时候,同事对象之间的关系通常是一对多的,引入中介者对象以后,中介者对象和同事对象的关系通常变成双向的一对一,这会让对象的关系更容易理解和实现。
缺点:
当同事类太多时,中介者的职责将很大,它会变得复杂而庞大,以至于系统难以维护。
中介者模式使用场景
系统中对象之间存在复杂的引用关系,系统结构混乱且难以理解。
当想创建丨运行于多个类之间的对象,又不想生成新的子类时。
迭代器模式 定义: 提供一个对象来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示
迭代器模式结构
抽象聚合 (Aggregate) 角色:定义存储、添加、删除聚合元素以及创建迭代器对象的接口。
具体聚合(ConcreteAggregate)角色:实现抽象聚合类,返回一个具体迭代器的实例。
抽象迭代器 (lterator) 角色:定义访问和遍历聚合元素的接口,通常包含 hasNext() 、 next() 等方法。
具体迭代器 (Concretelterator) 角色:实现抽象迭代器接口中所定义的方法,完成对聚合对象的遍历,记录遍历的当前位置。
迭代器模式案例实现 【例】定义一个可以存储学生对象的容器对象,将遍历该容器的功能交由迭代器实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 public class Student { private String name; private int age; public String toString () { return "学生姓名:" + name + ",学生年龄:" + age; } public Student (String name, int age) { this .name = name; this .age = age; } public Student () {} }public interface StudentIterator { boolean hasNext () ; Student next () ; }public class StudentIteratorImpl implements StudentIterator { private List<Student> list; private int position = 0 ; public StudentIteratorImpl (List<Student> list) { this .list = list; } public boolean hasNext () { return position < list.size(); } public Student next () { Student currentStudent = list.get(position); position++; return currentStudent; } }public interface StudentAggregate { void addStudent (Student student) ; void removeStudent (Student student) ; StudentIterator getStudentIterator () ; }public class StudentAggregateImpl implements StudentAggregate { private List<Student> list = new ArrayList <Student>(); public void addStudent (Student student) { list.add(student); } public void removeStudent (Student student) { list.remove(student); } public StudentIteratorImpl getStudentIterator () { return new StudentIteratorImpl (list); } }public static void main (String[] args) { StudentAggregate studentAggregate = new StudentAggregateImpl (); studentAggregate.addStudent(new Student ("张三" , 18 )); studentAggregate.addStudent(new Student ("李四" , 19 )); studentAggregate.addStudent(new Student ("王五" , 20 )); studentAggregate.addStudent(new Student ("赵六" , 21 )); StudentIterator studentIterator = studentAggregate.getStudentIterator(); while (studentIterator.hasNext()) { Student student = studentIterator.next(); System.out.println(student.toString()); } }
聚合对象用来存储、添加、删除对象以及获取迭代器对象 迭代器用来遍历
迭代器模式优缺点 优点:
它支持以不同的方式遍历一个聚合对象,在同一个聚合对象上可以定义多种遍历方式。在迭代器模式中只需要用一个不同的迭代器来替换原有迭代器即可改变遍历算法,我们也可以自己定义迭代器子类以支持新的遍历方式。
迭代器简化了聚合类。由于引入了迭代器,在原有的聚合对象中不需要再自行提供数据遍历等方法,这样可以简化聚合类的设计。
在迭代器模式中,由于引入了抽象层,增加新的聚合类和迭代器类都很方便,无须修改原有代码,满足”开闭原则”的要求。
缺点:
增加了类的个数,这在一定程度上增加了系统的复杂性。
迭代器模式使用场景
当需要为聚合对象提供多种遍历方式时。
当需要为遍历不同的聚合结构提供一个统一的接口时。
当访问一个聚合对象的内容而无须暴露其内部细节的表示时。
迭代器模式在 JDK 源码中的解析 迭代器模式在 JAVA 很多集合类中都被广泛应用
1 2 3 4 5 List<String> list = new ArrayList <>(); Iterrator<String> iterator = list.iterator(); while (iterator.hasNext()) { System.out.println(iterator.next()); }
以 ArrayList 举例说明
List:抽象聚合类
ArrayList:具体聚合类
Iterator:抽象迭代器
List.iterator():返回的是 Iterator 接口的子实现类对象
访问者模式 定义: 封装一些作用于某种数据结构中的各元素的操作,它可以在不改变这个数据结构的前提下定义作用于这些元素的新的操作。
访问者模式结构
抽象访问者 (visitor) 角色:定义了对每一个元素 (Element) 访问的行为,它的参数就是可以访问的元素,它的方法个数理论上来讲与元素类个数(Element 的实现类个数)是一样的,从这点不难看出,访问者模式要求元素类的个数不能改变。
具体访问者 (ConcreteVisitor) 角色:给出对每一个元素类访问时所产生的具体行为··抽象元素 (Element) 角色:定义了一个接受访问者的方法(accept),其意义是指,每一个元素都要可以被访问者访问。
具体元素 (concreteE1ement) 角色:提供接受访问方法的具体实现,而这个具体的实现,通常情况下是使用访问者提供的访问该元素类的方法。
对象结构 (Object structure) 角色:定义当中所提到的对象结构,对象结构是一个抽象表述具体点可以理解为一个具有容器性质或者复合对象特性的类,它会含有一组元素,并且可以迭代这些元素,供访问者访问。
访问者模式案例实现 【例】给宠物喂食
给宠物猫宠物狗喂食,主人可以喂,其他人也可以喂
访问者角色:给宠物喂食的人
具体访问者角色:主人、其他人
抽象元素角色:动物抽象类
具体元素角色:宠物猫、宠物狗
结构对象角色:主人家
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 public interface Person { void feed (Cat cat) ; void feed (Dog dog) ; }public interface Animal { void accept (Person person) ; }public class Cat implements Animal { public void accept (Person person) { person.feed(this ); System.out.println("猫吃饭" ); } }public class Dog implements Animal { public void accept (Person person) { person.feed(this ); System.out.println("狗吃饭" ); } }public class Owner implements Person { public void feed (Cat cat) { System.out.println("主人喂食宠物猫" ); } public void feed (Dog dog) { System.out.println("主人喂食宠物狗" ); } }public class Other implements Person { public void feed (Cat cat) { System.out.println("其他人喂食宠物猫" ); } public void feed (Dog dog) { System.out.println("其他人喂食宠物狗" ); } }public class Home { private List<Animal> animalList = new ArrayList <>(); public void add (Animal animal) { animalList.add(animal); } public void action (Person person) { for (Animal animal : animalList) { animal.accept(person); } } }public static void main (String[] args) { Home home = new Home (); home.add(new Cat ()); home.add(new Dog ()); Owner owner = new Owner (); home.action(owner); }
访问者模式优缺点 优点:
扩展性好,在不修改对象结构中的元素的情况下,为对象结构中的元素添加新的功能
复用性好,通过访问者来定义整个对象结构通用的功能,从而提高复用程度
分离无关行为,通过访问者来分离无关的行为,把相关的行为封装在一起,构成一个访问者,这样每一个访问者的功能都比较单一
缺点:
对象结构变化很困难
在访问者模式中,每增加一个新的元素类,都要在每一个具体访问者类中增加相应的具体操作,这违背了”开闭原则“。
违反了依赖倒置原则,访问者模式依赖了具体类,而没有依赖抽象类·
访问者模式使用场景
对象结构相对稳定,但其操作算法经常变化的程序
对象结构中的对象需要提供多种不同且不相关的操作,而且要避免让这些操作的变化影响对象的结构
备忘录模式 定义: 又叫快照模式,在不破坏封装性的前提下,获取一个对象的内部状态,并在该对象之外保存这个状态,以便以后当前需要时能将该对象恢复到原先保存的状态
备忘录模式结构
发起人 (Originator) 角色:记录当前时刻的内部状态信息,提供创建备忘录和恢复备忘录数据的功能,实现其他业务功能,它可以访问备忘录里的所有信息。
备忘录 (Memento) 角色:负责存储发起人的内部状态,在需要的时候提亻共这些内部状态给发起人。
管理者 (Caretaker) 角色:对备忘录进行管理,提供保存与获取备忘录的功能,但其不能对备忘录的内容进行访问与修改。
备忘录有两个等效的接口:
窄接口:管理者 (Caretaker) 对象(和其他发起人对象之外的任何对象)看到的是备忘录的窄接口 (narror lnterface) ,这个窄接口只允许把把备忘录对象传给其他的对象。
宽接口:与管理者看到的窄接口相反,发起人对象可以看到一个宽接口(wide lnterface) ,这个宽接口允许它读取所有的数据,以便根据这些数据恢复这个发起人对象的内部状态。
备忘录模式案例实现 【例】游戏打 BOSS 游戏中的某个场景,游戏角色有生命力、攻击力、防御力等数据,在打 BOSS 前和后一定会不一样的,我们允许玩家如果感觉与 BOSS 决斗的效果不理想,可以让游戏恢复到决斗之前的状态
要实现上述案例,有两种方式:
“白箱”备忘录模式 备忘录角色对任何对象都提供一个接口,即宽接口,备忘录角色的内部所存储的状态对所有对象公开
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 public class GameRole { private int vit; private int atk; private int def; public void initState () { this .vit = 100 ; this .atk = 100 ; this .def = 100 ; } public void fight () { this .vit = 0 ; this .atk = 0 ; this .def = 0 ; } public RoleStateMemento saveState () { return new RoleStateMemento (vit, atk, def); } public void recoverState (RoleStateMemento memento) { this .vit = memento.getVit(); this .atk = memento.getAtk(); this .def = memento.getDef(); } public void stateDisplay () { System.out.println("角色生命力:" + vit); System.out.println("角色攻击力:" + atk); System.out.println("角色防御力:" + def); } }public class RoleStateMemento { private int vit; private int atk; private int def; public RoleStateMemento (int vit, int atk, int def) { this .vit = vit; this .atk = atk; this .def = def; } public int RoleStateMemento () {} }public class RoleStateCaretaker { private RoleStateMemento memento; public RoleStateMemento getMemento () { return memento; } public void setMemento (RoleStateMemento memento) { this .memento = memento; } }public static void main (String[] args) { System.out.println("--------大战Boss前--------" ); GameRole gameRole = new GameRole (); gameRole.initState(); gameRole.stateDisplay(); RoleStateCaretaker roleStateCaretaker = new RoleStateCaretaker (); roleStateCaretaker.setMemento(gameRole.saveState()); System.out.println("--------大战Boss后--------" ); gameRole.fight(); gameRole.stateDisplay(); System.out.println("--------恢复之前状态--------" ); gameRole.recoverState(roleStateCaretaker.getMemento()); gameRole.stateDisplay(); }
“黑箱”备忘录模式 备忘录黑色对发起人对向提供一个宽接口,而为其他对象提供一个窄接口。在 Java 中,实现双重接口的办法就是将备忘录类 设计成发起人类的内部成员类
窄接口 Mememento,这是一个标识接口,因此没有定义出任何方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 public interface Memento { }public class GameRole { private int vit; private int atk; private int def; public void initState () { this .vit = 100 ; this .atk = 100 ; this .def = 100 ; } public void fight () { this .vit = 0 ; this .atk = 0 ; this .def = 0 ; } public Memento saveState () { return new RoleStateMemento (vit, atk, def); } public void recoverState (Memento memento) { RoleStateMemento memento = (RoleStateMemento) memento; this .vit = memento.getVit(); this .atk = memento.getAtk(); this .def = memento.getDef(); } public void stateDisplay () { System.out.println("角色生命力:" + vit); System.out.println("角色攻击力:" + atk); System.out.println("角色防御力:" + def); } private class RoleStateMemento implements Mememento { private int vit; private int atk; private int def; public RoleStateMemento (int vit, int atk, int def) { this .vit = vit; this .atk = atk; this .def = def; } } }public class RoleStateCaretaker { private Memento memento; public Memento getMemento () { return memento; } public void setMemento (Memento memento) { this .memento = memento; } }public static void main (String[] args) { System.out.println("--------大战Boss前--------" ); GameRole gameRole = new GameRole (); gameRole.initState(); gameRole.stateDisplay(); RoleStateCaretaker roleStateCaretaker = new RoleStateCaretaker (); roleStateCaretaker.setMemento(gameRole.saveState()); System.out.println("--------大战Boss后--------" ); gameRole.fight(); gameRole.stateDisplay(); System.out.println("--------恢复之前状态--------" ); gameRole.recoverState(roleStateCaretaker.getMemento()); gameRole.stateDisplay(); }
就是游戏角色类里有一个私有内部类实现了备忘录接口,然后管理者负责存储这个私有内部类的接口引用(窄接口) 游戏角色要恢复状态时,管理者将引用传递进来,因为是私有内部类,所以只有游戏角色类内部能将接口引用向下转型成实现类(也就是私有内部类)然后赋值
备忘录模式优缺点 优点:
提供了一种可以恢复状态的机制,当用户需要时能够比较方便的将数据恢复到某个历史状态
实现了内部状态的封装。除了创建它的发起人之外,其他对象都不能够访问这些状态信息
简化了发起人。发起人不需要管理和保存其内部状态的各个备份,所有状态信息都保存在备忘录中,并由管理者进行管理,这符合单一职责原则
缺点:
资源消耗大,如果要保存的内部状态信息过多或者特别频繁,将会占用比较大的内存资源
备忘录模式使用场景
需要保存与恢复数据的场景,如玩游戏时中间结果的存档功能
需要提供一个可回滚操作的场景,如 Word、记事本、Photoshop、idea 等软件在编辑时按 Ctrl+Z 时的撤销操作,还有数据库中事务操作
解释器模式 定义: 给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。
解释器模式结构
抽象表达式 (Abstract Expression) 角色:定义解释器的接口,约定解释器的解释操作,主要含解释方法 interpret()。
终结符表达式 (Terminal Expression) 角色:是抽象表达式的子类,用来实现文法中与终结符相关的操作,文法中的每一个终结符都有一个具体终结表达式与之相对应
非终结符表达式 (Nonterminal Expression) 角色:也是抽象表达式的子类,用来实现文法中与非终结符相关的操作,文法中的条规则都对应于一个非终结符表达式。
环境 (Context) 角色:通常包含各个解释器需要的数据或是公共的功能,一般用来传递被所有解释器共享的数据,后面的解释器可从这里获取这些值。
客户端 (Client) :主要任务是将需要分析的句子或表达式转换成使用解释器对象描述的抽象语法树,然后调用解释器的解释方法,当然也可以通过环境角色间接访问解释器的解释方法。
解释器模式案例实现 【例】设计实现加减法的软件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 public abstract class AbstractExpression { public abstract int interpret (Context context) ; }public class Context { private Map<Variable, Integer> map = new HashMap <>(); public void assign (Variable var , int value) { map.put(var , value); } public int getValue (Variable var ) { return map.get(var ); } }public class Variable extends AbstractExpression { private String name; public Variable (String name) { this .name = name; } public int interpret (Context context) { return context.getValue(this ); } @Override public String toString () { return name; } }public class Plus extends AbstractExpression { private AbstractExpression left; private AbstractExpression right; public Plus (AbstractExpression left, AbstractExpression right) { this .left = left; this .right = right; } public int interpret (Context context) { return left.interpret(context) + right.interpret(context); } @Override public String toString () { return "(" + left.toString() + " + " + right.toString() + ")" ; } }public class Minus extends AbstractExpression { private AbstractExpression left; private AbstractExpression right; public Minus (AbstractExpression left, AbstractExpression right) { this .left = left; this .right = right; } public int interpret (Context context) { return left.interpret(context) - right.interpret(context); } @Override public String toString () { return "(" + left.toString() + " - " + right.toString() + ")" ; } }public static void main (String[] args) { Context context = new Context (); Variable a = new Variable ("a" ); Variable b = new Variable ("b" ); Variable c = new Variable ("c" ); Variable d = new Variable ("d" ); context.assign(a, 1 ); context.assign(b, 2 ); context.assign(c, 3 ); context.assign(d, 4 ); AbstractExpression expression = new Minus (a, new Plus (new Minus (b, c), d)); int result = expression.interpret(context); System.out.println(expression + " = " + result); }
表达式可以是值也可以是加减法表达式,只要实现了 AbstractExpression 接口就行 通过接口里面的 interpret 方法进行解释,值就是返回值,而加法表达式返回左右连个表达式翻译后的结果
解释器模式优缺点 优点:
易于改变和扩展文法。由于在解释器式中使用类来表示语言的文法规则,因此可以通过继承等机制来改变或扩展文法。每一条文法规则都可以表示为一个类,因此可以方便地实现一个简单的语言。
实现文法较为容易。在抽象语法树中每一个表达式节点类的实现方式都是相似的,这些类的代码编写都不会特别复杂。
增加新的解释表达式较为方便。如果用户需要增加新的解释表达式只需要对应增加一个新的终结符表达式或非终结符表达式类,原有表达式类代码无须修改,符合”开闭原则”。
缺点:
对于复杂文法难以维护。在解释器模式中,每一条规则至少需要定义一个类,因此如果一个语言包含太多文法规则,类的个数将会急剧增加,导致系统难以管理和维护。
执行效率较低。由于在解释器模式中使用了大量的循环和递归调用,因此在解释较为复杂的句子时其速度很慢,而且代码的调试过程也比较麻烦。
解释器模式应用场景
当语言的文法较为简单,且执行效率不是关键问题时
当问题重复出现,且可以用一种简单的语言来表示时
当一个语言需要解释执行,并且语言中的句子可以表示为一个抽象语法树的时候