学习方法:
1.设计模式的学习提倡多问为什么 ,并且要对比没有该设计模式和有该设计模式的差异 ,才能体会到每一种设计模式的奇妙之处
2.每一种设计模式的代码都需要手动去实现
软件设计原则 核心内容:所有设计模式都是在这些原则的基础上进行设计的
开闭原则
里氏替换原则 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
依赖倒转原则
接口隔离原则
迪米特原则 迪米特法则要求我们在设计系统时,应该尽量减少对象之间的交互,如果两个对象之间不必彼此直接通信,那么这两个对象就不应当发生任何直接的相互作用,如果其中的一个对象需要调用另一个对象的某一个方法的话,可以通过第三者转发这个调用。简言之,就是通过引入一个合理的第三者来降低现有对象之间的耦合度 。
合成复用原则 合成复用原则是指:尽量先使用组合或者聚合等关联关系来实现,其次才考虑使用继承关系来实现。
通常类的复用分为继承复用和合成复用两种。
继承复用虽然有简单和易实现的优点,但它也存在以下缺点:
继承复用破坏了类的封装性。因为继承会将父类的实现细节暴露给子类,父类对子类是透明的,所以这种复用又称为“白箱”复用。
子类与父类的耦合度高。父类的实现的任何改变都会导致子类的实现发生变化,这不利于类的扩展与维护。
它限制了复用的灵活性。从父类继承而来的实现是静态的,在编译时已经定义,所以在运行时不可能发生变化。
采用组合或聚合复用时,可以将已有对象纳入新对象中,使之成为新对象的一部分,新对象可以调用已有对象的功能,它有以下优点:
它维持了类的封装性。因为成分对象的内部细节是新对象看不见的,所以这种复用又称为“黑箱”复用。
对象间的耦合度低。可以在类的成员位置声明抽象。
复用的灵活性高。这种复用可以在运行时动态进行,新对象可以动态地引用与成分对象类型相同的对象。
合成复用原则拥有灵活性的核心:面向抽象编程,因为面向编程可以任意接口的实现类 or 子类的切换,大大降低了类与类之间的耦合
细节:具体的代码在idea中有写,如果idea中没写(大部分都有写),可以去参考黑马资料中的代码案例
设计原则总结 核心是开闭原则,其次是合成复用原则
1.开闭原则是目的,里氏替换原则是保证开闭原则能够正常实现的基础,依赖倒转原则是实现开闭原则的手段
2.合成复用原则可以降低因为继承带来的强耦合,并且提高了非常高的灵活性,因为抽象+组合的模式可以实现动态的切换实现类,灵活性非常高
3.迪米特原则更像是引入一个中间人,降低客户和服务提供者二者之间的耦合
软件设计模式 创建者模式 单例模式 单例模式的实现有两种,饿汉式和懒汉式实现
饿汉式实现的方式有两种:静态变量实现,静态代码块实现
懒汉式实现的方式有四种:
1.静态方法实现(需要加synchronized防止线程安全问题)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class Singleton { private static Singleton singleton; private Singleton () { } public static synchronized Singleton getInstance () { if (singleton == null ){ singleton = new Singleton (); } return singleton; } }
2.静态方法+双重检查锁的方式实现(同样是为了避免 线程安全问题)
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 public class Singleton { private static volatile Singleton singleton; private Singleton () { } public static Singleton getInstance () { if (singleton == null ){ synchronized (Singleton.class){ if (singleton == null ){ singleton = new Singleton (); } } } return singleton; } }
3.静态内部类实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public class Singleton { private Singleton () { } private static class SingletonHolder { private static final Singleton singleton = new Singleton (); } public static Singleton getInstance () { return SingletonHolder.singleton; } }
4.枚举实现
1 2 3 4 5 6 7 public enum Singleton { INSTANCE; }
破坏单例模式的两种情况
1.对象的反序列化进行破坏及解决方案
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 public class Singleton implements Serializable { private Singleton () {} private static class SingletonHolder { private static final Singleton INSTANCE = new Singleton (); } public static Singleton getInstance () { return SingletonHolder.INSTANCE; } private Object readResolve () { return SingletonHolder.INSTANCE; } }
2.反射进行破坏
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 public class Singleton { private static volatile Singleton instance; private Singleton () { if (instance != null ){ throw new RuntimeException ("单例对象已构造成功,不允许重复构造" ); } } public static Singleton getInstance () { if (instance != null ) { return instance; } synchronized (Singleton.class) { if (instance != null ) { return instance; } instance = new Singleton (); return instance; } } }
工厂方法模式 案例需求:设计一个咖啡店点餐系统。
设计一个咖啡类(Coffee),并定义其两个子类(美式咖啡【AmericanCoffee】和拿铁咖啡【LatteCoffee】);再设计一个咖啡店类(CoffeeStore),咖啡店具有点咖啡的功能
1.简单工厂方法模式
案例的类图
2.工厂方法模式
案例的类图
3.工厂方法模式的另一种实现->配置文件方式实现
4.抽象工厂方法模式
案例的类图
精华总结
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
每一种代码在idea种都有手写,如果不清晰可以结合代码再次分析
原型模式 概念
用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型对象相同的新对象
原理
原型模式的克隆分为浅克隆和深克隆。
浅克隆:创建一个新对象,新对象的属性和原来对象完全相同,对于非基本类型属性,仍指向原有属性所指向的对象的内存地址。
深克隆:创建一个新对象,属性中引用的其他对象也会被克隆,不再指向原有对象地址
使用场景
对象的创建非常复杂,可以使用原型模式快捷的创建对象
性能和安全要求比较高
建造者模式 概述
将一个复杂对象的构建与表示分离 ,使得同样的构建过程可以创建不同的表示。
![建造者模式 1](建造者模式 1.png)
分离了部件的构造(由Builder来负责)和装配(由Director负责)。 从而可以构造出复杂的对象。这个模式适用于:某个对象的构建过程复杂的情况。
由于实现了构建和装配的解耦。不同的构建器,相同的装配,也可以做出不同的对象;相同的构建器,不同的装配顺序也可以做出不同的对象。也就是实现了构建算法、装配算法的解耦,实现了更好的复用。
建造者模式可以将部件和其组装过程分开,一步一步创建一个复杂的对象。用户只需要指定复杂对象的类型就可以得到该对象,而无须知道其内部的具体构造细节。
案例
创建共享单车
生产自行车是一个复杂的过程,它包含了车架,车座等组件的生产。而车架又有碳纤维,铝合金等材质的,车座有橡胶,真皮等材质。对于自行车的生产就可以使用建造者模式。
这里Bike是产品,包含车架,车座等组件;Builder是抽象建造者,MobikeBuilder和OfoBuilder是具体的建造者;Director是指挥者。类图如下:
具体的代码如下:
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 118 119 public class Bike { private String frame; private String seat; public String getFrame () { return frame; } public void setFrame (String frame) { this .frame = frame; } public String getSeat () { return seat; } public void setSeat (String seat) { this .seat = seat; } } public abstract class Builder { public Bike mBike = new Bike (); public abstract void buildFrame () ; public abstract void buildSeat () ; public abstract Bike createBike () ; } public class MobikeBuilder extends Builder { @Override public void buildFrame () { mBike.setFrame("铝合金车架" ); } @Override public void buildSeat () { mBike.setSeat("真皮车座" ); } @Override public Bike createBike () { return mBike; } } public class OfoBuilder extends Builder { @Override public void buildFrame () { mBike.setFrame("碳纤维车架" ); } @Override public void buildSeat () { mBike.setSeat("橡胶车座" ); } @Override public Bike createBike () { return mBike; } } public class Director { private Builder mBuilder; public Director (Builder builder) { mBuilder = builder; } public Bike construct () { mBuilder.buildFrame(); mBuilder.buildSeat(); return mBuilder.createBike(); } } public class Client { public static void main (String[] args) { showBike(new OfoBuilder ()); showBike(new MobikeBuilder ()); } private static void showBike (Builder builder) { Director director = new Director (builder); Bike bike = director.construct(); System.out.println(bike.getFrame()); System.out.println(bike.getSeat()); } }
注意:
上面示例是 Builder模式的常规用法,指挥者类 Director 在建造者模式中具有很重要的作用,它用于指导具体构建者如何构建产品,控制调用先后次序,并向调用者返回完整的产品类,但是有些情况下需要简化系统结构,可以把指挥者类和抽象建造者进行结合
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public abstract class Builder { protected Bike mBike = new Bike (); public abstract void buildFrame () ; public abstract void buildSeat () ; public abstract Bike createBike () ; public Bike construct () { this .buildFrame(); this .BuildSeat(); return this .createBike(); } }
说明:
这样做确实简化了系统结构,但同时也加重了抽象建造者类的职责,也不是太符合单一职责原则,如果construct() 过于复杂,建议还是封装到 Director 中。
优缺点
优点:
建造者模式的封装性很好。使用建造者模式可以有效的封装变化,在使用建造者模式的场景中,一般产品类和建造者类是比较稳定的,因此,将主要的业务逻辑封装在指挥者类中对整体而言可以取得比较好的稳定性。
在建造者模式中,客户端不必知道产品内部组成的细节,将产品本身与产品的创建过程解耦,使得相同的创建过程可以创建不同的产品对象。
可以更加精细地控制产品的创建过程 。将复杂产品的创建步骤分解在不同的方法中,使得创建过程更加清晰,也更方便使用程序来控制创建过程。
建造者模式很容易进行扩展。如果有新的需求,通过实现一个新的建造者类就可以完成,基本上不用修改之前已经测试通过的代码,因此也就不会对原有功能引入风险。符合开闭原则。
缺点:
造者模式所创建的产品一般具有较多的共同点,其组成部分相似,如果产品之间的差异性很大,则不适合使用建造者模式,因此其使用范围受到一定的限制。
使用场景
建造者(Builder)模式创建的是复杂对象,其产品的各个部分经常面临着剧烈的变化,但将它们组合在一起的算法却相对稳定,所以它通常在以下场合使用。
创建的对象较复杂,由多个部件构成,各部件面临着复杂的变化,但构件间的建造顺序是稳定的。
创建复杂对象的算法独立于该对象的组成部分以及它们的装配方式,即产品的构建过程和最终的表示是独立的。
拓展的建造者模式
demo3
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 public class Phone { private String cpu; private String screen; private String memory; private String mainboard; public Phone (String cpu, String screen, String memory, String mainboard) { this .cpu = cpu; this .screen = screen; this .memory = memory; this .mainboard = mainboard; } public String getCpu () { return cpu; } public void setCpu (String cpu) { this .cpu = cpu; } public String getScreen () { return screen; } public void setScreen (String screen) { this .screen = screen; } public String getMemory () { return memory; } public void setMemory (String memory) { this .memory = memory; } public String getMainboard () { return mainboard; } public void setMainboard (String mainboard) { this .mainboard = mainboard; } @Override public String toString () { return "Phone{" + "cpu='" + cpu + '\'' + ", screen='" + screen + '\'' + ", memory='" + memory + '\'' + ", mainboard='" + mainboard + '\'' + '}' ; } }
demo4
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 public class Phone { private String cpu; private String screen; private String memory; private String mainboard; public Phone () { } public Phone cpu (String cpu) { this .cpu = cpu; return this ; } public Phone screen (String screen) { this .screen = screen; return this ; } public Phone memory (String memory) { this .memory = memory; return this ; } public Phone mainboard (String mainboard) { this .mainboard = mainboard; return this ; } @Override public String toString () { return "Phone{" + "cpu='" + cpu + '\'' + ", screen='" + screen + '\'' + ", memory='" + memory + '\'' + ", mainboard='" + mainboard + '\'' + '}' ; } }
demo5
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 class Phone { private String cpu; private String screen; private String memory; private String mainboard; private Phone (Builder builder) { this .cpu = builder.cpu; this .screen = builder.screen; this .memory = builder.memory; this .mainboard = builder.mainboard; } @Override public String toString () { return "Phone{" + "cpu='" + cpu + '\'' + ", screen='" + screen + '\'' + ", memory='" + memory + '\'' + ", mainboard='" + mainboard + '\'' + '}' ; } public static final class Builder { private String cpu; private String screen; private String memory; private String mainboard; 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 mainboard (String mainboard) { this .mainboard = mainboard; return this ; } public Phone build () { return new Phone (this ); } } }
创建者模式对比 工厂方法模式和建造者模式对比
工厂方法模式注重的是整体对象的创建方式;而建造者模式注重的是部件构建的过程,意在通过一步一步地精确构造创建出一个复杂的对象
通俗点来说:
工厂方法模式只注重生产产品的结果,不注重产品构建过程 ,比如生产一台汽车,他直接就是给你返回一台汽车对象,并不关心汽车的内部的汽车组件(轮胎,底盘,天窗,方向盘…)是如何装配的
建造者模式则相反,非常注重产品的构建过程 ,比如生产汽车,他非常注重汽车组件的装配顺序,是现装配轮胎现还是先装配天窗先?先装配方向盘还是先装配天窗?…
抽象工厂和建造者模式对比
抽象工厂和工厂方法模式思想上是一致的,只是抽象工厂生产的产品范围更大,不局限于一种产品类型,比如工厂方法模式只生产电脑,而我抽象工厂就可以生产电器设备,如电脑,手机,电视…
跟建造者模式对比的结果也是一致,抽象工厂同样只注重生产产品的结果,不注重产品的构建过程,建造者模式则相反
他们之间的联系 :建造者需要先把产品的相关组件对象构造出来先,再进行特定顺序的装配。而构建产品的相关组件这一工作就是由工厂方法模式完成的
创建型模式总结 创建型模式的目的是让对象的创建和获取(使用)相分离
实现的核心思想是面向抽象编程+多态
对象的创建和使用分离是什么意思?比如你点饮料,无论你点的是可,雪碧,还是酸梅汁,他们的使用方法不会因为他们点的饮料类型不一样而发生改变
那么在代码层面怎么实现呢?
只有多态的思想能实现,因为多态支持动态绑定,他会根据你实际传入的类型进行动态绑定(动态调用)
那么如何实现多态呢?
多态实现的核心是面向抽象编程,因为面向抽象编程可以实现动态切换具体的接口实现类 or 抽象类的子类,只有先实现动态切换才能有多态的动态绑定和调用
下面我以工厂模式,建造者模式来举例说明
工厂模式
工厂模式的目的是需要产生各种类型的产品并且获取产品的方式不变
实现的核心是把工厂抽象起来,利用抽象实现工厂的动态切换,等待切换成功后利用多态实现动态绑定到具体工厂的创建产品方式
建造者模式
建造者模式的目的是创建复杂的对象,创建复杂的对象分为两步创建零件属性(对象),对这些零件实现装配
建造者模式的核心是实现零件对象的创建和零件对象的装配相分离,通俗点来说无论创建的是什么类型的零件,零件装配的顺序都不变
实现的核心是将零件的装配顺序抽象起来,利用抽象实现零件装配顺序的动态切换,等待切换成功后利用多态实现动态绑定到具体的零件装配顺序
以上的不变并不是真正的不变,而是表面上的调用不变,实际是利用了多态实现了动态绑定,进而实现相应的变化
至于另外两种创建者模式,单例模式和原型模式为什么不用抽象+多态呢?因为另外两种只创建一种类型的对象实例,不需要动态切换,所以不需要抽象+多态的思想实现
虽然创建者模式实现的核心思想是一致的,但不同的创建型模式功能不一样,如单例设计模式是为了产生单例对象,原型模式为了克隆复杂的对象,工厂模式是为了创建不同类型对象,构建者模式是为了创建复杂对象(需要定制复杂对象的零件装配顺序)
遗留的疑问:工厂和构建者设计模式创建的对象是否要求单例?
答案:可以实现单例也可以不实现单例,但为了对象资源的复用,最好还是实现单例为好
结构型模式 结构型模式描述如何将类或者对象结合在一起形成更复杂,功能更强大的结构
它分为类结构型模式和对象结构型模式,前者采用继承机制来组织接口和类,后者釆用组合或聚合来组合对象。
由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象结构型模式比类结构型模式具有更大的灵活性
结构型模式的核心理解:实际就是通过类与类的关系(继承),类与接口的方式(实现),类与对象的方式(组合),三者进行动态的结合,进而形成更复杂,功能更强大的结构
当然结合的过程一直贯穿着面向抽象编程的思想,毕竟要降低模块与模块之间的耦合,增加模块的独立性
不同的结构型模式,他们组合类 or 对象的方式不尽相同,对应着不同的业务场景
代理模式 核心概念:代理究竟代理的是什么?如何理解代理这个词语?
以前我的理解帮助被代理人干一些杂活,边缘性的业务,让被代理人可以专注的干自己重要的事情而不被这些次要的,或者不那么重要的事情分心,但现在我发现这个理解仍不够正确。
以前以为代理人和被代理人干的不是同一件事情,这是错误的想法,实际上代理人和被代理人干的同样的一件事
虽然干的是同一件事情,但他们在其中分工不同 。因为一件事情可以进一步拆分若干件小事,那么重要的就由被代理人自己亲自去干,不那么重要或者边缘性的事情又代理人帮忙去干
代理模式的实现方式
继承实现和接口实现,详细的链接如下(4条消息) 【Java】代理模式(Proxy模式)详解_java proxy_果壳~的博客-CSDN博客
主要搞清楚继承实现和接口实现两种方式优缺点即可,根据不同的场合选择不同的实现
案例说明
比如下面的火车站买票案例,火车站和代售点都干的是卖票这一件事情,但卖票这件事情可以划分为生产火车票,销售火车票两件事情,生产火车票是非常重要的事情,不允许出错的,所以由火车站亲自去干,而销售火车票不那么重要的事情就交给代理人去干,最终他们共同完成卖票这一任务
【例】火车站卖票
如果要买火车票的话,需要去火车站买票,坐车到火车站,排队等一系列的操作,显然比较麻烦。而火车站在多个地方都有代售点,我们去代售点买票就方便很多了。这个例子其实就是典型的代理模式,火车站是目标对象,代售点是代理对象。类图如下:
案例类图
静态代理
1 2 3 4 5 6 7 public interface SellTickets { void sell () ; }
1 2 3 4 5 6 7 8 9 10 public class TrainStation implements SellTickets { public void sell () { System.out.println("火车站卖票" ); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class ProxyPoint implements SellTickets { private final TrainStation trainStation = new TrainStation (); public void sell () { trainStation.sell(); } }
1 2 3 4 5 6 7 8 9 10 public class Client { public static void main (String[] args) { SellTickets proxyPoint = new ProxyPoint (); proxyPoint.sell(); } }
动态代理
jdk动态代理(接口实现,支持代理多个类)
demo1是用了jdk封装好的动态工具实现的动态
1 2 3 4 5 6 7 public interface SellTickets { void sell () ; }
1 2 3 4 5 6 7 8 9 10 public class TrainStation implements SellTickets { public void sell () { System.out.println("火车站卖票" ); } }
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 trainStation = new TrainStation (); public SellTickets getProxyObject () { SellTickets sellTickets = (SellTickets)Proxy.newProxyInstance(trainStation.getClass().getClassLoader(), trainStation.getClass().getInterfaces(), new InvocationHandler () { public Object invoke (Object proxy, Method method, Object[] args) throws Throwable { System.out.println("增强被代理类方法" ); method.invoke(trainStation, args); return proxy; } }); return sellTickets; } }
1 2 3 4 5 6 7 8 9 10 11 12 public class Client { public static void main (String[] args) { ProxyFactory proxyFactory = new ProxyFactory (); SellTickets proxyObject = proxyFactory.getProxyObject(); proxyObject.sell(); } }
demo2是利用通过阿里巴巴开源的 Java 诊断工具(Arthas【阿尔萨斯】)查看代理类的结构,获取jdk封装好的动态代理所生成的动态代理类进行分析和学习
1 2 3 4 5 6 7 public interface SellTickets { void sell () ; }
1 2 3 4 5 6 7 8 9 10 public class TrainStation implements SellTickets { public void sell () { System.out.println("火车站卖票" ); } }
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 public class ProxyPoint extends Proxy implements SellTickets { private static Method method; static { try { method = Class.forName("com.czq.design.pattern.proxy.jdk_proxy_demo2.SellTickets" ).getMethod("sell" ); } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } } public ProxyPoint (InvocationHandler invocationHandler) { super (invocationHandler); } public void sell () { try { this .h.invoke(this ,method,null ); } catch (Throwable throwable) { throwable.printStackTrace(); } } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public class Client { private static TrainStation trainStation = new TrainStation (); public static void main (String[] args) { ProxyPoint proxyPoint = new ProxyPoint (new InvocationHandler () { public Object invoke (Object proxy, Method method, Object[] args) throws Throwable { System.out.println("增强被代理类方法" ); method.invoke(trainStation,args); return proxy; } }); proxyPoint.sell(); } }
cglib实现动态代理
1 2 3 4 5 6 7 8 9 10 public class TrainStation { public void sell () { System.out.println("火车站卖票" ); } }
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 ProxyFactory { TrainStation trainStation = new TrainStation (); public TrainStation getProxyObject () { Enhancer enhancer = new Enhancer (); enhancer.setSuperclass(TrainStation.class); enhancer.setCallback(new MethodInterceptor () { public Object intercept (Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { System.out.println("cglib动态代理增强了方法" ); method.invoke(trainStation,objects); return o; } }); return (TrainStation) enhancer.create(); } }
1 2 3 4 5 6 7 8 9 10 11 public class Client { public static void main (String[] args) { ProxyFactory proxyFactory = new ProxyFactory (); TrainStation trainStation = proxyFactory.getProxyObject(); trainStation.sell(); } }
代理模式间的对比
动态代理和静态代理对比
动态代理和静态代理是两种不同的代理模式,它们在代理的实现方式和使用场景上有所不同。
实现方式:
静态代理:在编译阶段已经确定了要代理的目标对象,代理类需要手动编写,与目标对象实现相同的接口或继承相同的父类,并在代理类中调用目标对象的方法,并可以在方法前后添加额外的逻辑
动态代理:在运行时动态生成代理类,不需要手动编写代理类。通过 Java 的反射机制,在程序运行时动态地创建代理对象,并关联到目标对象上。通常会用到 Java 提供的两个代理实现方式:JDK 动态代理和 CGLIB 动态代理
使用场景:
静态代理:适用于只有少量目标对象需要代理,并且代理对象的行为相对固定的情况。可以在代理类中额外做一些前置或后置处理操作,如日志记录、性能统计等
动态代理:适用于需要代理的目标对象很多,且代理对象的行为需要根据具体情况动态调整的情况。动态代理能够在运行期间根据需要生成相应的代理对象,并且能够在方法调用前后执行一些通用的操作,比如事务管理、权限控制等
性能:
静态代理:由于代理类在编译阶段就确定了,所以在运行时性能较高
动态代理:由于需要在运行时动态生成代理类,所以相比静态代理会稍微降低一些性能,但这种性能损耗在大多数情况下是可以接受的
总的来说,静态代理是通过编写代理类来实现代理逻辑(需要手动编写非常麻烦,且编译时就确定,灵活性非常差),适用于目标对象较少、代理逻辑相对固定的情况; 而动态代理是在运行时动态生成代理对象 ,适用于目标对象较多、代理逻辑需要根据实际情况动态调整的场景。**动态代理相对灵活(运行时自动生成,非常方便,也非常灵活)**,但性能稍低。根据具体的需求和场景选择合适的代理模式
JDK动态代理和CGLIB代理对比
JDK 动态代理和 CGLIB 动态代理是两种常用的动态代理实现方式。它们在实现原理、适用场景和性能方面有所不同。
实现原理:
JDK 动态代理:JDK 动态代理是通过 Java 标准库中的 java.lang.reflect.Proxy 类和 java.lang.reflect.InvocationHandler 接口实现的。在运行时动态生成的代理类实现了目标接口,并且代理类的方法调用被转发到InvocationHandler接口的实现类中处理 。JDK 动态代理只能代理实现了接口的类
CGLIB 动态代理:CGLIB(Code Generation Library)是基于 ASM(Java 字节码操控框架)的代码生成库,通过生成目标类的子类来实现代理。在运行时动态生成的代理类继承了目标类,并且重写了目标类中的方法,在代理类中增加了额外的逻辑 。CGLIB 动态代理可以代理没有实现接口的类
适用场景:
JDK 动态代理:适用于需要代理的目标对象实现了接口的情况。JDK 动态代理相对于 CGLIB 动态代理更加轻量级,生成的代理对象性能较好
CGLIB 动态代理:适用于需要代理的目标对象没有实现接口的情况。CGLIB 动态代理通过继承目标类来实现代理,因此不能代理 final 类和 final 方法
性能:
JDK 动态代理:由于 JDK 动态代理是基于接口实现的,只能代理接口方法,不会代理目标对象中的非接口方法。生成的代理类相对较小,所以在程序运行时的性能上相对较高
CGLIB 动态代理:由于 CGLIB 动态代理是通过生成目标类的子类来实现代理,涉及到继承和重写方法,性能相对于 JDK 动态代理略低。适用于对性能要求不是非常苛刻的场景
综上所述,JDK 动态代理和 CGLIB 动态代理都是常见的动态代理实现方式。JDK 动态代理适用于接口代理的场景,性能相对较高;而 CGLIB 动态代理适用于类代理的场景,性能稍低。根据具体需求和场景选择合适的动态代理方式
总结 1.代理模式实现的两种方式
2.静态代理和动态代理对比
3.JDK动态代理和CGLIB动态代理对比以及他们的实现原理
4.代理模式的作用
A.代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用;
B.代理对象可以扩展目标对象的功能**(可以实现在不修改源代码的前提下对源码的功能进行拓展**);
C.代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度;
缺点:
增加了系统的复杂度;
适配器模式 概述
如果去欧洲国家去旅游的话,他们的插座如下图最左边,是欧洲标准。而我们使用的插头如下图最右边的。因此我们的笔记本电脑,手机在当地不能直接充电。所以就需要一个插座转换器,转换器第1面插入当地的插座,第2面供我们充电,这样使得我们的插头在当地能使用。生活中这样的例子很多,手机充电器(将220v转换为5v的电压),读卡器等,其实就是使用到了适配器模式
实质就是生活中的转接器,我自己的笔记本电脑只有usb接口,就使用网络接口的适配器,将网线接口适配成usb接口
定义:
将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作
适配器模式分为类适配器模式和对象适配器模式,前者类之间的耦合度比后者高,且要求程序员了解现有组件库中的相关组件的内部结构,所以应用相对较少些。
类适配器模式实现
实现方式:定义一个适配器类来实现当前系统的业务接口,同时又继承现有组件库中已经存在的组件。
【例】读卡器
现有一台电脑只能读取SD卡,而要读取TF卡中的内容的话就需要使用到适配器模式。创建一个读卡器,将TF卡中的内容读取出来。
类图如下:
对象适配器实现
1 2 3 4 5 6 7 public interface SDCard { String readSD () ; void writeSD (String msg) ; }
1 2 3 4 5 6 7 8 9 10 11 public class SDCardImpl implements SDCard { public String readSD () { String msg = "sd card read a msg :hello word SD" ; return msg; } public void writeSD (String msg) { System.out.println("sd card write msg : " + msg); } }
1 2 3 4 5 6 7 public interface TFCard { String readTF () ; void writeTF (String msg) ; }
1 2 3 4 5 6 7 8 9 10 11 12 public class TFCardImpl implements TFCard { public String readTF () { String msg = "tf card read msg : hello word tf card" ; return msg; } public void writeTF (String msg) { System.out.println("tf card write a msg : " + msg); } }
1 2 3 4 5 6 7 8 9 10 11 public class Computer { public String readSD (SDCard sdCard) { if (sdCard == null ) { throw new NullPointerException ("sd card null" ); } return sdCard.readSD(); } }
适配器类(继承方式实现)
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 SDAdapterTF extends TFCardImpl implements SDCard { @Override public String readSD () { return this .readTF(); } @Override public void writeSD (String msg) { this .writeTF(msg); } }
组合方式实现适配器模式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class SDAdapterTFTwo implements SDCard { private TFCard tfCard; public SDAdapterTFTwo (TFCard tfCard) { this .tfCard = tfCard; } @Override public String readSD () { return tfCard.readTF(); } @Override public void writeSD (String msg) { tfCard.writeTF(msg); } }
适配器模式的应用场景(作用)
A.以前开发的系统存在满足新系统功能需求的类,但其接口同新系统的接口不一致
B.使用第三方提供的组件,但组件接口定义和自己要求的接口定义不同
装饰者设计模式 定义:指在不改变现有对象结构的情况下,动态地给该对象增加一些职责(即增加其额外功能)的模式
快餐案例
快餐店有炒面、炒饭这些快餐,可以额外附加鸡蛋、火腿、培根这些配菜,当然加配菜需要额外加钱,每个配菜的价钱通常不太一样,那么计算总价就会显得比较麻烦
全部用继承实现装饰者设计模式
核心问题:产生过多的冗余子类
组合实现装饰者设计模式
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 public abstract class FastFood { private float price; private String desc; public FastFood () { } public FastFood (float price, String desc) { this .price = price; this .desc = desc; } public void setPrice (float price) { this .price = price; } public float getPrice () { return price; } public String getDesc () { return desc; } public void setDesc (String desc) { this .desc = desc; } public abstract float cost () ; }
1 2 3 4 5 6 7 8 9 10 11 public class FriedRice extends FastFood { public FriedRice () { super (10 , "炒饭" ); } public float cost () { return getPrice(); } }
1 2 3 4 5 6 7 8 9 10 11 public class FriedNoodles extends FastFood { public FriedNoodles () { super (12 , "炒面" ); } public float cost () { return getPrice(); } }
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 public abstract class Garnish extends FastFood { private FastFood fastFood; public Garnish (FastFood fastFood) { this .fastFood = fastFood; } public void setFastFood (FastFood fastFood) { this .fastFood = fastFood; } public FastFood getFastFood () { return fastFood; } public Garnish (FastFood fastFood, float price, String desc) { super (price,desc); this .fastFood = fastFood; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class Egg extends Garnish { public Egg (FastFood fastFood) { super (fastFood,1 ,"鸡蛋配料" ); } @Override public float cost () { return getPrice()+getFastFood().getPrice(); } @Override public String getDesc () { return getFastFood().getDesc()+super .getDesc(); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public class Bacon extends Garnish { public Bacon (FastFood fastFood) { super (fastFood,2 ,"培根配料" ); } @Override public float cost () { return getPrice()+getFastFood().getPrice(); } @Override public String getDesc () { return getFastFood().getDesc()+super .getDesc(); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class Client1 { public static void main (String[] args) { FastFood fastFood = new FriedRice (); System.out.println(fastFood.getDesc()+fastFood.getPrice()); fastFood = new Egg (fastFood); System.out.println(fastFood.getDesc()+fastFood.cost()); } }
装饰器的作用:指在不改变现有对象结构的情况下,动态地给该对象增加一些职责(即增加其额外功能 )的模式
疑问:同一个层级的装饰器只能装饰一种配料嘛?不能装饰多种配料吗?
解答:同一层级肯定只能加一种配料,但可以层层累加配料
代理和装饰者设计模式的区别
相同点:二者都可以增强方法逻辑
不同点:代理模式是在拓展类内部直接new 的被拓展类对象,对被拓展类对象起到了很好的隐藏作用,而装饰者设计模式是通过参数传入被拓展类对象的,并没有很好的隐藏和保护被拓展类对象
待解决问题
这个教学案例很不好,需要自己真正理解装饰模式再重构这个案例,理解装饰者模式的核心是理解他的四个角色,抽象组件,具体组件,抽象装饰器,具体装饰器
抽象组件:就是被装饰者,为什么被装饰的类要设计成抽象的呢?因为要将装饰者和被装饰着解耦,如果不设计成抽象的,一旦被装饰想要变化,那么所有对应的被装饰者都要跟着变化,这就是强耦合带来的牵一发动全身效应。
核心就一句话:在不影响装饰者的前提下实现被装饰者的动态切换(抽象带来的松耦合)
具体组件:具体被装饰者
抽象装饰器:就是装饰者了,为什么装饰者也要被设计成抽象的?原因还是将装饰者和被装饰者解耦,如果不设计成抽象的,被装饰者就无法动态变化
核心就一句话:在不影响被装饰者的前提下是实现装饰者的动态切换(抽象带来的松耦合)
具体装饰器:具体装饰者
核心思想:面向抽象编程,将强耦合变成松耦合
相关链接
设计模式-装饰器模式 - 掘金 (juejin.cn)
桥接模式 定义将抽象与实现分离,使它们可以独立变化(支持多维度变化 )。它是用组合关系代替继承关系来实现,从而降低了抽象和实现这两个可变维度的耦合度
因为继承关系是强耦合,不同维度之间是可以任意组合的,但如果使用继承来实现,因为继承是强耦合,会导致产生过多的冗余类
案例
【例】视频播放器
需要开发一个跨平台视频播放器,可以在不同操作系统平台(如Windows、Mac、Linux等)上播放多种格式的视频文件,常见的视频格式包括RMVB、AVI、WMV等。该播放器包含了两个维度 ,适合使用桥接模式
1 2 3 4 public interface VideoFile { void decode (String fileName) ; }
1 2 3 4 5 6 7 public class REVBBFile implements VideoFile { public void decode (String fileName) { System.out.println("rmvb文件:" + fileName); } }
1 2 3 4 5 6 public class AVIFile implements VideoFile { public void decode (String fileName) { System.out.println("avi视频文件:" + fileName); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 public abstract class OperatingSystemVersion { protected VideoFile videoFile; public OperatingSystemVersion (VideoFile videoFile) { this .videoFile = videoFile; } public abstract void play (String fileName) ; }
1 2 3 4 5 6 7 8 9 10 11 public class Mac extends OperatingSystemVersion { public Mac (VideoFile videoFile) { super (videoFile); } public void play (String fileName) { videoFile.decode(fileName); } }
1 2 3 4 5 6 7 8 9 10 11 public class Windows extends OperatingSystemVersion { public Windows (VideoFile videoFile) { super (videoFile); } public void play (String fileName) { videoFile.decode(fileName); } }
精华:桥接模式和前面的三种结构型模式(代理模式,适配器模式,装饰者模式)都有所不同,前面的三种模式都是同一纬度的(同一类型的),强调同一纬度下的增强,所以前面的三种模式都有使用继承 or 实现接口保持同一维度(类型),而桥接模式是支持多维度的组合增强,并不是仅仅在原有维度的基础上进行增强
外观模式 感觉外观模式就是对外提供一个统一访问的接口,屏蔽接口内部实现细节,降低客户端和服务器端之间的耦合,客户端通过访问统一接口进而访问服务器端,服务器端的具体实现他是不需要知道的,同时客户端和服务器端二者也是独立的,互不影响的
比如炒股,你一个普通人不懂得炒股,但是你可以通过基金这个接口找到专业的人士帮你炒股,而专业人士如何炒股你是不需要知道的
外观模式的核心作用:有非常好的封装性,隐藏了内部的实现细节,保护内部代码不受侵害
源码分析
使用tomcat作为web容器时,接收浏览器发送过来的请求,tomcat会将请求信息封装成ServletRequest对象,如下图①处对象。但是大家想想ServletRequest是一个接口,它还有一个子接口HttpServletRequest,而我们知道该request对象肯定是一个HttpServletRequest对象的子实现类对象,到底是哪个类的对象呢?可以通过输出request对象,我们就会发现是一个名为RequestFacade的类的对象。
RequestFacade类就使用了外观模式。先看结构图:
为什么在此处使用外观模式呢?
定义 RequestFacade 类,分别实现 ServletRequest ,同时定义私有成员变量 Request ,并且方法的实现调用 Request 的实现。然后,将 RequestFacade上转为 ServletRequest 传给 servlet 的 service 方法,这样即使在 servlet 中被下转为 RequestFacade ,也不能访问私有成员变量对象中的方法。既用了 Request ,又能防止其中方法被不合理的访问。
组合模式 概述
对于这个图片肯定会非常熟悉,上图我们可以看做是一个文件系统,对于这样的结构我们称之为树形结构。在树形结构中可以通过调用某个方法来遍历整个树,当我们找到某个叶子节点后,就可以对叶子节点进行相关的操作。可以将这颗树理解成一个大的容器,容器里面包含很多的成员对象,这些成员对象即可是容器对象也可以是叶子对象。但是由于容器对象和叶子对象在功能上面的区别,使得我们在使用的过程中必须要区分容器对象和叶子对象,但是这样就会给客户带来不必要的麻烦,作为客户而已,它始终希望能够一致的对待容器对象和叶子对象 。
定义:
又名部分整体模式,是用于把一组相似的对象当作一个单一的对象 。组合模式依据树形结构来组合对象,用来表示部分以及整体层次。这种类型的设计模式属于结构型模式,它创建了对象组的树形结构。
理解核心:将一组相似的对象当作一个对象进行一致性对待
案例实现
如下图,我们在访问别的一些管理系统时,经常可以看到类似的菜单。一个菜单可以包含菜单项(菜单项是指不再包含其他内容的菜单条目),也可以包含带有其他菜单项的菜单,因此使用组合模式描述菜单就很恰当,我们的需求是针对一个菜单,打印出其包含的所有菜单以及菜单项的名称
要实现该案例,我们先画出类图:
代码实现:
不管是菜单还是菜单项,都应该继承自统一的接口,这里姑且将这个统一的接口称为菜单组件。
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 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 i) { throw new UnsupportedOperationException (); } public String getName () { return name; } public void print () { throw new UnsupportedOperationException (); } }
这里的MenuComponent定义为抽象类,因为有一些共有的属性和行为要在该类中实现,Menu和MenuItem类就可以只覆盖自己感兴趣的方法,而不用搭理不需要或者不感兴趣的方法,举例来说,Menu类可以包含子菜单,因此需要覆盖add()、remove()、getChild()方法,但是MenuItem就不应该有这些方法。这里给出的默认实现是抛出异常,你也可以根据自己的需要改写默认实现。
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 public class Menu extends MenuComponent { private List<MenuComponent> menuComponentList; public Menu (String name,int level) { this .level = level; this .name = name; menuComponentList = new ArrayList <MenuComponent>(); } @Override public void add (MenuComponent menuComponent) { menuComponentList.add(menuComponent); } @Override public void remove (MenuComponent menuComponent) { menuComponentList.remove(menuComponent); } @Override public MenuComponent getChild (int i) { return menuComponentList.get(i); } @Override public void print () { for (int i = 1 ; i < level; i++) { System.out.print("--" ); } System.out.println(name); for (MenuComponent menuComponent : menuComponentList) { menuComponent.print(); } } }
Menu类已经实现了除了getName方法的其他所有方法,因为Menu类具有添加菜单,移除菜单和获取子菜单的功能
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class MenuItem extends MenuComponent { public MenuItem (String name,int level) { this .name = name; this .level = level; } @Override public void print () { for (int i = 1 ; i < level; i++) { System.out.print("--" ); } System.out.println(name); } }
MenuItem是菜单项,不能再有子菜单,所以添加菜单,移除菜单和获取子菜单的功能并不能实现
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 Client { 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 )); menu1.add(new MenuItem ("新增菜单" ,3 )); MenuComponent menu2 = new Menu ("权限管理" ,2 ); menu2.add(new MenuItem ("页面访问" ,3 )); menu2.add(new MenuItem ("提交保存" ,3 )); MenuComponent menu3 = new Menu ("角色管理" ,2 ); menu3.add(new MenuItem ("页面访问" ,3 )); menu3.add(new MenuItem ("新增角色" ,3 )); menu3.add(new MenuItem ("修改角色" ,3 )); MenuComponent component = new Menu ("系统管理" ,1 ); component.add(menu1); component.add(menu2); component.add(menu3); component.print(); } }
组合模式的核心:将相似的组件对象当作一致性对象来对待,巧妙的利用这一点将相似对象的操作(递归)转化成了类与类之间的关系,进一步避免了客户端对相似对象的复杂操作
利用案例解析组合模式的核心思想
如上面的菜单树案例,如果没有使用组合模式,我们需要使用方法的递归来实现树的构建和遍历,实质上递归也是一种一致性思想的体现,他将树枝节点和叶子节点统一看成是单一的对象来对待,只是递归的一致性思想是使用方法来实现的 ,而组合模式将其一致性对待的方式是通过继承让其保持一致的类型,并且每种类型都有其自己相应的一致性方法,此时就不需要进行方法的递归了,因为每种类型的节点都有相应的一致性方法,只需要动态切换对应的节点对象即可调用相应的方法即可
组合模式的分类
透明组合模式,安全组合模式
透明组合模式
透明组合模式中,抽象根节点角色中声明了所有用于管理成员对象的方法,比如在示例中 MenuComponent
声明了 add
、remove
、getChild
方法,这样做的好处是确保所有的构件类都有相同的接口。透明组合模式也是组合模式的标准形式
透明组合模式的缺点是不够安全,因为叶子对象和容器对象在本质上是有区别的 ,叶子对象不可能有下一个层次的对象,即不可能包含成员对象,因此为其提供 add()、remove() 等方法是没有意义的,这在编译阶段不会出错,但在运行阶段如果调用这些方法可能会出错(如果没有提供相应的错误处理代码)
安全组合模式
在安全组合模式中,在抽象构件角色中没有声明任何用于管理成员对象的方法,而是在树枝节点 Menu
类中声明并实现这些方法。安全组合模式的缺点是不够透明,因为叶子构件和容器构件具有不同的方法,且容器构件中那些用于管理成员对象的方法没有在抽象构件类中定义,因此客户端不能完全针对抽象编程,必须有区别地对待叶子构件和容器构件
优点
**组合模式可以清楚地定义分层次的复杂对象,表示对象的全部或部分层次(使用类的关系来实现)**,它让客户端忽略了层次的差异,方便对整个层次结构进行控制。
客户端可以一致地使用一个组合结构或其中单个对象,不必关心处理的是单个对象还是整个组合结构,简化了客户端代码。
在组合模式中增加新的树枝节点和叶子节点都很方便,无须对现有类库进行任何修改,符合“开闭原则”。
组合模式为树形结构的面向对象实现提供了一种灵活的解决方案,通过叶子节点和树枝节点的递归组合,可以形成复杂的树形结构,但对树形结构的控制却非常简单。
使用场景
组合模式正是应树形结构而生,所以组合模式的使用场景就是出现树形结构的地方。比如:文件目录显示,多级目录呈现等树形结构数据的操作
享元模式 定义:
运用共享技术来有效地支持大量细粒度对象的复用 ,它通过共享已经存在的对象来大幅度减少需要创建的对象数量、避免大量相似对象的开销 ,从而提高系统资源的利用率。
享元模式的核心思想:复用共享资源,避免共享资源重复创建而导致的浪费
案例实现
【例】俄罗斯方块
下面的图片是众所周知的俄罗斯方块中的一个个方块,如果在俄罗斯方块这个游戏中,每个不同的方块都是一个实例对象,这些对象就要占用很多的内存空间,下面利用享元模式进行实现。
先来看类图:
代码如下:
俄罗斯方块有不同的形状,我们可以对这些形状向上抽取出AbstractBox,用来定义共性的属性和行为。
1 2 3 4 5 6 7 public abstract class AbstractBox { public abstract String getShape () ; public void display (String color) { System.out.println("方块形状:" + this .getShape() + " 颜色:" + color); } }
接下来就是定义不同的形状了,IBox类、LBox类、OBox类等。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public class IBox extends AbstractBox { @Override public String getShape () { return "I" ; } } public class LBox extends AbstractBox { @Override public String getShape () { return "L" ; } } public class OBox extends AbstractBox { @Override public String getShape () { return "O" ; } }
提供了一个工厂类(BoxFactory),用来管理享元对象(也就是AbstractBox子类对象),该工厂类对象只需要一个,所以可以使用单例模式。并给工厂类提供一个获取形状的方法。
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 public class BoxFactory { private static HashMap<String, AbstractBox> map; private BoxFactory () { map = new HashMap <String, AbstractBox>(); AbstractBox iBox = new IBox (); AbstractBox lBox = new LBox (); AbstractBox oBox = new OBox (); map.put("I" , iBox); map.put("L" , lBox); map.put("O" , oBox); } public static final BoxFactory getInstance () { return SingletonHolder.INSTANCE; } private static class SingletonHolder { private static final BoxFactory INSTANCE = new BoxFactory (); } public AbstractBox getBox (String key) { return map.get(key); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class Client { public static void main (String[] args) { AbstractBox box1 = BoxFactory.getInstance().getBox("I" ); box1.display("灰色" ); AbstractBox box2 = BoxFactory.getInstance().getBox("L" ); box2.display("绿色" ); AbstractBox box3 = BoxFactory.getInstance().getBox("O" ); box3.display("灰色" ); AbstractBox box4 = BoxFactory.getInstance().getBox("O" ); box4.display("红色" ); System.out.println("两次获取到的O图形对象是否是同一个对象:" + (box3 == box4)); } }
享元模式和工厂模式的区别 :
享元模式共享的是大量相似 or 相同的对象资源 ,比如大量的共享单车(仅仅只是厂家不一样,颜色不一样…),支持修改外部状态且不影响内部状态
单例模式和工厂模式(创建型模式)的核心工作是将对象创建和使用相分离(解耦),并不是要复用对象资源,二者的应用场景完全不一样,当然单例模式和工厂模式可以结合享元模式使用,即实现了对象的创建和使用相分离,又实现了大量相似 or 相同的对象资源复用
享元模式是要先创建对象资源再复用对象资源的,在创建对象资源这部分情景,可以结合创建型模式的单例模式或者工厂模式
优缺点和使用场景
1,优点
极大减少内存中相似或相同对象数量,节约系统资源,提供系统性能
享元模式中的外部状态相对独立,且不影响内部状态
2,缺点:
为了使对象可以共享,需要将享元对象的部分状态外部化,分离内部状态和外部状态,使程序逻辑复杂
3,使用场景:
一个系统有大量相同或者相似的对象,造成内存的大量耗费。
对象的大部分状态都可以外部化,可以将这些外部状态传入对象中。
在使用享元模式时需要维护一个存储享元对象的享元池 ,而这需要耗费一定的系统资源,因此,应当在需要多次重复使用享元对象时才值得使用享元模式。
维护大量的相似 or 相同的共享对象资源,这一现象又可共享资源池(享元池)
典型享元模式的案例有:jdk源码种的Integer常量池(-128-127),字符串常量池,数据库连接池..
行为模式 行为型模式用于描述程序在运行时复杂的流程控制(着重于业务逻辑),即描述多个类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,它涉及算法与对象间职责的分配(如何将业务逻辑同类之间的分工合作 or 对象之间的分工合作完成复杂的业务逻辑)
行为型模式分为类行为模式和对象行为模式,前者采用继承机制来在类间分派行为,后者采用组合或聚合在对象间分配行为。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象行为模式比类行为模式具有更大的灵活性
行为型模式分为:
模板方法模式
策略模式
命令模式
职责链模式
状态模式
观察者模式
中介者模式
迭代器模式
访问者模式
备忘录模式
解释器模式
以上 11 种行为型模式,除了模板方法模式和解释器模式是类行为型模式,其他的全部属于对象行为型模式
模板方法模式 概述
在面向对象程序设计过程中,程序员常常会遇到这种情况:设计一个系统时知道了算法所需的关键步骤,而且确定了这些步骤的执行顺序,但某些步骤的具体实现还未知,或者说某些步骤的实现与具体的环境相关 。
例如,去银行办理业务一般要经过以下4个流程:取号、排队、办理具体业务、对银行工作人员进行评分等,其中取号、排队和对银行工作人员进行评分的业务对每个客户是一样的,可以在父类中实现,但是办理具体业务却因人而异,它可能是存款、取款或者转账等,可以延迟到子类中实现。
定义(模板方法的核心理解)
定义一个操作中的算法骨架(算法骨架已经固定,不会随着环境的变化而变化),而将算法的一些步骤延迟到子类中(将会随着环境变化的具体执行步骤延迟到子类中),使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤
案例实现
【例】炒菜
炒菜的步骤(算法骨架 )是固定的,分为倒油、热油、倒蔬菜、倒调料品、翻炒等步骤。现通过模板方法模式来用代码模拟。类图如下:
代码如下:
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 public abstract class AbstractClass { public final void cookProcess () { this .pourOil(); this .heatOil(); this .pourVegetable(); this .pourSauce(); this .fry(); } public void pourOil () { System.out.println("倒油" ); } public void heatOil () { System.out.println("热油" ); } public abstract void pourVegetable () ; public abstract void pourSauce () ; public void fry () { System.out.println("炒啊炒啊炒到熟啊" ); } } public class ConcreteClass_BaoCai extends AbstractClass { @Override public void pourVegetable () { System.out.println("下锅的蔬菜是包菜" ); } @Override public void pourSauce () { System.out.println("下锅的酱料是辣椒" ); } } public class ConcreteClass_CaiXin extends AbstractClass { @Override public void pourVegetable () { System.out.println("下锅的蔬菜是菜心" ); } @Override public void pourSauce () { System.out.println("下锅的酱料是蒜蓉" ); } } public class Client { public static void main (String[] args) { ConcreteClass_BaoCai baoCai = new ConcreteClass_BaoCai (); baoCai.cookProcess(); ConcreteClass_CaiXin caiXin = new ConcreteClass_CaiXin (); caiXin.cookProcess(); } }
注意:为防止恶意操作,一般模板方法都加上 final 关键词。
优缺点
优点:
缺点:
对每个不同的实现都需要定义一个子类,这会导致类的个数增加,系统更加庞大,设计也更加抽象。
父类中的抽象方法由子类实现,子类执行的结果会影响父类的结果,这导致一种反向的控制结构,它提高了代码阅读的难度 。
适用场景
算法的整体步骤很固定,但其中个别部分易变时,这时候可以使用模板方法模式,将容易变的部分抽象出来,供子类实现。
需要通过子类来决定父类算法中某个步骤是否执行,实现子类对父类的反向控制。
策略模式 概述
先看下面的图片,我们去旅游选择出行模式有很多种,可以骑自行车、可以坐汽车、可以坐火车、可以坐飞机。
作为一个程序猿,开发需要选择一款开发工具,当然可以进行代码开发的工具有很多,可以选择Idea进行开发,也可以使用eclipse进行开发,也可以使用其他的一些开发工具。
定义:
该模式定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的变化不会影响使用算法的客户 。策略模式属于对象行为模式,它通过对算法进行封装,把使用算法的责任和算法的实现分割开来 ,并委派给不同的对象对这些算法进行管理
核心:同一种功能的使用有很多不同的算法策略去实现,让功能的使用和功能对应的算法实现相分离,让客户可以根据具体的业务场景选择对应的算法实现该功能
比如:
1.旅游出行方式,要使用的功能是旅游出现,可供选择算法策略是走路,骑自行车,汽车,飞机等
2.程序员要使用开发工具实现开发的功能,可供选择的开发工具有很多,有idea,eclipse,visio…
3.真实的业务场景,用户要使用支付的功能,可供选择支付算法有很多,微信支付,支付宝支付,银行卡支付,现金支付…
具体的代码实现是:面向抽象+组合动态选择该功能的算法实现
角色
抽象策略类,具体策略类,策略环境类
案例
【例】促销活动
一家百货公司在定年度的促销活动。针对不同的节日(春节、中秋节、圣诞节)推出不同的促销活动,由促销员将促销活动展示给客户。类图如下:
代码如下:
定义百货公司所有促销活动的共同接口
1 2 3 public interface Strategy { void show () ; }
定义具体策略角色(Concrete Strategy):每个节日具体的促销活动
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public class StrategyA implements Strategy { public void show () { System.out.println("买一送一" ); } } public class StrategyB implements Strategy { public void show () { System.out.println("满200元减50元" ); } } public class StrategyC implements Strategy { public void show () { System.out.println("满1000元加一元换购任意200元以下商品" ); } }
定义环境角色(Context):用于连接上下文,即把促销活动推销给客户,这里可以理解为销售员
1 2 3 4 5 6 7 8 9 10 11 12 13 public class SalesMan { private Strategy strategy; public SalesMan (Strategy strategy) { this .strategy = strategy; } public void salesManShow () { strategy.show(); } }
关于策略设计模式理解犯的两个错误(以上的策略模式案例尚存在两个问题)
1.将具体的策略和环境对象耦合起来了,实质上环境只是存储和获取策略的容器,环境对象不应该和策略产生强耦合关系,比如具体的策略对象直接跟环境对象耦合起来,弱耦合关系才对,环境对象提供好注册和获取具体的方法即可
2.并没有完全做到对修改关闭的原则,因为如果添加新策略,即使策略接口本身不需要改变,但你需要手动在环境里中添加,还是修改了环境类的代码,本质修改还是没有关闭,只是从策略类中转移到了环境类中
核心的点在于添加策略时,为什么是由环境方来添加呢?为什么不是由策略本身来自己实现策略的添加呢?如果是前者的话就需要改动原来的代码,违反修改关闭的原则,如果是后者的话就不会修改原有的代码了
所以我们现在的目标是让新的策略自己实现自动注册,而无需环境方再手动帮他注册,那具体怎么实现呢? 任何一个类都是以对象的形式发挥作用的,所以我们只需要在起创建的对象的时候调用注册方法即可, 具体的做法是在其构造函数中注册即可,这样就把创新对象和注册两个功能捆绑在了一起,只要其创建对象就自然而然会将策略对象注册到容器中
关于策略模式的两个深刻理解
1.因为多态的原理是根据具体的实现类去动态绑定具体的实现方法,进而根据具体的环境选择具体的实现类执行具体的实现方法,但往往很多业务有业务的区别标识,而不是以类的类型为区分标识。所以要在环境类中额外建立这二者的映射关系,一般是使用map集合来建立
2.接口中的默认方法也能被实现类继承过来,那为什么我们需要额外定义一个抽象类来实现自动注册的功能呢?为什么不能直接用接口实现呢?把自动注册的功能放在接口上也是可以实现自动注册的功能的呀?
因为策略接口是暴露给外界调用的,如果你把注册功能也写在接口上,等于也暴露出去了。虽然功能上都可以实现自动注册, 但破坏了注册功能的封装性,没有实现屏蔽内部实现细节的作用,可以让调用者清晰的看见你的注册接口,但是注册功能是自动注册, 没有暴露给外界的,封装性更好也可以给接口提供更好的保护作用,也可以降低使用者的使用成本
最顶层(基类中的顶层)的类型才是暴露给外界使用的,第二点让我真正体会到了封装性
改进后的案例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public interface Strategy { void show () ; } public abstract class AbstractArticleStrategy implements ArticleStrategy { public void register (Byte type) { ArticleStrategyContext.register(type,this ); } }
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 public class StrategyA extends AbstractStrategy implements Strategy { public StrategyA () { register("A" ); } public void show () { System.out.println("买一送一" ); } } public class StrategyB extends AbstractStrategy implements Strategy { public StrategyB () { register("B" ); } public void show () { System.out.println("满200元减50元" ); } } public class StrategyC extends AbstractStrategy implements Strategy { public StrategyC () { register("C" ); } public void show () { System.out.println("满1000元加一元换购任意200元以下商品" ); } }
定义环境角色(Context):用于连接上下文,即把促销活动推销给客户,这里可以理解为销售员
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class SalesMan { private static Map<String,Strategy> map = new HashMap <>(); public static void register (String type,Strategy strategy) { map.putIfAbsent(type,strategy); } public static Strategy getStrategy (String type) { return map.get(type); } }
优缺点
1,优点:
策略类之间可以自由切换
由于策略类都实现同一个接口,所以使它们之间可以自由切换。
易于扩展
增加一个新的策略只需要添加一个具体的策略类即可,基本不需要改变原有的代码,符合“开闭原则“
避免使用多重条件选择语句(if else),充分体现面向对象设计思想
意思是可以替换同一种功能的不同实现的多种if-else语法,因为策略模式本身就是面向同一种功能的使用选择不同的策略(算法)实现
2,缺点:
客户端必须知道所有的策略类,并自行决定使用哪一个策略类。
策略模式将造成产生很多策略类,可以通过使用享元模式在一定程度上减少对象的数量。
使用场景
一个系统需要动态地在几种算法中选择一种时,可将每个算法封装到策略类中
一个类定义了多种行为,并且这些行为在这个类的操作中以多个条件语句的形式出现,可将每个条件分支移入它们各自的策略类中以代替这些条件语句
系统中各算法彼此完全独立,且要求对客户隐藏具体算法的实现细节时
系统要求使用算法的客户不应该知道其操作的数据时,可使用策略模式来隐藏与算法相关的数据结构
多个类只区别在表现行为不同,可以使用策略模式,在运行时动态选择具体要执行的行为
策略模式在实际的业务场景用的非常多,因为很多业务场景都是同一种功能的使用有种很多不同的策略实现,要支持动态的切换,比如支付场景,评论场景(区分是否是会员)….太多了
命令模式 定义:
将一个请求封装为一个对象,**使发出请求的责任和执行请求的责任分割开(将命令的发出和命令的执行进行解耦)**。这样两者之间通过命令对象进行沟通,这样方便将命令对象进行存储、传递、调用、增加与管理。
案例实现
案例场景:实现服务员下订单的命令和厨师执行订单命令的解耦合,让服务员可以只关心命令的下达而无需关心的命令的接收者是谁,命令具体怎么执行
这个类图画的不好,Client类只是测试类,没有任何意义,不应该画上来,简单解析一下该类图
Waitor:服务员类,负责下达命令,list集合代表服务员可以下达多个命令
Command:抽象命令类,负责根据具体的业务场景切换具体的命令
OrderCommand:具体的订单下达命令,命令下达时需要根据实际情况指定具体的命令接收者和命令具体的执行内容,所以需要传入接收者,和命令的内容(订单)
SeniorChef:厨师类,命令的接收者
Order:订单类,命令执行的具体内容
代码如下:
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 public interface Command { void execute () ; } public class OrderCommand implements Command { private SeniorChef receiver; private Order order; public OrderCommand (SeniorChef receiver, Order order) { this .receiver = receiver; this .order = order; } public void execute () { System.out.println(order.getDiningTable() + "桌的订单:" ); Set<String> keys = order.getFoodDic().keySet(); for (String key : keys) { receiver.makeFood(order.getFoodDic().get(key),key); } try { Thread.sleep(100 ); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(order.getDiningTable() + "桌的饭弄好了" ); } } public class Order { private int diningTable; private Map<String, Integer> foodDic = new HashMap <String, Integer>(); public int getDiningTable () { return diningTable; } public void setDiningTable (int diningTable) { this .diningTable = diningTable; } public Map<String, Integer> getFoodDic () { return foodDic; } public void setFoodDic (String name, int num) { foodDic.put(name,num); } } public class SeniorChef { public void makeFood (int num,String foodName) { System.out.println(num + "份" + foodName); } } public class Waitor { private ArrayList<Command> commands; public Waitor () { commands = new ArrayList (); } public void setCommand (Command cmd) { commands.add(cmd); } public void orderUp () { System.out.println("美女服务员:叮咚,大厨,新订单来了......." ); for (int i = 0 ; i < commands.size(); i++) { Command cmd = commands.get(i); if (cmd != null ) { cmd.execute(); } } } } public class Client { public static void main (String[] args) { Order order1 = new Order (); order1.setDiningTable(1 ); order1.getFoodDic().put("西红柿鸡蛋面" ,1 ); order1.getFoodDic().put("小杯可乐" ,2 ); Order order2 = new Order (); order2.setDiningTable(3 ); order2.getFoodDic().put("尖椒肉丝盖饭" ,1 ); order2.getFoodDic().put("小杯雪碧" ,1 ); SeniorChef receiver=new SeniorChef (); OrderCommand cmd1 = new OrderCommand (receiver, order1); OrderCommand cmd2 = new OrderCommand (receiver, order2); Waitor invoker = new Waitor (); invoker.setCommand(cmd1); invoker.setCommand(cmd2); invoker.orderUp(); } }
优缺点
1,优点:
降低系统的耦合度。**命令模式能将调用操作的对象与实现该操作的对象解耦(命令的发出者和命令的执行者解耦)**。
**增加或删除命令非常方便(因为对命令进行了抽象,所以可以非常灵活的拓展)**。采用命令模式增加与删除命令不会影响其他类,它满足“开闭原则”,对扩展比较灵活。
可以实现宏命令。命令模式可以与组合模式结合,将多个命令装配成一个组合命令,即宏命令。
方便实现 Undo 和 Redo 操作。命令模式可以与后面介绍的备忘录模式结合,实现命令的撤销与恢复。
2,缺点:
使用命令模式可能会导致某些系统有过多的具体命令类 。
系统结构更加复杂。
使用场景
系统需要将请求调用者和请求接收者解耦(命令的发出者和命令的执行者解耦),使得调用者和接收者不直接交互 。
系统需要在不同的时间指定请求、将请求排队和执行请求。
系统需要支持命令的撤销(Undo)操作和恢复(Redo)操作。
命令模式的核心就是将命令抽象起来,让命令的发送者只关心命令发送即可,而命令的接收者和命令具体怎么执行可以根据实际情况进行动态的切换,命令的发送者无需关心这部分内容,实际上就是一句话,将命令的发送和命令的执行解耦合,提高二者的独立性
典型的消息发布订阅模式就是采用这种设计模式的,消息的发送者只需要发送消息即可不用关心消息具体是由谁来接受,是由谁来执行,消息接收者可以根据需求自己选择需要接受的内容(错误的理解)
消息的发布订阅模式是用观察者模式实现 ,首先消息的发布订阅机制是一个一对多的依赖关系 ,而命令模式是一个一对一的依赖关系 ,每个命令都有唯一的接收者,只能接收一次,不能群体接收该命令并执行 ,其次消息的发布订阅模式是需要知道谁订阅了我们的消息进而实现消息传递的 ,而命令模式将命令的发送和命令的执行解耦了,是不知道谁执行了命令的 ,所以消息的发布订阅机制不是用命令模式实现的
造成错误理解的根本原因是对消息的发布订阅机制不熟悉
jDK源码解析
Runable是一个典型命令模式,Runnable担当命令的角色,Thread充当的是调用者,start方法就是其执行方法
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 public interface Runnable { public abstract void run () ; } public class Thread implements Runnable { private Runnable target; public synchronized void start () { if (threadStatus != 0 ) throw new IllegalThreadStateException (); group.add(this ); boolean started = false ; try { start0(); started = true ; } finally { try { if (!started) { group.threadStartFailed(this ); } } catch (Throwable ignore) { } } } private native void start0 () ; }
会调用一个native方法start0(),调用系统方法,开启一个线程。而接收者是对程序员开放的,可以自己定义接收者。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public class TurnOffThread implements Runnable { private Receiver receiver; public TurnOffThread (Receiver receiver) { this .receiver = receiver; } public void run () { receiver.turnOFF(); } }
1 2 3 4 5 6 7 8 9 10 11 public class Demo { public static void main (String[] args) { Receiver receiver = new Receiver (); TurnOffThread turnOffThread = new TurnOffThread (receiver); Thread thread = new Thread (turnOffThread); thread.start(); } }
职责链模式 概述
在现实生活中,常常会出现这样的事例:一个请求有多个对象可以处理,但每个对象的处理条件或权限不同 。例如,公司员工请假,可批假的领导有部门负责人、副总经理、总经理等,但每个领导能批准的天数不同,员工必须根据自己要请假的天数去找不同的领导签名,也就是说员工必须记住每个领导的姓名、电话和地址等信息,这增加了难度。这样的例子还有很多,如找领导出差报销、生活中的“击鼓传花”游戏等
定义:
又名职责链模式,为了避免请求发送者与多个请求处理者耦合在一起 ,将所有请求的处理者通过前一对象记住其下一个对象的引用而连成一条链;当有请求发生时,可将请求沿着这条链传递,直到有对象处理它为止
职责链的核心理解:
1.将请求发送与多个请求处理者进行解耦,降低了二者的耦合,提高了二者的独立性,最明显的好处是请求发送者的业务逻辑会简单很多,因为他只需要把请求给链头对象即可
2.其次因为职责链上的对象符合单一职责原则,通俗点来说每个人只干一种活,然后彼此分工合作,降低了职责之间的耦合度,提高了每个职责的可扩展性,可维护性,进一步来说就是提高了系统的可扩展性,可维护性
案例
现需要开发一个请假流程控制系统。请假一天以下的假只需要小组长同意即可;请假1天到3天的假还需要部门经理同意;请求3天到7天还需要总经理同意才行。
类图如下:
类图解析:
Client是测试类,不需要理会
LeaveRequest:请假请求
Handler:抽象处理者类,负责抽取处理者的共性如处理属性,处理方法(处理方法必须是抽象的,因为每个处理者的职责不一样,对应的处理方案也不一样),然后还需要有一个传递请求给一个职责对象的方法
GroupLeader,Manager,GeneraManager:具体处理器类
代码如下:
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 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 public class LeaveRequest { private String name; private int num; private String content; public LeaveRequest (String name, int num, String content) { this .name = name; this .num = num; this .content = content; } public String getName () { return name; } public int getNum () { return num; } public String getContent () { return 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) { this .numStart = numStart; } public Handler (int numStart, int numEnd) { this .numStart = numStart; this .numEnd = numEnd; } public void setNextHandler (Handler nextHandler) { this .nextHandler = nextHandler; } public final void submit (LeaveRequest leave) { if (0 == this .numStart){ return ; } if (leave.getNum() >= this .numStart){ this .handleLeave(leave); if (null != this .nextHandler && leave.getNum() > numEnd){ this .nextHandler.submit(leave); } else { System.out.println("流程结束" ); } } } protected abstract void handleLeave (LeaveRequest leave) ; } public class GroupLeader extends Handler { public GroupLeader () { super (Handler.NUM_ONE, Handler.NUM_THREE); } @Override 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); } @Override protected void handleLeave (LeaveRequest leave) { System.out.println(leave.getName() + "请假" + leave.getNum() + "天," + leave.getContent() + "。" ); System.out.println("部门经理审批:同意。" ); } } public class GeneralManager extends Handler { public GeneralManager () { super (Handler.NUM_SEVEN); } @Override protected void handleLeave (LeaveRequest leave) { System.out.println(leave.getName() + "请假" + leave.getNum() + "天," + leave.getContent() + "。" ); System.out.println("总经理审批:同意。" ); } } public class Client { public static void main (String[] args) { LeaveRequest leave = new LeaveRequest ("小花" ,5 ,"身体不适" ); GroupLeader groupLeader = new GroupLeader (); Manager manager = new Manager (); GeneralManager generalManager = new GeneralManager (); groupLeader.setNextHandler(manager); manager.setNextHandler(generalManager); groupLeader.submit(leave); } }
优缺点
1,优点:
降低了对象之间的耦合度
该模式降低了请求发送者和接收者的耦合度(请求发送者只需要把请求发送给链头对象即可)
增强了系统的可扩展性
可以根据需要增加新的请求处理类,满足开闭原则(有新的职责链只需要直接加链尾即可,无需变动原有代码)
增强了给对象指派职责的灵活性
当工作流程发生变化,可以动态地改变链内的成员或者修改它们的次序,也可动态地新增或者删除责任。
责任链简化了对象之间的连接
**一个对象只需保持一个指向其后继者的引用,不需保持其他所有处理者的引用,这避免了使用众多的 if 或者 if···else 语句(一个处理者只需要和其后一个处理者进行交互,而不用和其他的处理者进行交互)**。
责任分担
每个类只需要处理自己该处理的工作,不能处理的传递给下一个对象完成,明确各类的责任范围,符合类的单一职责原则
2,缺点:
不能保证每个请求一定被处理。由于一个请求没有明确的接收者,所以不能保证它一定会被处理,该请求可能一直传到链的末端都得不到处理
对比较长的职责链,请求的处理可能涉及多个处理对象,系统性能将受到一定影响 。
职责链建立的合理性要靠客户端来保证,增加了客户端的复杂性,可能会由于职责链的错误设置而导致系统出错,如可能会造成循环调用
源码解析
在javaWeb应用开发中,FilterChain是职责链(过滤器)模式的典型应用,以下是Filter的模拟实现分析:
模拟web请求Request以及web响应Response
1 2 3 4 5 6 7 public interface Request { } public interface Response { }
模拟web过滤器Filter
1 2 3 public interface Filter { public void doFilter (Request req,Response res,FilterChain c) ; }
模拟实现具体过滤器
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 FirstFilter implements Filter { @Override public void doFilter (Request request, Response response, FilterChain chain) { System.out.println("过滤器1 前置处理" ); chain.doFilter(request, response); System.out.println("过滤器1 后置处理" ); } } public class SecondFilter implements Filter { @Override public void doFilter (Request request, Response response, FilterChain chain) { System.out.println("过滤器2 前置处理" ); chain.doFilter(request, response); System.out.println("过滤器2 后置处理" ); } }
模拟实现过滤器链FilterChain
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class FilterChain { private List<Filter> filters = new ArrayList <Filter>(); private int index = 0 ; public FilterChain addFilter (Filter filter) { this .filters.add(filter); return this ; } public void doFilter (Request request, Response response) { if (index == filters.size()) { return ; } Filter filter = filters.get(index); index++; filter.doFilter(request, response, this ); } }
测试类
1 2 3 4 5 6 7 8 9 10 public class Client { public static void main (String[] args) { Request req = null ; Response res = null ; FilterChain filterChain = new FilterChain (); filterChain.addFilter(new FirstFilter ()).addFilter(new SecondFilter ()); filterChain.doFilter(req,res); } }
可以打断点感受真实的过滤器的执行过程,并从中感受职责链设计模式
和上面标准的职责链模式不一样的是,过滤器链抽取了一个过滤器链对象来维护职责链,并没有把维护过滤器链的任务耦合到每一个指责类上
状态模式 概述
【例】通过按钮来控制一个电梯的状态,一个电梯有开门状态,关门状态,停止状态,运行状态。每一种状态改变,都有可能要根据其他状态来更新处理。例如,如果电梯门现在处于运行时状态,就不能进行开门操作,而如果电梯门是停止状态,就可以执行开门操作。
类图如下:
代码如下:
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 118 119 120 121 122 123 public interface ILift { public final static int OPENING_STATE = 1 ; public final static int CLOSING_STATE = 2 ; public final static int RUNNING_STATE = 3 ; public final static int STOPPING_STATE = 4 ; public void setState (int state) ; public void open () ; public void close () ; public void run () ; public void stop () ; } public class Lift implements ILift { private int state; @Override public void setState (int state) { this .state = state; } @Override public void close () { switch (this .state) { case OPENING_STATE: System.out.println("电梯关门了。。。" ); this .setState(CLOSING_STATE); break ; case CLOSING_STATE: break ; case RUNNING_STATE: break ; case STOPPING_STATE: break ; } } @Override public void open () { switch (this .state) { case OPENING_STATE: break ; case CLOSING_STATE: System.out.println("电梯门打开了。。。" ); this .setState(OPENING_STATE); break ; case RUNNING_STATE: break ; case STOPPING_STATE: System.out.println("电梯门开了。。。" ); this .setState(OPENING_STATE); break ; } } @Override public void run () { switch (this .state) { case OPENING_STATE: break ; case CLOSING_STATE: System.out.println("电梯开始运行了。。。" ); this .setState(RUNNING_STATE); break ; case RUNNING_STATE: break ; case STOPPING_STATE: System.out.println("电梯开始运行了。。。" ); this .setState(RUNNING_STATE); break ; } } @Override public void stop () { switch (this .state) { case OPENING_STATE: break ; case CLOSING_STATE: System.out.println("电梯停止了。。。" ); this .setState(STOPPING_STATE); break ; case RUNNING_STATE: System.out.println("电梯停止了。。。" ); this .setState(STOPPING_STATE); break ; case STOPPING_STATE: break ; } } } public class Client { public static void main (String[] args) { Lift lift = new Lift (); lift.setState(ILift.STOPPING_STATE); lift.open(); lift.close(); lift.run(); lift.stop(); } }
问题分析:
使用了大量的switch…case这样的判断(if…else也是一样),使程序的可阅读性变差。
扩展性很差。如果新加了断电的状态,我们需要修改上面判断逻辑
定义:
对有状态的对象,把复杂的“判断逻辑”提取到不同的状态对象中,允许状态对象在其内部状态发生改变时改变其行为
角色
抽象状态类,具体状态类,状态环境类
案例实现
对上述电梯的案例使用状态模式进行改进。类图如下:
代码如下:
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 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 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 OpenningState extends LiftState { @Override public void open () { System.out.println("电梯门开启..." ); } @Override public void close () { super .context.setLiftState(Context.closeingState); super .context.getLiftState().close(); } @Override public void run () { } @Override public void stop () { } } public class RunningState extends LiftState { @Override public void open () { } @Override public void close () { } @Override public void run () { System.out.println("电梯正在运行..." ); } @Override public void stop () { super .context.setLiftState(Context.stoppingState); super .context.stop(); } } public class StoppingState extends LiftState { @Override public void open () { super .context.setLiftState(Context.openningState); super .context.getLiftState().open(); } @Override public void close () { super .context.setLiftState(Context.closeingState); super .context.getLiftState().close(); } @Override public void run () { super .context.setLiftState(Context.runningState); super .context.getLiftState().run(); } @Override public void stop () { System.out.println("电梯停止了..." ); } } public class ClosingState extends LiftState { @Override public void close () { System.out.println("电梯门关闭..." ); } @Override public void open () { super .context.setLiftState(Context.openningState); super .context.open(); } @Override public void run () { super .context.setLiftState(Context.runningState); super .context.run(); } @Override public void stop () { super .context.setLiftState(Context.stoppingState); super .context.stop(); } } public class Context { public final static OpenningState openningState = new OpenningState (); public final static ClosingState closeingState = new ClosingState (); public final static RunningState runningState = new RunningState (); public final static StoppingState stoppingState = new StoppingState (); private LiftState liftState; public LiftState getLiftState () { return this .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 class Client { public static void main (String[] args) { Context context = new Context (); context.setLiftState(new ClosingState ()); context.open(); context.close(); context.run(); context.stop(); } }
案例缺点:
1.其实一种状态只是对应一种行为,上面的案例一种状态却写了多种行为
改进后的案例
1 2 3 4 5 6 7 public interface LiftState { void handleState (LiftStateContext 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 public class OpenLiftState implements LiftState { @Override public void handleState (LiftStateContext context) { System.out.println("open" ); context.setLiftState(new CloseLiftState ()); } } public class CloseLiftState implements LiftState { @Override public void handleState (LiftStateContext context) { System.out.println("close" ); context.setLiftState(new RunningLiftState ()); } } public class RunningLiftState implements LiftState { @Override public void handleState (LiftStateContext context) { System.out.println("running" ); context.setLiftState(new StopLiftState ()); } } public class StopLiftState implements LiftState { @Override public void handleState (LiftStateContext context) { System.out.println("stop" ); context.setLiftState(this ); } }
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 class LiftStateContext { private LiftState liftState; public LiftStateContext (LiftState state) { this .liftState = state; } public void setLiftState (LiftState liftState) { this .liftState = liftState; } public LiftState getLiftState () { return liftState; } public void handle () { liftState.handleState(this ); }
状态模式的深入理解
1.一种状态对应一种行为,并且要用类的关系描述出不同状态对应不同的行为,一般是将状态和其对应的行为抽象起来即可
2.要从整体的角度看到状态的变化,所有状态的变化是属于一个流程的,比如这里的电话状态的变化,开门,关门,运行,停止,状态前后的流转是有严格的控制,比如只有开了门才能关门等等。核心是要从一个流程的角度看待状态变化 。再比如订单状态的变化
3.如何描述状态的变化呢?前面也说了状态的转换是有严格流程控制的,所以在状态相应的行为中,根据流程进行状态的变化即可
优缺点
1,优点:
将所有与某个状态有关的行为放到一个类中,并且可以方便地增加新的状态,只需要改变对象状态即可改变对象的行为。
允许状态转换逻辑与状态对象合成一体,而不是某一个巨大的条件语句块 。
2,缺点:
状态模式的使用必然会增加系统类和对象的个数。
状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱。
状态模式对”开闭原则”的支持并不太好
为什么状态模式对开闭原则的支持不太友好呢?
因为状态的变化是属于一个流程的,流程中的状态前后是耦合在一起的 。比如电梯要想关门,前面的状态就必须是开门,只有开门了才能关门,所以如果你新添加一个电梯状态,那么可能电梯流程的状态转换都是需要发生相应的变化的。
所以如果状态流转逻辑变化频繁,那么可能要慎重使用状态模式
使用场景
当一个对象的行为取决于它的状态,并且它必须在运行时根据状态改变它的行为时,就可以考虑使用状态模式。
一个操作中含有庞大的分支结构,并且这些分支决定于对象的状态时。
状态模式主要的问题不遵守开闭原则,如果想要拓展的话还是需要修改原代码,但最大的作用是避免了多种if-elses的状态判断,提高了代码的可阅读性
其实可以考虑优化成根据设置状态的动态选择需要执行的行为,这样的话虽然降低了代码的可阅读,但是可以遵守开闭原则,提高代码的可扩展性
观察者模式 概述
定义:
又被称为发布-订阅(Publish/Subscribe)模式,它定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象 。这个主题对象在状态变化时,会通知所有的观察者对象,使他们能够自动更新自己。
通俗理解:本质上就是监听模式,多个观察者监听同一个事物,如果事物发生变化,多个观察者监听到其变化后会做出相应的处理行为
角色
抽象主题,具体主题,抽象观察者,具体观察者
核心理解
1.核心是定义了一种一对多的依赖关系,该对象做出任何行为,其他依赖该对象的会同步监听到此行为并进行处理
2.观察者模式不仅仅是一对多的依赖的关系,他还对这个一对多的监听依赖关系进行了两层抽象,分别是主题层和观察者层
主题层抽象是为了让观察的事物可以动态变化而不影响原来的代码,满足开闭原则;观察者层抽象同样是为了观察者的类型可以动态变化而不影响原来的代码,同样满足开闭原则
案例实现
【例】微信公众号
在使用微信公众号时,大家都会有这样的体验,当你关注的公众号中有新内容更新的话,它就会推送给关注公众号的微信用户端。我们使用观察者模式来模拟这样的场景,微信用户就是观察者,微信公众号是被观察者,有多个的微信用户关注了程序猿这个公众号。
代码如下:
定义抽象观察者类,里面定义一个更新的方法
1 2 3 public interface Observer { void update (String message) ; }
定义具体观察者类,微信用户是观察者,里面实现了更新的方法
1 2 3 4 5 6 7 8 9 10 11 12 public class WeixinUser implements Observer { private String name; public WeixinUser (String name) { this .name = name; } @Override public void update (String message) { System.out.println(name + "-" + message); } }
定义抽象主题类,提供了attach、detach、notify三个方法
1 2 3 4 5 6 7 8 9 10 11 public interface Subject { public void attach (Observer observer) ; public void detach (Observer observer) ; public void notify (String message) ; }
微信公众号是具体主题(具体被观察者),里面存储了订阅该公众号的微信用户,并实现了抽象主题中的方法
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 public class SubscriptionSubject implements Subject { private List<Observer> weixinUserlist = new ArrayList <Observer>(); @Override public void attach (Observer observer) { weixinUserlist.add(observer); } @Override public void detach (Observer observer) { weixinUserlist.remove(observer); } @Override public void notify (String message) { for (Observer observer : weixinUserlist) { observer.update(message); } } }
客户端程序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public class Client { public static void main (String[] args) { SubscriptionSubject mSubscriptionSubject=new SubscriptionSubject (); WeixinUser user1=new WeixinUser ("孙悟空" ); WeixinUser user2=new WeixinUser ("猪悟能" ); WeixinUser user3=new WeixinUser ("沙悟净" ); mSubscriptionSubject.attach(user1); mSubscriptionSubject.attach(user2); mSubscriptionSubject.attach(user3); mSubscriptionSubject.notify("传智黑马的专栏更新了" ); } }
优缺点
1,优点:
降低了目标与观察者之间的耦合关系,两者之间是抽象耦合关系(被观察者设置成抽象的,可以随意切换躯体的被观察者,并且观察者也设置成抽象的,因为不同的观察者对同一个消息会做出不同的处理)
被观察者发送通知,所有注册的观察者都会收到信息【可以实现广播机制 】
2,缺点:
如果观察者非常多的话 ,那么所有的观察者收到被观察者发送的通知会耗时
如果被观察者有循环依赖 的话,那么被观察者发送通知会使观察者循环调用,会导致系统崩溃
使用场景
对象间存在一对多关系,一个对象的状态发生改变会影响其他对象
当一个抽象模型有两个方面,其中一个方面依赖于另一方面时
JDK中提供的实现
在 Java 源码已经提供了设计好的观察者的相关api了,通过 java.util.Observable(被观察者 )类和 java.util.Observer(观察者 ) 接口定义了观察者模式,只要实现它们的子类就可以编写观察者模式实例
1,Observable类
Observable 类是抽象目标类(被观察者),它有一个 Vector 集合成员变量,用于保存所有要通知的观察者对象,下面来介绍它最重要的 3 个方法。
void addObserver(Observer o) 方法:用于将新的观察者对象添加到集合中。
void notifyObservers(Object arg) 方法:调用集合中的所有观察者对象的 update方法,通知它们数据发生改变。通常越晚加入集合的观察者越先得到通知(底层是数组逆序遍历 )
void setChange() 方法:用来设置一个 boolean 类型的内部标志,注明目标对象发生了变化 。当它为true时,notifyObservers() 才会通知观察者。
2,Observer 接口
Observer 接口是抽象观察者,它监视目标对象的变化,当目标对象发生变化时,观察者得到通知,并调用 update 方法,进行相应的工作
中介者模式 概述
一般来说,同事类之间的关系是比较复杂的,**多个同事类之间互相关联时,他们之间的关系会呈现为复杂的网状结构,这是一种过度耦合的架构,即不利于类的复用,也不稳定(多对多个关联非常复杂,耦合度非常高)**。例如在下左图中,有六个同事类对象,假如对象1发生变化,那么将会有4个对象受到影响。如果对象2发生变化,那么将会有5个对象受到影响。也就是说,同事类之间直接关联的设计是不好的。
如果引入中介者模式,那么同事类之间的关系将变为星型结构,从下右图中可以看到,任何一个类的变动,只会影响的类本身,以及中介者,这样就减小了系统的耦合。一个好的设计,必定不会把所有的对象关系处理逻辑封装在本类中,而是使用一个专门的类来管理那些不属于自己的行为。
定义:
又叫调停模式,定义一个中介角色来封装一系列对象之间的交互,使原有对象之间的耦合松散,且可以独立地改变它们之间的交互
核心的理解:
1.将原来多对图的复杂网状结构的关联转化成了两个一对多的关联 (客户与中介者是n:1,中介者是服务提供者也是1:n),大大降低彼此之间关联的耦合度,提高了彼此之间的可拓展和可维护性
2.引入一个中介者将客户与服务提供者的耦合度转化成中介者与客户,中介者与服务提供者的耦合,大大降低了客户和服务提供者的耦合,增加二者的独立性,可拓展性和可维护性
案例实现
【例】租房
现在租房基本都是通过房屋中介,房主将房屋托管给房屋中介,而租房者从房屋中介获取房屋信息。房屋中介充当租房者与房屋所有者之间的中介者。
类图如下:
简单解析一下类结构
Person:与中介者交互的抽象类,比如是租房人,包租公
Teant:与中介交互的具体租房人
HouseOwner:与中介交互的具体包租公
Mediator:抽象中介者,负责将客户的消息转发给服务提供者
MediatorStrcture:具体中介者,负责将具体的消息转发给服务提供者
代码如下:
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 public abstract class Mediator { public abstract void constact (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 HouseOwner extends Person { public HouseOwner (String name, Mediator mediator) { super (name, mediator); } public void constact (String message) { mediator.constact(message, this ); } public void getMessage (String message) { System.out.println("房主" + name +"获取到的信息:" + message); } } public class Tenant extends Person { public Tenant (String name, Mediator mediator) { super (name, mediator); } public void constact (String message) { mediator.constact(message, this ); } public void getMessage (String message) { System.out.println("租房者" + name +"获取到的信息:" + message); } } public class MediatorStructure extends Mediator { private HouseOwner houseOwner; private Tenant tenant; public HouseOwner getHouseOwner () { return houseOwner; } public void setHouseOwner (HouseOwner houseOwner) { this .houseOwner = houseOwner; } public Tenant getTenant () { return tenant; } public void setTenant (Tenant tenant) { this .tenant = tenant; } public void constact (String message, Person person) { if (person == houseOwner) { tenant.getMessage(message); } else { houseOwner.getMessage(message); } } } public class Client { public static void main (String[] args) { MediatorStructure mediator = new MediatorStructure (); HouseOwner houseOwner = new HouseOwner ("张三" , mediator); Tenant tenant = new Tenant ("李四" , mediator); mediator.setHouseOwner(houseOwner); mediator.setTenant(tenant); tenant.constact("需要租三室的房子" ); houseOwner.constact("我这有三室的房子,你需要租吗?" ); } }
优缺点
1,优点:
松散耦合(通过引入中介者将客户与服务提供的耦合转化成了客户与中介者的耦合,中介者与服务提供者的耦合,并且该耦合是一对多,不再是之前的多对多,大大降低了彼此的耦合度)
中介者模式通过把多个同事对象之间的交互封装到中介者对象里面,从而使得同事对象之间松散耦合,基本上可以做到互补依赖。这样一来,同事对象就可以独立地变化和复用,而不再像以前那样“牵一处而动全身”了
集中控制交互
多个同事对象的交互,被封装在中介者对象里面集中管理,使得这些交互行为发生变化的时候,只需要修改中介者对象就可以了,当然如果是已经做好的系统,那么就扩展中介者对象,而各个同事类不需要做修改
一对多关联转变为一对一的关联(将多对多的关联转化成了两个一对多的关联)
没有使用中介者模式的时候,同事对象之间的关系通常是一对多的,引入中介者对象以后,中介者对象和同事对象的关系通常变成双向的一对一,这会让对象的关系更容易理解和实现
2,缺点:
当同事类太多时,中介者的职责将很大,它会变得复杂而庞大,以至于系统难以维护
使用场景
系统中对象之间存在复杂的引用关系,系统结构混乱且难以理解
当想创建一个运行于多个类之间的对象,又不想生成新的子类时
迭代器模式 概述
定义:
提供一个对象来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示 。
核心理解:将容器的数据结构和容器的遍历方式进行解耦,让容器可以根据自己特定的结构 or 业务场景的需要随时定制和切换安自己的遍历方式
实现方式:抽象+多态
1.抽象迭代器类,因为每种容器的结构不一样,判断和获取元素的方式自然也就不一样,所以必须设计成抽象的,典型的案例就是数组结构实现的容器和链表结构实现的容器结构
2.抽象容器类,因为每种容器底层的数据结构不一样,对应的增删改查方法自然也就不一样,所以必须设计成抽象的
3.jdk源码中具体迭代器类是作为具体容器类的内部类的,外部类是没有查看权限的,进一步隐藏了容器内部的遍历细节
案例实现
【例】定义一个可以存储学生对象的容器对象,将遍历该容器的功能交由迭代器实现,涉及到的类如下:
代码如下:
定义迭代器接口,声明hasNext、next方法
1 2 3 4 5 6 public interface StudentIterator { boolean hasNext () ; Student next () ; }
定义具体的迭代器类,重写所有的抽象方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public class StudentIteratorImpl implements StudentIterator { private List<Student> list; private int position = 0 ; public StudentIteratorImpl (List<Student> list) { this .list = list; } @Override public boolean hasNext () { return position < list.size(); } @Override public Student next () { Student currentStudent = list.get(position); position ++; return currentStudent; } }
定义抽象容器类,包含添加元素,删除元素,获取迭代器对象的方法
1 2 3 4 5 6 7 8 public interface StudentAggregate { void addStudent (Student student) ; void removeStudent (Student student) ; StudentIterator getStudentIterator () ; }
定义具体的容器类,重写所有的方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public class StudentAggregateImpl implements StudentAggregate { private List<Student> list = new ArrayList <Student>(); @Override public void addStudent (Student student) { this .list.add(student); } @Override public void removeStudent (Student student) { this .list.remove(student); } @Override public StudentIterator getStudentIterator () { return new StudentIteratorImpl (list); } }
优缺点
1,优点:
**它支持以不同的方式遍历一个聚合对象,在同一个聚合对象上可以定义多种遍历方式(因为将容器的结构和容器的遍历方式解耦了,所以同一种容器可以根据业务场景的需要动态的定制和切换自己的遍历方式)**,在迭代器模式中只需要用一个不同的迭代器来替换原有迭代器即可改变遍历算法,我们也可以自己定义迭代器的子类以支持新的遍历方式
迭代器简化了聚合类。由于引入了迭代器,在原有的聚合对象中不需要再自行提供数据遍历等方法,这样可以简化聚合类的设计
在迭代器模式中,由于引入了抽象层,增加新的聚合类和迭代器类都很方便,无须修改原有代码,满足 “开闭原则” 的要求(满足开闭原则是因为将容器的结构和容器的遍历方式进行了解耦,如果拓展只需要增加相应的接口实现类,自然满足开闭原则)
2,缺点:
增加了类的个数,这在一定程度上增加了系统的复杂性。
使用场景
当需要为聚合对象提供多种遍历方式时 。
**当需要为遍历不同的聚合结构提供一个统一的接口(不同的容器结构遍历调用的是同一个接口的抽象方法,然后根据传入的具体容器结构动态绑定具体的遍历实现类)**时。
当访问一个聚合对象的内容而无须暴露其内部细节的表示时。
JDK源码解析
迭代器模式在JAVA的很多集合类中被广泛应用,接下来看看JAVA源码中是如何使用迭代器模式的。
1 2 3 4 5 List<String> list = new ArrayList <>(); Iterator<String> iterator = list.iterator(); while (iterator.hasNext()) { System.out.println(iterator.next()); }
看完这段代码是不是很熟悉,与我们上面代码基本类似。单列集合都使用到了迭代器,我们以ArrayList举例来说明
List:抽象聚合类
ArrayList:具体的聚合类
Iterator:抽象迭代器
list.iterator():返回的是实现了 Iterator
接口的具体迭代器对象
具体的来看看 ArrayList的代码实现
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 ArrayList <E> extends AbstractList <E> implements List <E>, RandomAccess, Cloneable, java.io.Serializable { public Iterator<E> iterator () { return new Itr (); } private class Itr implements Iterator <E> { int cursor; int lastRet = -1 ; int expectedModCount = modCount; Itr() {} public boolean hasNext () { return cursor != size; } public E next () { checkForComodification(); int i = cursor; if (i >= size) throw new NoSuchElementException (); Object[] elementData = ArrayList.this .elementData; if (i >= elementData.length) throw new ConcurrentModificationException (); cursor = i + 1 ; return (E) elementData[lastRet = i]; } ... }
这部分代码还是比较简单,大致就是在 iterator
方法中返回了一个实例化的 Iterator
对象。Itr是一个内部类,它实现了 Iterator
接口并重写了其中的抽象方法
Jdk源码已经帮我们提供好了抽象迭代器类java.util.Iterable,有需要的话直接使用该接口实现迭代器模式即可
注意:
当我们在使用JAVA开发的时候,想使用迭代器模式的话,只要让我们自己定义的容器类实现java.util.Iterable
并实现其中的iterator()方法使其返回一个 java.util.Iterator
的实现类就可以了
访问者模式 概述
定义:
封装一些作用于某种数据结构中的各元素的操作,它可以在不改变这个数据结构的前提下定义作用于这些元素的新的操作
核心理解:
1.将被访者和访问者进行解耦,即拓展和更新访问者的操作是不影响被访问者的代码实现的
2.其实将被放在和访问者进行解耦很简单,面向抽象+多态就可以实现(策略模式)动态切换访问者的行为,但这样是有限制的,每一个访问者只能有一种访问行为,虽然可以动态切换访问者进而达到切换访问行为的目的 ,但是如果我想一个访问者有多个访问行为并且实现多个访问者多个行为的切换呢? 那么这种方案就解决不了这个问题
访问者设计模式是用了双分派机制来实现一个访问者可以有多个行为,并且可以在多个访问者和多个行为之间切换
双分派机制的核心是:先用动态分派机制实现访问者的切换,再在动态执行的方法中使用静态分配机制实现同一个访问者不同的访问行为的切换
案例实现
【例】给宠物喂食
现在养宠物的人特别多,我们就以这个为例,当然宠物还分为狗,猫等,要给宠物喂食的话,主人可以喂,其他人也可以喂食。
访问者角色:给宠物喂食的人
具体访问者角色:主人、其他人
抽象元素角色:动物抽象类
具体元素角色:宠物狗、宠物猫
结构对象角色:主人家
类图如下:
代码如下:
创建抽象访问者接口
1 2 3 4 5 public interface Person { void feed (Cat cat) ; void feed (Dog dog) ; }
创建不同的具体访问者角色(主人和其他人),都需要实现 Person
接口
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 Owner implements Person { @Override public void feed (Cat cat) { System.out.println("主人喂食猫" ); } @Override public void feed (Dog dog) { System.out.println("主人喂食狗" ); } } public class Someone implements Person { @Override public void feed (Cat cat) { System.out.println("其他人喂食猫" ); } @Override public void feed (Dog dog) { System.out.println("其他人喂食狗" ); } }
定义抽象节点 – 宠物
1 2 3 public interface Animal { void accept (Person person) ; }
定义实现Animal
接口的 具体节点(元素)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public class Dog implements Animal { @Override public void accept (Person person) { person.feed(this ); System.out.println("好好吃,汪汪汪!!!" ); } } public class Cat implements Animal { @Override public void accept (Person person) { person.feed(this ); System.out.println("好好吃,喵喵喵!!!" ); } }
定义对象结构,此案例中就是主人的家
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class Home { private List<Animal> nodeList = new ArrayList <Animal>(); public void action (Person person) { for (Animal node : nodeList) { node.accept(person); } } public void add (Animal animal) { nodeList.add(animal); } }
测试类
1 2 3 4 5 6 7 8 9 10 11 12 13 public class Client { public static void main (String[] args) { Home home = new Home (); home.add(new Dog ()); home.add(new Cat ()); Owner owner = new Owner (); home.action(owner); Someone someone = new Someone (); home.action(someone); } }
优缺点
1,优点:
扩展性好
在不修改对象结构中的元素的情况下,为对象结构中的元素添加新的功能**(意思就拓展 or 修改访问者的行为不影响被访问者的对象结构)**
复用性好
通过访问者来定义整个对象结构通用的功能,从而提高复用程度。
分离无关行为
通过访问者来分离无关的行为,把相关的行为封装在一起,构成一个访问者,这样每一个访问者的功能都比较单一。
2,缺点:
使用场景
扩展
访问者模式用到了一种双分派的技术
1,分派:
变量被声明时的类型叫做变量的静态类型,有些人又把静态类型叫做明显类型;而变量所引用的对象的真实类型又叫做变量的实际类型。比如 Map map = new HashMap()
,map变量的静态类型是 Map
,实际类型是 HashMap
。根据对象的类型而对方法进行的选择,就是分派(Dispatch),分派(Dispatch)又分为两种,即静态分派和动态分派
静态分派(Static Dispatch) 发生在编译时期,分派根据静态类型信息发生。静态分派对于我们来说并不陌生,方法重载就是静态分派**(方法重载就是静态分派,编译时就会根据声明的类型(不是实际的类型)确定调用的方法)**
动态分派(Dynamic Dispatch) 发生在运行时期,动态分派动态地置换掉某个方法。Java通过方法的重写支持动态分派**(方法覆盖重写就是动态分派机制,编译时是无法确定具体执行的方法,因为父子类可能都拥有该方法,只有在运行时才能确定传入的实际类型,进而调用对应的方法)**
2,动态分派:
通过方法的重写支持动态分派。
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 public class Animal { public void execute () { System.out.println("Animal" ); } } public class Dog extends Animal { @Override public void execute () { System.out.println("dog" ); } } public class Cat extends Animal { @Override public void execute () { System.out.println("cat" ); } } public class Client { public static void main (String[] args) { Animal a = new Dog (); a.execute(); Animal a1 = new Cat (); a1.execute(); } }
上面代码的结果大家应该直接可以说出来,这不就是多态吗!运行执行的是子类中的方法。
Java编译器在编译时期并不总是知道哪些代码会被执行,因为编译器仅仅知道对象的静态类型,而不知道对象的真实类型;
3,静态分派:
通过方法重载支持静态分派。
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 public class Animal {} public class Dog extends Animal {} public class Cat extends Animal {} public class Execute { public void execute (Animal a) { System.out.println("Animal" ); } public void execute (Dog d) { System.out.println("dog" ); } public void execute (Cat c) { System.out.println("cat" ); } } public class Client { public static void main (String[] args) { Animal a = new Animal (); Animal a1 = new Dog (); Animal a2 = new Cat (); Execute exe = new Execute (); exe.execute(a); exe.execute(a1); exe.execute(a2); } }
运行结果:
这个结果可能出乎一些人的意料了,为什么呢?
重载方法的分派是根据静态类型进行的,这个分派过程在编译时期就完成了。
4,双分派:
所谓双分派技术就是在选择一个方法的时候,不仅仅要根据消息接收者(receiver)的运行时区别,还要根据参数的运行时区别。
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 Animal { public void accept (Execute exe) { exe.execute(this ); } } public class Dog extends Animal { public void accept (Execute exe) { exe.execute(this ); } } public class Cat extends Animal { public void accept (Execute exe) { exe.execute(this ); } } public class Execute { public void execute (Animal a) { System.out.println("animal" ); } public void execute (Dog d) { System.out.println("dog" ); } public void execute (Cat c) { System.out.println("cat" ); } } public class Client { public static void main (String[] args) { Animal a = new Animal (); Animal d = new Dog (); Animal c = new Cat (); Execute exe = new Execute (); a.accept(exe); d.accept(exe); c.accept(exe); } }
在上面代码中,客户端将Execute对象做为参数传递给Animal类型的变量调用的方法,这里完成第一次分派,这里是方法重写,所以是动态分派,也就是执行实际类型中的方法,同时也将自己this作为参数传递进去,这里就完成了第二次分派
,这里的Execute类中有多个重载的方法,而传递进行的是this,就是具体的实际类型的对象。
说到这里,我们已经明白双分派是怎么回事了,但是它有什么效果呢?就是可以实现方法的动态绑定,我们可以对上面的程序进行修改。
运行结果如下:
双分派实现动态绑定的本质,就是在重载方法委派的前面加上了继承体系中覆盖的环节,由于覆盖是动态的,所以重载就是动态的了,先动态分派后静态分派
备忘录模式 概述
备忘录模式提供了一种状态恢复的实现机制,使得用户可以方便地回到一个特定的历史步骤,当新的状态无效或者存在问题时,可以使用暂时存储起来的备忘录将状态复原,很多软件都提供了撤销(Undo)操作,如 Word、记事本、Photoshop、IDEA等软件在编辑时按 Ctrl+Z 组合键时能撤销当前操作,使文档恢复到之前的状态;还有在 浏览器 中的后退键、数据库事务管理中的回滚操作、玩游戏时的中间结果存档功能、数据库与操作系统的备份操作、棋类游戏中的悔棋功能等都属于这类。
定义:
又叫快照模式,在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,以便以后当需要时能将该对象恢复到原先保存的状态
备忘录有两个等效的接口:
窄接口 :管理者(Caretaker)对象(和其他发起人对象之外的任何对象)看到的是备忘录的窄接口(narror Interface),这个窄接口只允许他把备忘录对象传给其他的对象 。
宽接口 :与管理者看到的窄接口相反,发起人对象可以看到一个宽接口(wide Interface),这个宽接口允许它读取所有的数据,以便根据这些数据恢复这个发起人对象的内部状态 。
案例实现
【例】游戏挑战BOSS
游戏中的某个场景,一游戏角色有生命力、攻击力、防御力等数据,在打Boss前和后一定会不一样的,我们允许玩家如果感觉与Boss决斗的效果不理想可以让游戏恢复到决斗之前的状态。
要实现上述案例,有两种方式:
“白箱”备忘录模式
备忘录角色对任何对象都提供一个接口,即宽接口,备忘录角色的内部所存储的状态就对所有对象公开。类图如下:
简单解析一下类图:
GameRole:备忘录发起人,负责创建备忘录和恢复备忘录 RoleStateMemento:备忘录保存的状态的信息 RoleStateCaretaker:备忘录管理者,设置备忘录和负责获取备忘录
备忘录管理者的作用是负责存储和管理备忘录信息,简化发起人的职责,发起人只需要发起存储请求和恢复请求,具体的备忘录信息全部交由备忘录管理者来挂案例
代码如下:
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 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 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 roleStateMemento) { this .vit = roleStateMemento.getVit(); this .atk = roleStateMemento.getAtk(); this .def = roleStateMemento.getDef(); } public void stateDisplay () { System.out.println("角色生命力:" + vit); System.out.println("角色攻击力:" + atk); System.out.println("角色防御力:" + def); } public int getVit () { return vit; } public void setVit (int vit) { this .vit = vit; } public int getAtk () { return atk; } public void setAtk (int atk) { this .atk = atk; } public int getDef () { return def; } public void setDef (int def) { this .def = 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 getVit () { return vit; } public void setVit (int vit) { this .vit = vit; } public int getAtk () { return atk; } public void setAtk (int atk) { this .atk = atk; } public int getDef () { return def; } public void setDef (int def) { this .def = def; } } public class RoleStateCaretaker { private RoleStateMemento roleStateMemento; public RoleStateMemento getRoleStateMemento () { return roleStateMemento; } public void setRoleStateMemento (RoleStateMemento roleStateMemento) { this .roleStateMemento = roleStateMemento; } } public class Client { public static void main (String[] args) { System.out.println("------------大战Boss前------------" ); GameRole gameRole = new GameRole (); gameRole.initState(); gameRole.stateDisplay(); RoleStateCaretaker roleStateCaretaker = new RoleStateCaretaker (); roleStateCaretaker.setRoleStateMemento(gameRole.saveState()); System.out.println("------------大战Boss后------------" ); gameRole.fight(); gameRole.stateDisplay(); System.out.println("------------恢复之前状态------------" ); gameRole.recoverState(roleStateCaretaker.getRoleStateMemento()); gameRole.stateDisplay(); } }
分析:白箱备忘录模式是破坏封装性的。但是通过程序员自律,同样可以在一定程度上实现模式的大部分用意。
“黑箱”备忘录模式
备忘录角色对发起人对象提供一个宽接口,而为其他对象提供一个窄接口。在Java语言中,实现双重接口的办法就是将备忘录类设计成发起人类的内部成员类
将 RoleStateMemento
设为 GameRole
的内部类,从而将 RoleStateMemento
对象(实际的接口实现类)封装在 GameRole
里面(作为GameRole的成员内部类); 在外面提供一个标识接口 Memento
给 RoleStateCaretaker
及其他对象使用。这样 GameRole
类看到的是 RoleStateMemento
所有的接口,而RoleStateCaretaker
及其他对象看到的仅仅是标识接口 Memento
所暴露出来的接口,从而维护了封装型。类图如下:
通俗点的解释:
代码如下:
窄接口Memento
,这是一个标识接口,因此没有定义出任何的方法
1 2 public interface Memento {}
定义发起人类 GameRole
,并在内部定义备忘录内部类 RoleStateMemento
(该内部类设置为私有的)
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 package com.czq.design.pattern.memento.black_box;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 roleStateMemento = (RoleStateMemento) memento; this .vit = roleStateMemento.getVit(); this .atk = roleStateMemento.getAtk(); this .def = roleStateMemento.getDef(); } public void stateDisplay () { System.out.println("角色生命力:" + vit); System.out.println("角色攻击力:" + atk); System.out.println("角色防御力:" + def); } public int getVit () { return vit; } public void setVit (int vit) { this .vit = vit; } public int getAtk () { return atk; } public void setAtk (int atk) { this .atk = atk; } public int getDef () { return def; } public void setDef (int def) { this .def = def; } private class RoleStateMemento implements Memento { 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 getVit () { return vit; } public void setVit (int vit) { this .vit = vit; } public int getAtk () { return atk; } public void setAtk (int atk) { this .atk = atk; } public int getDef () { return def; } public void setDef (int def) { this .def = def; } } }
负责人角色类 RoleStateCaretaker
能够得到的备忘录对象是以 Memento
为接口的,由于这个接口仅仅是一个标识接口,因此负责人角色不可能改变这个备忘录对象的内容
1 2 3 4 5 6 7 8 9 10 11 12 public class RoleStateCaretaker { private Memento memento; public Memento getMemento () { return memento; } public void setMemento (Memento memento) { this .memento = memento; } }
客户端测试类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public class Client { 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(); } }
优缺点
1,优点:
提供了一种可以恢复状态的机制。当用户需要时能够比较方便地将数据恢复到某个历史的状态
实现了内部状态的封装,除了创建它的发起人之外,其他对象都不能够访问这些状态信息
简化了发起人类,发起人不需要管理和保存其内部状态的各个备份,所有状态信息都保存在备忘录中,并由管理者进行管理,这符合单一职责原则
2,缺点:
资源消耗大。如果要保存的内部状态信息过多或者特别频繁,将会占用比较大的内存资源。
使用场景
解释器模式 概述
如上图,设计一个软件用来进行加减计算。我们第一想法就是使用工具类,提供对应的加法和减法的工具方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public static int add (int a,int b) { return a + b; } public static int add (int a,int b,int c) { return a + b + c; } public static int add (Integer ... arr) { int sum = 0 ; for (Integer i : arr) { sum += i; } return sum; }
上面的形式比较单一、有限,如果形式变化非常多,这就不符合要求,因为加法和减法运算,两个运算符与数值可以有无限种组合方式 。比如 1+2+3+4+5、1+2+3-4等等。
显然,现在需要一种翻译识别机器,能够解析由数字以及 + - 符号构成的合法的运算序列。如果把运算符和数字都看作节点的话,能够逐个节点的进行读取解析运算,这就是解释器模式的思维。
定义:
给定一个语言,定义它的文法表示,并定义一个解释器,这个解释器使用该标识来解释语言中的句子。
在解释器模式中,我们需要将待解决的问题,提取出规则,抽象为一种“语言” 。比如加减法运算,规则为:由数值和+-符号组成的合法序列,“1+3-2” 就是这种语言的句子。
核心理解:对表达式的解析规则进行抽象并根据具体的规则定制解析方案,比如一个基本算数运算,有加法规则,减法规则,乘法规则,除法规则
解释器就是要解析出来语句的含义。但是如何描述规则呢 ?
文法(语法)规则:
文法是用于描述语言的语法结构的形式规则。
1 2 3 4 5 6 7 8 //抽象表达式 expression ::= value | plus | minus //加法表达式 plus ::= expression ‘+’ expression //减法表达式 minus ::= expression ‘-’ expression //获取值的表达式 value ::= integer
注意: 这里的符号“::=”表示“定义为”的意思,竖线 | 表示或,左右的其中一个,引号内为字符本身,引号外为语法。
上面规则描述为 :
表达式可以是一个值,也可以是plus或者minus运算,而plus和minus又是由表达式结合运算符构成,值的类型为整型数 。
抽象语法树:
在计算机科学中,抽象语法树(AbstractSyntaxTree,AST),或简称语法树(Syntax tree),是源代码语法结构的一种抽象表示。它以树状的形式表现编程语言的语法结构,树上的每个节点都表示源代码中的一种结构。
用树形来表示符合文法规则的句子
案例实现
【例】设计实现加减法的软件
对类图的简单解析:
AbstractExpression:抽象表达式类,定义抽象表达式的解析规则
Plus:加法表达式,定义加法表达式的解析规则
Minus:减法表达式,定义减法表达式的解析规则
Value:值表达式,定义获取值的解析规则
代码如下:
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 118 119 120 121 122 public abstract class AbstractExpression { public abstract int interpret (Context context) ; } public class Value extends AbstractExpression { private int value; public Value (int value) { this .value = value; } @Override public int interpret (Context context) { return value; } @Override public String toString () { return new Integer (value).toString(); } } public class Plus extends AbstractExpression { private AbstractExpression left; private AbstractExpression right; public Plus (AbstractExpression left, AbstractExpression right) { this .left = left; this .right = right; } @Override 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; } @Override public int interpret (Context context) { return left.interpret(context) - right.interpret(context); } @Override public String toString () { return "(" + left.toString() + " - " + right.toString() + ")" ; } } public class Variable extends AbstractExpression { private String name; public Variable (String name) { this .name = name; } @Override public int interpret (Context ctx) { return ctx.getValue(this ); } @Override public String toString () { return name; } } public class Context { private Map<Variable, Integer> map = new HashMap <Variable, Integer>(); public void assign (Variable var , Integer value) { map.put(var , value); } public int getValue (Variable var ) { Integer value = map.get(var ); return value; } } public class Client { 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" ); Variable e = new Variable ("e" ); context.assign(a, 1 ); context.assign(b, 2 ); context.assign(c, 3 ); context.assign(d, 4 ); context.assign(e, 5 ); AbstractExpression expression = new Minus (new Plus (new Plus (new Plus (a, b), c), d), e); System.out.println(expression + "= " + expression.interpret(context)); } }
优缺点
1,优点:
易于改变和扩展文法(因为语法表达式被定义为抽象的,直接继承就可以直接拓展不同规则的表达式了)
由于在解释器模式中使用类来表示语言的文法规则,因此可以通过继承等机制来改变或扩展文法。每一条文法规则都可以表示为一个类,因此可以方便地实现一个简单的语言
实现文法较为容易
在抽象语法树中每一个表达式节点类的实现方式都是相似的,这些类的代码编写都不会特别复杂
增加新的解释表达式较为方便**(还是因为表达式是设置为抽象的,拓展性强)**
如果用户需要增加新的解释表达式只需要对应增加一个新的终结符表达式或非终结符表达式类,原有表达式类代码无须修改,符合 “开闭原则”
2,缺点:
解释器设计模式本质上是将语法的解析逻辑转化成了类与类的关系来实现,如果是直接用逻辑来实现,必然是用树结构来实现的,但转化成了类与类的关系来实现,就不用树形结构实现,但底层实现的思想还是树型结构的思想(语法树),所以在表达式的解析过程中,还是用到了递归 or 循环来解析,当语法过于复杂时,解析的性能就会非常差
上面的案例是用递归来进行解析的,根据传入具体的表达式动态选择相应表达式解析规则类进行解析,是先自顶向下获取到终结表达式(变量的数值,终结表达式也是递归的出口),然后再自低向上进行不断的解析和运算,在这个过程中会不断的动态切换具体的规则解析类进行解析,和树的递归遍历思想是一致的
使用场景
设计模式总结 学了那么多设计模式最大的感觉除了面向抽象编程进行解耦合,多态动态选择执行的方法外,还有就是要将业务逻辑设计转化类与类的关系设计,因为一旦转化类与类的关系设计,那么可拓展性和可维护性就会强很多,因为类与类的关系支持拓展和维护的维度有很多,如继承,实现,组合,抽象等等
设计模式后续需要深入学习的地方
1.需要深入理解每种设计模式的实现过程,优缺点和应用场景
2.同一种类型的模式(如结构型模式)实现的思想非常类似,研究清楚他们的区别
3.结合开源框架的源码(如Spring,SpringMvc,SpringBoot)继续深入学习设计模式的实践应用
设计模式课程总结 此总结是学校实验和大作业总结,属于学校内部实践课总结
1.发现很多设计模式理解都比较浅,以下是某些设计模式经过这次实践后更为深入的理解
A.原型模式。原型模式分为浅克隆和深克隆,我们要的独立copy一份出来,不受原来的影响。如果只是复制原来的引用指针,那叫浅克隆,如果是完全新的一份数据副本,那叫深克隆
B.装饰器设计模式,核心是两层抽象实现被装饰者和装饰者的解耦。两层抽象分别是被装饰者层和装饰者,让其都可以实现任意切换。而且有个细节点就是装饰者本身也是属于装饰者的一种,因为被装饰者也可以被下一层装饰者进行装饰的(类型兼容)
C.外观模式就是暴露给外部使用的接口,但屏蔽所有使用细节,降低使用成本,比如启动电脑,你不需要里面具体的零件是怎么启动的,你只需要按开机键即可完成开机
D.命令模式暂时还没学明白,需要用到的时候再深入学习
E.迭代器模式是实现数据容器和数据遍历方式的解耦合,所以抽象层也有两层,分别是数据容器层和数据遍历方式层
F.观察者模式同样是两层抽象,被观察者的抽象,观察者的抽象
2.理解设计模式的核心思想是面向抽象编程的思想,设计模式的目的就是高内聚,低耦合,只有实现这个目的才能让程序有更好的扩展性和维护性,而高内聚,低耦合的核心思想就是面向抽象编程思想,只有抽象才能将强耦合转换成松耦合
3.根据设计模式面向抽象编程的核心思想,每种设计模式都划分出了对应的角色,比如抽象xxx角色,具体xxx角色,带着面向抽象编程的思想去理解相应的角色职责便能更好,更容易的理解设计模式
SpringIOC容器的简单解析 Spring核心功能结构 Spring大约有20个模块,由1300多个不同的文件构成。这些模块可以分为:
核心容器、AOP和设备支持、数据访问与集成、Web组件、通信报文和集成测试等,下面是 Spring 框架的总体架构图:
![Spring 框架的总体架构图](Spring 框架的总体架构图.png)
核心容器由 beans、core、context 和 expression(Spring Expression Language,SpEL)4个模块组成。
spring-beans和spring-core模块是Spring框架的核心模块,包含了控制反转(Inversion of Control,IOC)和依赖注入(Dependency Injection,DI) 。BeanFactory使用控制反转对应用程序的配置和依赖性规范与实际的应用程序代码进行了分离(依赖注入将对象创建和使用先分离(实际上依赖注入就是设计模式中的依赖倒转原则) )。BeanFactory属于延时加载 ,也就是说在实例化容器对象后并不会自动实例化Bean,只有当Bean被使用时,BeanFactory才会对该 Bean 进行实例化与依赖关系的装配。
spring-context模块构架于核心模块之上,扩展了BeanFactory,为它添加了Bean生命周期控制、框架事件体系及资源加载透明化等功能 。此外,该模块还提供了许多企业级支持,如邮件访问、远程访问、任务调度等,ApplicationContext 是该模块的核心接口 ,它的超类是 BeanFactory。与BeanFactory不同,ApplicationContext实例化后会自动对所有的单实例Bean进行实例化与依赖关系的装配 ,使之处于待用状态。
spring-context-support模块是对Spring IoC容器及IoC子容器的扩展支持。
spring-context-indexer模块是Spring的类管理组件和Classpath扫描组件。
spring-expression 模块是统一表达式语言(EL)的扩展模块,可以查询、管理运行中的对象,同时也可以方便地调用对象方法,以及操作数组、集合等。它的语法类似于传统EL,但提供了额外的功能,最出色的要数函数调用和简单字符串的模板函数。EL的特性是基于Spring产品的需求而设计的,可以非常方便地同Spring IoC进行交互。
bean概述 Spring 就是面向 Bean
的编程(BOP,Bean Oriented Programming),Bean 在 Spring 中处于核心地位。Bean对于Spring的意义就像Object对于OOP的意义一样,Spring中没有Bean也就没有Spring存在的意义。Spring IoC容器通过配置文件或者注解的方式来管理bean对象之间的依赖关系。
spring中bean用于对一个类进行封装。如下面的配置:
下面的配置是对一个bean对象的相关数据的配置,后续将会加载这些bean的配置文件并解析
1 2 3 4 <bean id ="userService" class ="com.itheima.service.impl.UserServiceImpl" > <property name ="userDao" ref ="userDao" > </property > </bean > <bean id ="userDao" class ="com.itheima.dao.impl.UserDaoImpl" > </bean >
为什么Bean如此重要呢?
spring 将bean对象交由一个叫IOC容器进行管理。
bean对象之间的依赖关系在配置文件中体现 ,并由spring完成。
Spring IOC相关接口分析 BeanFactory解析 Spring中Bean的创建是典型的工厂模式,这一系列的Bean工厂,即IoC容器,为开发者管理对象之间的依赖关系提供了很多便利和基础服务,在Spring中有许多IoC容器的实现供用户选择,其相互关系如下图所示。
其中,BeanFactory作为最顶层的一个接口,定义了IoC容器的基本功能规范,BeanFactory有三个重要的子接口:ListableBeanFactory、HierarchicalBeanFactory和AutowireCapableBeanFactory。但是从类图中我们可以发现最终的默认实现类是DefaultListableBeanFactory,它实现了所有的接口。
那么为何要定义这么多层次的接口呢?
每个接口都有它的使用场合,主要是为了区分在Spring内部操作过程中对象的传递和转化,对对象的数据访问所做的限制 ,例如,
ListableBeanFactory接口表示这些Bean可列表化。
HierarchicalBeanFactory表示这些Bean 是有继承关系的,也就是每个 Bean 可能有父 Bean
AutowireCapableBeanFactory 接口定义Bean的自动装配规则。
这三个接口共同定义了Bean的集合、Bean之间的关系及Bean行为。最基本的IoC容器接口是BeanFactory,来看一下它的源码:
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 public interface BeanFactory { String FACTORY_BEAN_PREFIX = "&" ; Object getBean (String name) throws BeansException; <T> T getBean (String name, Class<T> requiredType) throws BeansException; Object getBean (String name, Object... args) throws BeansException; <T> T getBean (Class<T> requiredType) throws BeansException; <T> T getBean (Class<T> requiredType, Object... args) throws BeansException; <T> ObjectProvider<T> getBeanProvider (Class<T> requiredType) ; <T> ObjectProvider<T> getBeanProvider (ResolvableType requiredType) ; boolean containsBean (String name) ; boolean isSingleton (String name) throws NoSuchBeanDefinitionException; boolean isPrototype (String name) throws NoSuchBeanDefinitionException; boolean isTypeMatch (String name, ResolvableType typeToMatch) throws NoSuchBeanDefinitionException; boolean isTypeMatch (String name, Class<?> typeToMatch) throws NoSuchBeanDefinitionException; @Nullable Class<?> getType(String name) throws NoSuchBeanDefinitionException; String[] getAliases(String name); }
**在BeanFactory里只对IoC容器的基本行为做了定义,根本不关心你的Bean是如何定义及怎样加载的(即只关心获取bean对象,至于具体bean对象是如何加载和初始化的交由具体的实现类来实现)**。正如我们只关心能从工厂里得到什么产品,不关心工厂是怎么生产这些产品的
BeanFactory有一个很重要的子接口,就是ApplicationContext接口,该接口主要来规范容器中的bean对象是非延时加载 ,即在创建容器对象的时候就对象bean进行初始化,并存储到一个容器中。
要知道工厂是如何产生对象的,我们需要看具体的IoC容器实现,Spring提供了许多IoC容器实现,比如:
ClasspathXmlApplicationContext : 根据类路径加载xml配置文件,并创建IOC容器对象。
FileSystemXmlApplicationContext :根据系统路径加载xml配置文件,并创建IOC容器对象。
AnnotationConfigApplicationContext :加载注解类配置,并创建IOC容器。
BeanDefinition解析 Spring IoC容器管理我们定义的各种Bean对象及其相互关系,而Bean对象在Spring实现中是以BeanDefinition来描述的,如下面配置文件
BeanDefinition是用来描述和存储bean对象的配置文件信息,并且还可以描述bean与bean之间的依赖关系
BeanDefintion同样被设计成抽象的,因为描述的bean的方式有很多种,常见的有xml配置文件,propertity配置文件,注解等等,需要根据具体的业务场景进行实际的选择,同时设计成抽象的也方便后续拓展描述bean配置文件的方式
1 2 3 4 <bean id ="userDao" class ="com.itheima.dao.impl.UserDaoImpl" > </bean > bean标签还有很多属性: scope、init-method、destory-method等。
其继承体系如下图所示。
BeanDefinitionReader解析 Bean的解析过程非常复杂,功能被分得很细,因为这里需要被扩展的地方很多,必须保证足够的灵活性,以应对可能的变化,Bean的解析主要就是对Spring配置文件的解析。这个解析过程主要通过BeanDefinitionReader来完成 ,看看Spring中BeanDefinitionReader的类结构图,如下图所示
既然bean的描述方式多种多样,那么bean的解析方式也多种多样,必须设计成抽象的,方便后续根据实际业务场景需要进行拓展和替换
看看BeanDefinitionReader接口定义的功能来理解它具体的作用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public interface BeanDefinitionReader { BeanDefinitionRegistry getRegistry () ; @Nullable ResourceLoader getResourceLoader () ; @Nullable ClassLoader getBeanClassLoader () ; BeanNameGenerator getBeanNameGenerator () ; int loadBeanDefinitions (Resource resource) throws BeanDefinitionStoreException; int loadBeanDefinitions (Resource... resources) throws BeanDefinitionStoreException; int loadBeanDefinitions (String location) throws BeanDefinitionStoreException; int loadBeanDefinitions (String... locations) throws BeanDefinitionStoreException; }
BeanDefinitionRegistry解析 BeanDefinitionReader用来解析bean定义,并封装BeanDefinition对象,而我们定义的配置文件中定义了很多bean标签,所以就有一个问题,解析的BeanDefinition对象存储到哪儿?答案就是BeanDefinition的注册中心,而该注册中心顶层接口就是BeanDefinitionRegistry
将解析好的bean的配置文件信息对象全部存储到注册表BeanDefinitionRegistry中,因为bean的配置文件中定义多了bean对象的配置文件,所以必须有一个配置文件信息的容器来进行存储,同样存储bean配置文件的容器实现也有很多种,必须设计成抽象的,方便后续的拓展和替换
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public interface BeanDefinitionRegistry extends AliasRegistry { void registerBeanDefinition (String beanName, BeanDefinition beanDefinition) throws BeanDefinitionStoreException; void removeBeanDefinition (String beanName) throws NoSuchBeanDefinitionException; BeanDefinition getBeanDefinition (String beanName) throws NoSuchBeanDefinitionException; boolean containsBeanDefinition (String beanName) ; String[] getBeanDefinitionNames(); int getBeanDefinitionCount () ; boolean isBeanNameInUse (String beanName) ; }
继承结构图如下:
从上面类图可以看到BeanDefinitionRegistry接口的子实现类主要有以下几个:
为什么使用 ConcurrentHashMaps而不使用HashMap,因为前者是线程安全的,后者是线程不安全的
创建容器 创建bean的IOC容器总共两个过程
1.加载和解析bean的配置文件,并将解析好的bean配置文件信息注册到注册表中
2.利用获取到的bean的配置文件信息初始化bean对象并将bean对象注册到IOC容器中
创建bean的IOC容器由beanFactory的具体实现类调用相应的方法,比如ClassPathXmlApplicationContext的方法就会调用refresh()方法来完成创建IOC容器的过程,因为IOC容器的创建算法是固定的,但里面很多步骤依赖于具体的环境变化而变化,所以使用模板方法的设计模式
创建IOC容器的相关类解析
BeanDefinition:负责描述和存储bean的配置文件信息
BeanDefinitionReader:负责加载和解析bean的配置文件信息
BeanDefinitionRegistry:负责将解析好的bean配置文件信息注册到注册表中
BeanFactory:负责创建IOC容器并提过获取IOC容器bean对象的方法
自定义SpirngIOC容器 pojo包:负责描述和存储Bean的配置文件信息
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 public class BeanDefinition { private String id; private String className; private MutablePropertyValues propertyValues; public BeanDefinition () { propertyValues = new MutablePropertyValues (); } public String getId () { return id; } public void setId (String id) { this .id = id; } public String getClassName () { return className; } public void setClassName (String className) { this .className = className; } public MutablePropertyValues getPropertyValues () { return propertyValues; } public void setPropertyValues (MutablePropertyValues propertyValues) { this .propertyValues = propertyValues; } }
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 public class MutablePropertyValues implements Iterable <PropertyValue>{ private final List<PropertyValue> propertyValueList; public MutablePropertyValues () { propertyValueList = new ArrayList <PropertyValue>(); } public MutablePropertyValues (List<PropertyValue> propertyValueList) { if (propertyValueList == null ){ this .propertyValueList = new ArrayList <PropertyValue>(); }else { this .propertyValueList = propertyValueList; } } public Iterator<PropertyValue> iterator () { return propertyValueList.iterator(); } public PropertyValue[] getPropertyValues(){ return propertyValueList.toArray(new PropertyValue [0 ]); } public PropertyValue getPropertyValue (String name) { for (PropertyValue propertyValue : propertyValueList) { if (propertyValue.getName().equals(name)){ return propertyValue; } } return null ; } public boolean isEmpty () { return this .propertyValueList.isEmpty(); } public MutablePropertyValues addPropertyValue (PropertyValue pv) { for (int i = 0 ; i < this .propertyValueList.size(); i++) { PropertyValue currentPv = this .propertyValueList.get(i); if (currentPv.getName().equals(pv.getName())) { this .propertyValueList.set(i, new PropertyValue (pv.getName(),pv.getRef(), pv.getValue())); return this ; } } this .propertyValueList.add(pv); return this ; } public boolean contains (String propertyName) { return getPropertyValue(propertyName) != null ; } }
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 public class PropertyValue { private String name; private String ref; private String value; public PropertyValue () { } public PropertyValue (String name, String ref, String value) { this .name = name; this .ref = ref; this .value = value; } public String getName () { return name; } public void setName (String name) { this .name = name; } public String getRef () { return ref; } public void setRef (String ref) { this .ref = ref; } public String getValue () { return value; } public void setValue (String value) { this .value = value; } }
support包:负责加载和解析bean的配置文件信息,并将加载好的配置文件信息存储到注册表中
BeanDefinitionReader类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public interface BeanDefinitionReader { BeanDefinitionRegistry getRegistry () ; void loadBeanDefinitions (String configLocation) throws Exception; }
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 public class XmlBeanDefinitionReader implements BeanDefinitionReader { private BeanDefinitionRegistry registry; public XmlBeanDefinitionReader () { this .registry = new SimpleBeanDefinitionRegistry (); } @Override public BeanDefinitionRegistry getRegistry () { return registry; } @Override public void loadBeanDefinitions (String configLocation) throws Exception { InputStream is = this .getClass().getClassLoader().getResourceAsStream(configLocation); SAXReader reader = new SAXReader (); Document document = reader.read(is); Element rootElement = document.getRootElement(); parseBean(rootElement); } private void parseBean (Element rootElement) { List<Element> elements = rootElement.elements(); for (Element element : elements) { String id = element.attributeValue("id" ); String className = element.attributeValue("class" ); BeanDefinition beanDefinition = new BeanDefinition (); beanDefinition.setId(id); beanDefinition.setClassName(className); List<Element> list = element.elements("property" ); MutablePropertyValues mutablePropertyValues = new MutablePropertyValues (); for (Element element1 : list) { String name = element1.attributeValue("name" ); String ref = element1.attributeValue("ref" ); String value = element1.attributeValue("value" ); PropertyValue propertyValue = new PropertyValue (name,ref,value); mutablePropertyValues.addPropertyValue(propertyValue); } beanDefinition.setPropertyValues(mutablePropertyValues); registry.registerBeanDefinition(id,beanDefinition); } } }
BeanDefinitionRegistry类
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 package com.czq.spring.framework.beas.factory.support;import com.czq.spring.framework.beas.factory.pojo.BeanDefinition;public interface BeanDefinitionRegistry { void registerBeanDefinition (String beanName, BeanDefinition beanDefinition) ; void removeBeanDefinition (String beanName) throws Exception; BeanDefinition getBeanDefinition (String beanName) throws Exception; boolean containsBeanDefinition (String beanName) ; int getBeanDefinitionCount () ; String[] getBeanDefinitionNames(); }
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 package com.czq.spring.framework.beas.factory.support;import com.czq.spring.framework.beas.factory.pojo.BeanDefinition;import java.util.HashMap;import java.util.Map;public class SimpleBeanDefinitionRegistry implements BeanDefinitionRegistry { private Map<String, BeanDefinition> beanDefinitionMap = new HashMap <String, BeanDefinition>(); @Override public void registerBeanDefinition (String beanName, BeanDefinition beanDefinition) { beanDefinitionMap.put(beanName,beanDefinition); } @Override public void removeBeanDefinition (String beanName) throws Exception { beanDefinitionMap.remove(beanName); } @Override public BeanDefinition getBeanDefinition (String beanName) throws Exception { return beanDefinitionMap.get(beanName); } @Override public boolean containsBeanDefinition (String beanName) { return beanDefinitionMap.containsKey(beanName); } @Override public int getBeanDefinitionCount () { return beanDefinitionMap.size(); } @Override public String[] getBeanDefinitionNames() { return beanDefinitionMap.keySet().toArray(new String [1 ]); } }
context包:负责创建IOC容器并获取bean对象的相关方法
1 2 3 4 5 6 7 8 9 public interface BeanFactory { Object getBean (String name) throws Exception; <T> T getBean (String name, Class<? extends T> clazz) throws Exception; }
1 2 3 4 5 6 7 8 public interface ApplicationContext extends BeanFactory { void refresh () throws IllegalStateException, Exception; }
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 public abstract class AbstractApplicationContext implements ApplicationContext { protected BeanDefinitionReader beanDefinitionReader; protected Map<String, Object> singletonObjects = new HashMap <String, Object>(); protected String configLocation; @Override public void refresh () throws IllegalStateException, Exception { beanDefinitionReader.loadBeanDefinitions(configLocation); finishBeanInitialization(); } private void finishBeanInitialization () throws Exception{ BeanDefinitionRegistry registry = beanDefinitionReader.getRegistry(); String[] beanNames = registry.getBeanDefinitionNames(); for (String beanName : beanNames) { getBean(beanName); } } }
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 public class ClassPathXmlApplicationContext extends AbstractApplicationContext { public ClassPathXmlApplicationContext (String configLocation) { this .configLocation = configLocation; this .beanDefinitionReader = new XmlBeanDefinitionReader (); try { this .refresh(); } catch (Exception e) { e.printStackTrace(); } } @Override public Object getBean (String name) throws Exception { Object obj = singletonObjects.get(name); if (obj !=null ){ return obj; } BeanDefinitionRegistry registry = beanDefinitionReader.getRegistry(); BeanDefinition beanDefinition = registry.getBeanDefinition(name); String className = beanDefinition.getClassName(); Class<?> clazz = Class.forName(className); Object beanObj = clazz.newInstance(); MutablePropertyValues propertyValues = beanDefinition.getPropertyValues(); for (PropertyValue propertyValue : propertyValues) { String propertyValueName = propertyValue.getName(); String ref = propertyValue.getRef(); if (ref!=null && !"" .equals(ref)){ Object propertyBeanObj = getBean(propertyValueName); String methodName = StringUtils.getSetterMethodNameByFieldName(propertyValueName); Method[] methods = clazz.getMethods(); for (Method method : methods) { if (method.getName().equals(methodName)){ method.invoke(beanObj,propertyBeanObj); } } } String value = propertyValue.getValue(); if (value!=null && !"" .equals(value)){ String methodName = StringUtils.getSetterMethodNameByFieldName(propertyValueName); Method method = clazz.getMethod(methodName, String.class); method.invoke(beanObj,value); } } singletonObjects.put(name,beanObj); return beanObj; } @Override public <T> T getBean (String name, Class<? extends T> clazz) throws Exception { Object bean = getBean(name); if (bean == null ){ return null ; } return clazz.cast(bean); } }
工具包
1 2 3 4 5 6 7 public class StringUtils { public static String getSetterMethodNameByFieldName (String fieldName) { return "set" +fieldName.substring(0 ,1 ).toUpperCase()+fieldName.substring(1 ); } }