好的设计模式能解决代码重用性、提高可读性、可扩展性、可靠性、使程序高内聚低耦合
单一职责原则的定义是:应该有且仅有一个原因引起类的变更。
对类来说:A类有两个方法 a1和a2,当a1变化时有可能引起a2出现问题,单一职责原则是将A的粒度分为A1和A2两个来做、
我们以打电话为例,电话通话的时候有 4 个过程发生:拨号、通话、回应、挂机。那我们写一个接口,类图如下:
代码为:
我们看这个接口有没有问题?相信大部分同学会觉得没问题,因为平常我们就是这么写的。没错,这个接口接近于完美,注意,是“接近”。单一职责原则要求一个接口或一个类只能有一个原因引起变化,也就是一个接口或者类只能有一个职责,它就负责一件事情,看看上面的接口只负责一件事情吗?明显不是。
IPhone这个接口包含了两个职责:协议管理和数据传送。dial 和 hangup 这两个方法实现的是协议管理,分别负责拨号接通和挂机,chat 方法实现的是数据传送。不管是协议接通的变化还是输出传送的变化,都会引起这个接口的变化。所以,IPhone这个接口并不符合单一职责原则。若要让IPhone满足单一职责原则,我们就要对其进行拆分,拆分后的类图如下:
这样设计就完美了,一个类实现了两个接口,把两个职责融合在一个类中。你会觉得这个Phone有两个原因引起变化了啊,是的,但是别忘了我们是面向接口编程,我们对外公布的是接口而不是实现类。
另外,单一职责原则不仅适用于接口和类,也适用于方法。一个方法尽可能只做一件事,比如一个修改用户密码的方法,不要把这个方法放到“修改用户信息”方法中。
单一职责的好处
1. 类的复杂性降低,实现什么职责都有清晰明确的定义;
2. 可读性高,复杂性降低,可读性自然就提高了;
3. 可维护性提高,可读性提高了,那自然更容易维护了;
4. 变更引起的风险降低,变更是必不可少的,如果接口的单一职责做得好,一个接口修改只对相应的实现类有影响,对其他的接口无影响,这对系统的扩展性、维护性都有非常大的帮助。
客户端不应该依赖它不需要的接口。一个类对另一个类的依赖应该建立在最小的接口上。
使用场合,提供调用者需要的方法,屏蔽不需要的方法.满足接口隔离原则.比如说电子商务的系统,有订单这个类,有三个地方会使用到,
一个是用户,只能有查询方法,
一个是外部系统,有添加订单的方法,
一个是管理后台,添加删除修改查询都要用到.
根据接口隔离原则(ISP),一个类对另外一个类的依赖性应当是建立在最小的接口上.
也就是说,对于用户,它只能依赖有一个查询方法的接口.
UML结构如下:
接口隔离原则
下面是实现的代码.
//--这儿不用接口继承,因为可能出现修改了父接口影响了子接口
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 |
|
这样就能很好的满足接口隔离原则了,调用者只能访问它自己的方法,不能访问到不应该访问的方法.
依赖倒置原则在Java语言中的表现是:
1. 模块间的依赖通过抽象发生,实现类之间不直接发生依赖关系,其依赖关系是通过接口或抽象类产生的;
2. 接口或抽象类不依赖于实现类;
3. 实现类依赖接口或抽象类。
说白了,就是“面向接口编程”。
依赖倒置原则可以减少类间的耦合性,提高系统的稳定性,降低并行开发引起的风险,提高代码的可读性和可维护性。
我们以汽车和司机举例,画出类图:
奔驰车源代码:
司机源代码:
客户端源代码:
通过以上的代码,完成了司机开动奔驰车的场景。可以看到,这个场景并没有引用依赖倒置原则,司机Driver类直接依赖奔驰车Benz类,这样会有什么隐患呢?试想,后期业务变动,司机又买了一辆宝马车,源代码如下:
由于司机现在只有开奔驰的方法,所以他是开不了宝马的。一个拿有C驾照的司机能开奔驰,不能开宝马?太不合理了。所以,这就暴露出上面的设计问题了。我们对上面的功能重新设计,首先新建两个接口。
汽车接口ICar:
司机接口IDriver:
IDriver中,通过传入ICar接口实现了抽象之间的依赖关系。
接下来创建汽车实现类:奔驰和宝马。
然后创建司机实现类:
最后是场景类调用:
Client属于高层业务逻辑,它对低层模块的依赖都建立在抽象上,driver的表面类型是IDriver,benz的表面类型是ICar。
依赖倒置原则的使用建议:
(1)每个类尽量都有接口或抽象类,或者接口和抽象类两者都具备。
(2)变量的表面类型尽量是接口或抽象类。
(3)任何类都不应该从具体类派生。
(4)尽量不要重写基类的方法。如果基类是一个抽象类,而且这个方法已经实现了,子类尽量不要重写。
(5)结合里氏替换原则使用。
在面向对象的语言中,继承是必不可少的、非常优秀的语言机制,它有如下优点:
代码共享,减少创建类的工作量,每个子类都拥有父类的属性和方法;提高代码的重用性;子类可以形似父类,但又异于父类;提高代码的可扩展性;提高产品或项目的开放性。有优点就必然存在缺点:
继承是侵入性的。只要继承,就必须拥有父类的属性和方法。降低代码的灵活性。子类会多一些父类的约束。增强了耦合性。当父类的常量、变量、方法被修改时,需要考虑子类的修改。为了让“利”的因素发挥最大的作用,同时减少“弊”带来的麻烦,引入了里氏替换原则(LSP)。
如果对每一个类型为S的对象o1,都有类型为T的对象o2,使得以T定义的所有程序P在所有的对象o1都代替o2时,程序P的行为没有发生变化,那么类型S是类型T的子类型。
通俗点讲,就是只要父类能出现的地方,子类就可以出现,而且替换为子类也不会产生任何错误或异常。
里氏替换原则为良好的继承定义了一个规范,一句简单的定义包含了4层含义。
1. 子类必须完全实现父类的方法。
我们在做系统设计的时候,经常会定义一个接口或抽象类,然后编码实现,调用类则直接传入接口或抽象类,其实这里就已经使用了里氏替换原则。我们以打CS举例,来描述一下里面用到的枪。类图如下:
枪的主要职责是射击,如何射击在各个具体的子类中实现,在士兵类Soldier中定义了一个方法 killEnemy,使用枪来kill敌人,具体用什么枪,调用的时候才知道。
AbstractGun类源码如下:
手枪、步枪、机枪的实现类代码如下:
士兵类的源码为:
注意,士兵类的killEnemy方法中使用的gun是抽象的,具体时间什么枪需要由客户端(Client)调用Soldier的构造方法传参确定。
客户端Client源码如下:
注意:在类中调用其他类时务必要使用父类或接口,如果不能使用父类或接口,则说明类的设计已经违背了LSP原则。
2. 孩子类可以有自己的个性。
孩子类当然可以有自己的属性和方法了,也正因如此,在子类出现的地方,父类未必就可以代替。
还是以上面的关于枪支的例子为例,步枪有 AK47、SKS狙击步枪等型号,把这两个型号的枪引入后的Rifle的子类图如下:
SKS狙击步枪可以配一个8倍镜进行远程瞄准,相对于父类步枪,这就是SKS的个性。源码如下:
狙击手Spinner类的源码如下:
狙击手因为只能使用狙击枪,所以,狙击手类中持有的枪只能是狙击类型的,如果换成父类步枪Rifle,则传递进来的可能就不是狙击枪,而是AK47了,而AK47是没有zoomOut方法的,所以肯定是不行的。这也验证了里氏替换原则的那一句话:有子类出现的地方,父类未必就可以代替。
3. 覆盖或实现父类的方法时,输入参数可以被放大。
来看一个例子,我们先定义一个Father类:
然后定义一个子类:
子类方法与父类方法同名,但又不是覆写父类的方法。你加个@Override看看,会报错的。像这种方法名相同,方法参数不同,叫做方法的重载。你可能会有疑问:重载不是只能在当前类内部重载吗?因为Son继承了Father,Son就有了Father的所有属性和方法,自然就有了Father的doSomething这个方法,所以,这里就构成了重载。
接下来看场景类:
根据里氏替换原则,父类出现的地方子类就可以出现,我们把上面的父类替换为子类:
我们发现运行结果是一样的。为什么会这样呢?因为子类Son继承了Father,就拥有了doSomething(HashMap map)这个方法,不过由于Son没有重写这个方法,当调用Son的这个方法的时候,就会自动调用其父类的这个方法。所以两次的结果是一致的。
举个反例,如果父类的输入参数类型大于子类的输入参数类型,会出现什么问题呢?我们直接看代码执行结果即可轻松看出问题:
扩大父类方法入参:
缩小子类方法入参:
场景类:
根据里氏替换原则,有父类的地方就可以有子类,我们把Father替换为Son看看结果:
两次运行结果不一致,违反了里氏替换原则,所以子类中方法的入参类型必须与父类中被覆写的方法的入参类型相同或更宽松。
4. 覆盖或实现父类的方法时,输出结果可以被缩小。
这句话的意思就是,父类的一个方法的返回值是类型T,子类的相同方法(重载或重写)的返回值为类型S,那么里氏替换原则就要求S必须小于等于T。为什么呢?因为重写父类方法,父类和子类的同名方法的输入参数是相同的,两个方法的范围值S小于等于T,这时重写父类方法的要求。
开闭原则是指一个软件实体如类、模块和函数应该对扩展开放,对修改关闭。也就是说一个软件实体应该通过扩展来实现变化,而不是通过修改已有的代码来实现变化。我们以书店销售书籍为例来说明什么是开闭原则。
其类图如下:
书籍及其实现类代码如下:
书店类代码:
项目开发完了,开始正常卖书了。假如到了双十一,要搞打折活动,上面的功能是不支持的,所以需要修改程序。有三种方法可以解决这个问题:
(1)修改接口
在IBook接口里新增getOffPrice()方法,专门用于进行打折,所有的实现类都实现该方法。但这样修改的后果就是,实现类NovelBook要修改,书店类BookStore中的main方法也要修改,同时,IBook作为接口应该是稳定且可靠的,不应该经常发生变化,因此,该方案被否定。
(2)修改实现类
修改NovelBook类中的方法,直接在getPrice()方法中实现打折处理,这个方法可以是可以,但如果采购书籍的人员要看价格怎么办,由于该方法已经进行了打折处理,因此采购人员看到的也是打折后的价格,会因信息不对称出现决策失误的情况。因此,该方案也不是一个最优的方案。
(3)通过扩展实现变化
增加一个子类OffNovelBook,覆写getPrice方法,高层次的模块(也就是BookStore中static静态块中)通过OffNovelBook类产生新的对象,完成业务变化对系统的最小开发。这样修改也少,风险也小,修改后的类图如下:
OffNovelBook源码如下:
然后修改BookStore中的书籍类为OffNovelBook:
为什么要用开闭原则
1. 开闭原则非常著名,只要是做面向对象编程的,在开发时都会提及开闭原则。
2. 开闭原则是最基础的一个原则,前面介绍的5个原则都是开闭原则的具体形态,而开闭原则才是其精神领袖。
3. 开闭原则提高了复用性,以及可维护性。
迪米特法则(LoD)也叫最少知道法则:一个对象应该对其他对象有最少的了解。
1.只和朋友交流
迪米特法则还有一个英文解释是:Only talk to your immediate friends(只和直接的朋友交流)。每个对象都必然会与其他对象耦合,两个对象的耦合就成为朋友关系。下面我们通过体育课老师让班长清点女生人数为例讲解。
首先设计程序的类图:
编码实现:
程序开发完了,我们首先看下Teacher类有几个朋友类,首先要知道朋友类的定义:出现在成员变量、方法的输入输出参数中的类称为成员朋友类。所以Teacher类只有一个GroupLeader朋友类。根据迪米特法则,一个类只能和朋友类交流,上面的Teacher类内部却与非朋友类Girl发生了交流,这就不符合迪米特法则,破坏了程序的健壮性。
我们对类图做下修改:
修改后的代码:
再看场景类调用:
总之,就是类与类之间的关系是建立在类间的,而不是方法间,因此一个方法尽量不引入一个类中不存在的对象。
2.朋友间也是有距离的
我们在开发中经常有这种场景:调用一个或多个类,先执行第一个方法,然后是第二个方法,根据返回结果再看是否执行第三个方法。我们以安装某个软件为例,其类图为:
代码如下:
程序很简单,但也存在一些问题:Wizard类把太多方法暴露给InstallSoftware类了,两者的朋友关系太亲密了,耦合关系变的异常牢固,如果要把Wizard中first方法的返回值改为Boolean类型,则要同时修改InstallSoftware类,增加了风险。因此,这种耦合是不合适的,我们需要对其优化。重构后的类图如下:
代码如下。导向类:
我们把安装步骤改为私有方法,只向外暴露一个安装方法,这样,即使修改步骤的逻辑,也只是对Wizard自己有影响,只需要修改自己的安装方法逻辑即可,其他类不会受到影响。
安装类:
一个类公开的public属性或方法越多,修改时涉及的面也就越大,变更引起的风险扩散也就越大。所以,我们开发中尽量不要对外公布太多public方法和非静态的public变量,尽量内敛。
3.是自己的就是自己的
在实际开发中经常会出现这样一种情况:一个方法放在吧本类中也可以,放在其他类中也没有错。那这时,我们只需要坚持一个原则:如果一个方法放在本类中,既不增加类间关系,也对本类不产生负面影响,那就放置在本类中。
总之,迪米特法则的核心观念就是类间解耦,弱耦合,只有弱耦合了以后,类的复用率才可以提升上去。
合成复用原则又称为组合/聚合复用原则(Composition/Aggregate Reuse Principle, CARP),其定 义如下:
合成复用原则(Composite Reuse Principle, CRP)
:尽量使用对象组合,而不是继承来达到复 用的目的。
合成复用原则就是在一个新的对象里通过关联关系(包括组合关系和聚合关系)来使用一些 已有的对象,使之成为新对象的一部分;新对象通过委派调用已有对象的方法达到复用功能 的目的。简言之:复用时要尽量使用组合/聚合关系(关联关系),少用继承
。
在面向对象设计中,可以通过两种方法在不同的环境中复用已有的设计和实现,即通过组合/ 聚合关系或通过继承,但首先应该考虑使用组合/聚合,组合/聚合可以使系统更加灵活,降低 类与类之间的耦合度,一个类的变化对其他类造成的影响相对较少;其次才考虑继承,在使 用继承时,需要严格遵循里氏代换原则,有效使用继承会有助于对问题的理解,降低复杂 度,而滥用继承反而会增加系统构建和维护的难度以及系统的复杂度,因此需要慎重使用继 承复用。
通过继承来进行复用的主要问题在于继承复用会破坏系统的封装性,因为继承会将基类的实 现细节暴露给子类,由于基类的内部细节通常对子类来说是可见的,所以这种复用又称“白 箱”复用,如果基类发生改变,那么子类的实现也不得不发生改变;从基类继承而来的实现是 静态的,不可能在运行时发生改变,没有足够的灵活性;而且继承只能在有限的环境中使用 (如类没有声明为不能被继承)。
一般而言,如果两个类之间是“Has-A”的关系应使用组合或聚合,如果是“Is-A”关系可使用继 承。"Is-A"是严格的分类学意义上的定义,意思是一个类是另一个类的"一种";而"Has-A"则不 同,它表示某一个角色具有某一项责任。
下面通过一个简单实例来加深对合成复用原则的理解:
Sunny软件公司开发人员在初期的CRM系统设计中,考虑到客户数量不多,系统采用MySQL 作为数据库,与数据库操作有关的类如CustomerDAO类等都需要连接数据库,连接数据库的 方法getConnection()封装在DBUtil类中,由于需要重用DBUtil类的getConnection()方法,设计人 员将CustomerDAO作为DBUtil类的子类,初始设计方案结构如图1所示:
图1 初始设计方案结构图
随着客户数量的增加,系统决定升级为Oracle数据库,因此需要增加一个新的OracleDBUtil类 来连接Oracle数据库,由于在初始设计方案中CustomerDAO和DBUtil之间是继承关系,因此在 更换数据库连接方式时需要修改CustomerDAO类的源代码,将CustomerDAO作为OracleDBUtil 的子类,这将违反开闭原则。【当然也可以修改DBUtil类的源代码,同样会违反开闭原 则。】
现使用合成复用原则对其进行重构。
根据合成复用原则,我们在实现复用时应该多用关联,少用继承。因此在本实例中我们可以 使用关联复用来取代继承复用,重构后的结构如图2所示:
图2 重构后的结构图
在图2中,CustomerDAO和DBUtil之间的关系由继承关系变为关联关系,采用依赖注入的方式 将DBUtil对象注入到CustomerDAO中,可以使用构造注入,也可以使用Setter注入。如果需要 对DBUtil的功能进行扩展,可以通过其子类来实现,如通过子类OracleDBUtil来连接Oracle数 据库。由于CustomerDAO针对DBUtil编程,根据里氏代换原则,DBUtil子类的对象可以覆盖 DBUtil对象,只需在CustomerDAO中注入子类对象即可使用子类所扩展的方法。例如在 CustomerDAO中注入OracleDBUtil对象,即可实现Oracle数据库连接,原有代码无须进行修改,而且还可以很灵活地增加新的数据库连接方式。
以汽车分类管理程序为例:
分析:汽车按“动力源"划分可分为汽油汽车、电动汽车等;按“颜色”划分可分为白色汽车、黑色汽车和红色汽车等。如果同时考虑这这两种分类,其组合就很多。
package CRP;
public interface Color {
public String color();
}
package CRP;
public class White implements Color {
@Override
public String color() {
return "白色";
}
}
package CRP;
public class Black implements Color {
@Override
public String color() {
return "黑色";
}
}
package CRP;
public class Red implements Color {
@Override
public String color() {
return "红色";
}
}
package CRP;
//汽车
public class Car {
protected Color color;
public Car(Color color){
this.color = color;
}
public void move(){
System.out.println(color.color() + "汽车移动");
}
}
package CRP;
public class GasolineCar extends Car{
protected Color color;
public GasolineCar(Color color) {
super(color);
}
@Override
public void move() {
System.out.println(color.color() + "汽油汽车移动");
}
}
package CRP;
public class ElectricCar extends Car {
public ElectricCar(Color color) {
super(color);
}
@Override
public void move() {
System.out.println(color.color() + "电动汽车移动");
}
}
package CRP;
public class CRPTest {
public static void main(String[] args) {
White white = new White();
Black black = new Black();
Red red = new Red();
Car car1 = new GasolineCar(white);
Car car2 = new GasolineCar(black);
Car car3 = new GasolineCar(red);
Car car4 = new ElectricCar(white);
Car car5 = new ElectricCar(black);
Car car6 = new ElectricCar(red);
car1.move();
car2.move();
car3.move();
car4.move();
car5.move();
car6.move();
}
}
统一建模语言(Unified Modeling Language,UML)是用来设计软件的可视化建模语言。它的特点是简单、统一、图形化、能表达软件设计中的动态与静态信息。
UML 从目标系统的不同角度出发,定义了用例图、类图、对象图、状态图、活动图、时序图、协作图、构件图、部署图等 9 种图。
类图(Class diagram)是显示了模型的静态结构,特别是模型中存在的类、类的内部结构以及它们与其他类的关系等。类图不显示暂时性的信息。类图是面向对象建模的主要组成部分。
在软件工程中,类图是一种静态的结构图,描述了系统的类的集合,类的属性和类之间的关系,可以简化了人们对系统的理解;
类图是系统分析和设计阶段的重要产物,是系统编码和测试的重要模型。
在UML类图中,类使用包含类名、属性(field) 和方法(method) 且带有分割线的矩形来表示,比如下图表示一个Employee类,它包含name,age和address这3个属性,以及work()方法。
属性/方法名称前加的加号和减号表示了这个属性/方法的可见性,UML类图中表示可见性的符号有三种:
+:表示public
-:表示private
:表示protected
属性的完整表示方式是: 可见性 名称 :类型 [ = 缺省值]
方法的完整表示方式是: 可见性 名称(参数列表) [ : 返回类型]
注意:
1,中括号中的内容表示是可选的
2,也有将类型放在变量名前面,返回值类型放在方法名前面
上图Demo类定义了三个方法:
method()方法:修饰符为public,没有参数,没有返回值。
method1()方法:修饰符为private,没有参数,返回值类型为String。
method2()方法:修饰符为protected,接收两个参数,第一个参数类型为int,第二个参数类型为String,返回值类型是int。
关联关系
关联关系是对象之间的一种引用关系,用于表示一类对象与另一类对象之间的联系,如老师和学生、师傅和徒弟、丈夫和妻子等。关联关系是类与类之间最常用的一种关系,分为一般关联关系、聚合关系和组合关系。我们先介绍一般关联。
关联又可以分为单向关联,双向关联,自关联。
在UML类图中单向关联用一个带箭头的实线表示。上图表示每个顾客都有一个地址,这通过让Customer类持有一个类型为Address的成员变量类实现。
2.双向关联
从上图中我们很容易看出,所谓的双向关联就是双方各自持有对方类型的成员变量。
在UML类图中,双向关联用一个不带箭头的直线表示。上图中在Customer类中维护一个List
自关联在UML类图中用一个带有箭头且指向自身的线表示。上图的意思就是Node类包含类型为Node的成员变量,也就是“自己包含自己”。
聚合关系是关联关系的一种,是强关联关系,是整体和部分之间的关系。
聚合关系也是通过成员对象来实现的,其中成员对象是整体对象的一部分,但是成员对象可以脱离整体对象而独立存在。例如,学校与老师的关系,学校包含老师,但如果学校停办了,老师依然存在。
在 UML 类图中,聚合关系可以用带空心菱形的实线来表示,菱形指向整体。下图所示是大学和教师的关系图:
组合表示类之间的整体与部分的关系,但它是一种更强烈的聚合关系。
在组合关系中,整体对象可以控制部分对象的生命周期,一旦整体对象不存在,部分对象也将不存在,部分对象不能脱离整体对象而存在。例如,头和嘴的关系,没有了头,嘴也就不存在了。
在 UML 类图中,组合关系用带实心菱形的实线来表示,菱形指向整体。下图所示是头和嘴的关系图:
依赖关系是一种使用关系,它是对象之间耦合度最弱的一种关联方式,是临时性的关联。在代码中,某个类的方法通过局部变量、方法的参数或者对静态方法的调用来访问另一个类(被依赖类)中的某些方法来完成一些职责。
在 UML 类图中,依赖关系使用带箭头的虚线来表示,箭头从使用类指向被依赖的类。下图所示是司机和汽车的关系图,司机驾驶汽车:
继承关系是对象之间耦合度最大的一种关系,表示一般与特殊的关系,是父类与子类之间的关系,是一种继承关系。
在 UML 类图中,泛化关系用带空心三角箭头的实线来表示,箭头从子类指向父类。在代码实现时,使用面向对象的继承机制来实现泛化关系。例如,Student 类和 Teacher 类都是 Person 类的子类,其类图如下图所示:
实现关系是接口与实现类之间的关系。在这种关系中,类实现了接口,类中的操作实现了接口中所声明的所有的抽象操作。
在 UML 类图中,实现关系使用带空心三角箭头的虚线来表示,箭头从实现类指向接口。例如,汽车和船实现了交通工具,其类图如图 9 所示。
工厂模式可以细分为:简单工厂、工厂方法和抽象工厂三种模式
对单例模式不做介绍
工厂模式可以细分为:简单工厂、工厂方法和抽象工厂三种模式。
使用场景:
1:单个对象的创建过程比较复杂,如需要做复杂初始化操作的对象
2:需要根据不同的类型创建不同的对象
针对细分的三种模式,使用场景又可以区分:
1、当对象的创建逻辑简单,通常只需要new一下就可以,此时可以考虑简单工厂模式
2、当对象的创建逻辑很复杂,需要做各种初始化操作,此时可以考虑使用工厂方法模式,将对象创建的复杂逻辑拆分到各个工厂类中,让每个工厂类都不至于过于复杂
3、当系统中有多于一个产品族,而每次只使用其中某一产品族,此时使用抽象工厂模式
示例:
ProcuctA和ProductB继承Product抽象类,ProductFactory根据传入的type来返回不同的Product实例
Product 抽象类:
public abstract class Product {
public abstract void use();
}
ProductA类:
public class ProductA extends Product {
@Override
public void use() {
System.out.println("You are using ProductA...");
}
}
ProductB类;
public class ProductB extends Product {
@Override
public void use() {
System.out.println("You are using ProductB...");
}
}
ProductFactory:
public class ProductFactory {
public Product createProduct(String type) {
Product product = null;
if ("A".equals(type)) {
product = new ProductA();
} else if ("B".equals(type)) {
product = new ProductB();
}
return product;
}
}
调用:
public class Main {
public static void main(String[] args) {
ProductFactory factory = new ProductFactory();
Product product;
product = factory.createProduct("A");
product.use();
product = factory.createProduct("B");
product.use();
}
}
缺点、当频繁的新增不同产品时,需要频繁的修改ProductFactory中的if/else逻辑
工具及jdk中的应用:
java.text.DateFormat#getDateInstance()
java.text.DateFormat#getDateInstance(int)
java.text.DateFormat#getDateInstance(int, java.util.Locale)
KeyGenerator keyGen=KeyGenerator.getInstance("DESede");
示例:
相比简单工厂,这种方式将ProductFactory定义为抽象类,然后创建不同的具体ProductAFactory和ProductBFactory,在使用时决定创建那种工厂和对象,避免了if/else判断
Product
public abstract class Product {
public abstract void use();
}
ProductA
public class ProductA extends Product {
@Override
public void use() {
System.out.println("You are using ProductA...");
}
}
ProductB
public class ProductB extends Product {
@Override
public void use() {
System.out.println("You are using ProductB...");
}
}
ProductFactory
public abstract class ProductFactory {
public abstract Product createProduct();
}
ProductAFactory
public class ProductAFactory extends ProductFactory {
@Override
public Product createProduct() {
return new ProductA();
}
}
ProductBFactory
public class ProductBFactory extends ProductFactory {
@Override
public Product createProduct() {
return new ProductB();
}
}
调用
public class Main {
public static void main(String[] args) {
ProductFactory factory;
Product product;
factory = new ProductAFactory();
product = factory.createProduct();
product.use();
factory = new ProductBFactory();
product = factory.createProduct();
product.use();
}
}
抽象工厂模式在工厂方法模式的基础上进行进一步抽象。设想下面这种场景:
现有两种具体产品(具体产品):篮球,足球(在此基础上的抽象产品可看成球)。同时,对于足球和篮球来说,他们都有两种品牌安踏、李宁。
如果采用工厂方法模式解决上述场景中创建产品的问题,需要在抽象工厂中定义创建产品的方法,并新建四个用于创建具体产品的具体工厂类:用于创建“李宁篮球”的具体工厂,用于创建“李宁足球”的具体工厂,用于创建“安踏篮球”的具体工厂,用于创建“安踏足球”的具体工厂。
从上面的解决方式可以看出:使用工厂方法模式,创建了很多具体工厂类,而没有利用产品的“商品族”的概念。
由此引出,抽象工厂模式是用于解决“一类产品”的创建问题(在上述场景中,可以把“李宁篮球”,“李宁足球”,“安踏篮球”,“安踏足球”归纳为:“篮球”,“足球”这两类商品)
抽象工厂模式的角色
抽象工厂:
/**
* 抽象工厂
*/
public interface AbstractFactory {
Basketball makeBasketball();
Football makeFootball();
}
抽象产品族篮球:
/**
* 抽象产品族;篮球
*/
public interface Basketball {
void sayBasketball();
}
抽象产品族足球:
/**
* 抽象产品族:足球
*/
public interface Football {
void sayFootball();
}
四个具体产品:李宁篮球、李宁足球、安踏篮球、安踏足球
/**
* 具体产品:李宁篮球
*/
@Slf4j
public class LiningBasketball implements Basketball {
@Override
public void sayBasketball() {
log.info("我是李宁篮球");
}
}
/**
* 具体产品:李宁足球
*/
@Slf4j
public class LiningFootball implements Football {
@Override
public void sayFootball() {
log.info("我是李宁足球");
}
}
/**
* 具体产品:安踏篮球
*/
@Slf4j
public class AntaBasketball implements Basketball {
@Override
public void sayBasketball() {
log.info("我是安踏篮球");
}
}
/**
* 具体产品:安踏足球
*/
@Slf4j
public class AntaFootball implements Football {
@Override
public void sayFootball() {
log.info("我是安踏足球");
}
}
具体工厂:
/**
* 具体工厂,负责生产李宁篮球,李宁足球
*/
public class LiningFactoy implements AbstractFactory {
@Override
public Basketball makeBasketball() {
return new LiningBasketball();
}
@Override
public Football makeFootball() {
return new LiningFootball();
}
}
/**
* 具体工厂,负责生产安踏篮球,安踏足球
*/
public class AntaFactory implements AbstractFactory {
@Override
public Basketball makeBasketball() {
return new AntaBasketball();
}
@Override
public Football makeFootball() {
return new AntaFootball();
}
}
调用
public class Client {
public static void main(String[] args){
// 生产李宁篮球和安踏足球
LiningFactoy liningFactoy = new LiningFactoy();
AntaFactory antaFactory = new AntaFactory();
liningFactoy.makeBasketball().sayBasketball();
antaFactory.makeFootball().sayFootball();
}
}
参照文档:工厂模式 | 菜鸟教程
原型模式 | 菜鸟教程
违背ocp原则,应用度也不是很高,不做详细介绍,参照原型模式熟悉上手
建造者模式(Builder Pattern)使用多个简单的对象一步一步构建成一个复杂的对象。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。一个 Builder 类会一步一步构造最终的对象。该 Builder 类是独立于其他对象的。
可以简单的理解为两层含义。
1、构建与表示分离:构建代表对象创建,表示代表对象行为,方法。也就是将对象的创建与行为进行分离(对应到java代码,其实就是使用接口规定行为,然后由具体的实体类进行构建)
2、创建不同表示:也就是具备同样的行为,但是却由于构建的行为顺序不同或其他原因可以创建出不同的表示
从定义来看,建造者模式和工厂模式是非常相似的,和工厂模式一样,具备创建与表示分离的特性。建造者模式唯一区别与工厂模式的是针对复杂对象的创建,也就是说,如果创建简单对象,通常使用工厂模式进行创建,而如果复杂的对象,就可以考虑使用建造者模式。
当需要创建产品具备复杂创建过程时,可以抽取出共性创建过程,然后交由具体实现类自定义创建流程,使得同样的创建行为可以生产出不同的产品,分离了创建与表示,是创建产品的灵活性大大增加,建造者模式主要用于以下场景
1、相同的方法,不同的执行顺序,产生不同的结果
2、多个部件或零件,都可以装配到一个对象中,但是产生的结果有不相同
3、产品类非常复杂,或者产品类中不同的调用顺序产生不同的作用
4、初始化一个对象特别复杂,参数多,而且很多参数都具有默认值
1.Product(产品角色): 一个具体的产品对象。
2.Builder(抽象建造者): 创建一个Product对象的各个部件指定的 接口/抽象类。
3.ConcreteBuilder(具体建造者): 实现接口,构建和装配各个部件。
4.Director(指挥者): 构建一个使用Builder接口的对象。它主要是用于创建一个复杂的对象。它主要有两个作用,一是:隔离了客户与对象的生产过程,二是:负责控制产品对象的生产过程。
示例:
定义产品:
//产品->Product
public class House {
//地基
private String baise;
//砌墙
private String wall;
//封顶
private String roofed;
public String getBaise() {
return baise;
}
public void setBaise(String baise) {
this.baise = baise;
}
public String getWall() {
return wall;
}
public void setWall(String wall) {
this.wall = wall;
}
public String getRoofed() {
return roofed;
}
public void setRoofed(String roofed) {
this.roofed = roofed;
}
}
抽象建造者
// 抽象的建造者
public abstract class HouseBuilder {
protected House house = new House();
//将建造的流程写好, 抽象的方法
public abstract void buildBasic();
public abstract void buildWalls();
public abstract void roofed();
//建造房子好, 将产品(房子) 返回
public House buildHouse() {
return house;
}
}
具体建造者:
//具体建造者
public class HighBuilding extends HouseBuilder {
@Override
public void buildBasic() {
// TODO Auto-generated method stub
System.out.println(" 高楼的打地基100米 ");
}
@Override
public void buildWalls() {
// TODO Auto-generated method stub
System.out.println(" 高楼的砌墙20cm ");
}
@Override
public void roofed() {
// TODO Auto-generated method stub
System.out.println(" 高楼的透明屋顶 ");
}
}
//具体建造者
public class CommonHouse extends HouseBuilder {
@Override
public void buildBasic() {
// TODO Auto-generated method stub
System.out.println(" 普通房子打地基5米 ");
}
@Override
public void buildWalls() {
// TODO Auto-generated method stub
System.out.println(" 普通房子砌墙10cm ");
}
@Override
public void roofed() {
// TODO Auto-generated method stub
System.out.println(" 普通房子屋顶 ");
}
}
指挥者:
//指挥者,这里去指定制作流程,返回产品(指挥者就是分离产品和生厂对象的关系交给指挥者去处理)
public class HouseDirector {
HouseBuilder houseBuilder = null;
//构造器传入 houseBuilder
public HouseDirector(HouseBuilder houseBuilder) {
this.houseBuilder = houseBuilder;
}
//通过setter 传入 houseBuilder
public void setHouseBuilder(HouseBuilder houseBuilder) {
this.houseBuilder = houseBuilder;
}
//如何处理建造房子的流程,交给指挥者
public House constructHouse() {
houseBuilder.buildBasic();
houseBuilder.buildWalls();
houseBuilder.roofed();
return houseBuilder.buildHouse();
}
}
调用示例:
public static void main(String[] args) {
//盖普通房子
CommonHouse commonHouse = new CommonHouse();
//准备创建房子的指挥者
HouseDirector houseDirector = new HouseDirector(commonHouse);
//完成盖房子,返回产品(普通房子)
House house = houseDirector.constructHouse();
//盖高楼
HighBuilding highBuilding = new HighBuilding();
//重置建造者
houseDirector.setHouseBuilder(highBuilding);
//完成盖房子,返回产品(高楼)
houseDirector.constructHouse();
}
实际开发中常见的用法
lombok插件中的@Builder注解
适配器模式(Adapter Pattern):结构型模式之一,将一个类的接口转换成客户希望的另一个接口。Adapter模式使得原本由于接口不兼容而不能一起工作的哪些类可以一起工作。
角色职责
1、目标角色(Target):该角色定义把其他类转换为何种接口,也就是我们的期望接口。
2、源角色(Adaptee):你想把谁转换成目标角色,这个“谁”就是源角色,它是已经存在的、运行良好的类或对象。
3、适配器角色(Adapter):适配器模式的核心角色,其他两个角色都是已经存在的角色,而适配器角色是需要新建立的,它的职责非常简单:通过继承或是类关联的方式把源角色转换为目标角色。
代码实现
前言:举个栗子,我今天买了机票,飞到香港迪士尼去游玩,晚上回到了酒店,想给我的笔记本电脑充电,但这时我发现,香港的插座是英式三角插座,我的充电器插不进去。这时我们就可以使用适配器模式,进行适配。
类适配器:适配器通过类来实现,以类来继承和实现接口的方式,来获取被适配类的信息并转换输出重写到适配接口。
中式插座(源角色 Adaptee)
@AllArgsConstructor
@Data
public class ChineseStandard {
public String getChineseStandard() {
return "中式插座";
}
}
英式插座(目标角色 Target)
public interface BritishStandard {
String getBritishStandard();
}
插座适配器(适配器角色 Adapter)
public class StandardAdapter extends ChineseStandard implements BritishStandard {
@Override
public String getBritishStandard() {
return this.getChineseStandard();
}
}
笔记本电脑(客户端 Client)
public class Notebook {
public void charge(BritishStandard britishStandard) {
if ("中式插座".equals(britishStandard.getBritishStandard())) {
System.out.println("充电成功!");
} else {
System.out.println("充电失败!");
}
}
}
测试类
public class AdapterTest {
public static void main(String[] args) {
// 充电成功!
new Notebook().charge(new StandardAdapter());
}
}
对象适配器:通过实例对象(构造器传递)来实现适配器,而不是再用继承,其余基本同类适配器。
我么们将插座适配器就行修改即可
@AllArgsConstructor
public class StandardAdapter implements BritishStandard {
private ChineseStandard chineseStandard;
@Override
public String getBritishStandard() {
return chineseStandard.getChineseStandard();
}
}
测试类
public class AdapterTest {
public static void main(String[] args) {
// 充电成功!
new Notebook().charge(new StandardAdapter(new ChineseStandard()));
}
}
接口适配器:当不需要全部实现接口提供的方法时,可先设计一个抽象类实现接口,并为该接口中每个方法提供一个默认实现(空方法),那么该抽象类的子类可有选择地覆盖父类的某些方法来实现需求。
英式插座(目标角色 Target)
public interface BritishStandard {
String getBritishStandard();
String getTypeC();
String getUSB();
}
插座适配器(适配器角色 Adapter)
@AllArgsConstructor
public abstract class StandardAdapter extends ChineseStandard implements BritishStandard {
@Override
public String getBritishStandard() {
return null;
}
@Override
public String getTypeC() {
return null;
}
@Override
public String getUSB() {
return null;
}
}
测试类
public class AdapterTest {
public static void main(String[] args) {
StandardAdapter standardAdapter= new StandardAdapter() {
@Override
public String getBritishStandard() {
return new ChineseStandard().getChineseStandard();
}
};
// 充电成功!
new Notebook().charge(standardAdapter);
}
}
桥接(Bridge)模式属于结构型设计模式。通过提供抽象化和实现化之间的桥接结构,来实现二者的解耦。把抽象(abstraction)与行为实现(implementation)分离开来,从而可以保持各部分的独立性以及应对它们的功能扩展
主要角色:
(1)Abstraction:抽象类。
(2)RefinedAbstraction:扩充抽象类。
(3)Implementor:实现类接口。
(4)ConcreteImplementor:具体实现类 。
代码示例
场景: 一个甜品店,售卖的食品有蛋糕、奶茶和牛奶,但是每个商品都有各种口味:草莓味、芒果味、香蕉味。
那如果这时候食品又新增了拿铁,口味又新增了青提,难道要把商品和口味再全部绑定一套吗?
显然这种两个维度扩展的情况不适合这种方案。那我们另辟蹊径,创建两个父类,一个食品类一个水果类。两个父类各自包含了相应的子类,然后根据需要将食品和水果进行组合。如下:
创建食品类,该类为抽象类,主要提供添加add的方法;
public abstract class Food {
//水果
Fruit fruit;
public void setFruit(Fruit fruit) {
this.fruit = fruit;
}
/**
* 制作时添加
*/
public abstract void add();
}
食品类的子类一:蛋糕
public class Cake extends Food {
@Override
public void add() {
fruit.beAdd("蛋糕");
}
}
食品类的子类二:牛奶
public class Milk extends Food {
@Override
public void add() {
fruit.beAdd("牛奶");
}
}
食品类的子类三:奶茶
public class MilkTea extends Food {
@Override
public void add() {
fruit.beAdd("奶茶");
}
}
创建水果接口类
public interface Fruit {
/**
* 被添加
* @param food
*/
public void beAdd(String food);
}
水果接口实现类一:香蕉
public class Banana implements Fruit {
@Override
public void beAdd(String food) {
System.out.println("香蕉" + food);
}
}
水果接口实现类二:草莓
public class Strawberry implements Fruit {
@Override
public void beAdd(String food) {
System.out.println("草莓" + food);
}
}
水果接口实现类三:芒果
public class Mango implements Fruit {
@Override
public void beAdd(String food) {
System.out.println("芒果" + food);
}
}
主方法测试
public class TestMain {
public static void main(String[] args) {
//蛋糕
Fruit mango = new Mango();
Food cake = new Cake();
cake.setFruit(mango);
cake.add();
//牛奶
Food milk = new Milk();
milk.setFruit(mango);
milk.add();
}
}
装饰者模式(Decorator Pattern)是指在不改变原有对象的基础上,将功能附加到对象上,提供了比继承更有弹性的替代方案(扩展原有对象的功能,它通过创建一个包装对象,也就是装饰来包裹真实的对象)。
**(1).抽象构件(Component):**通常是一个抽象类或者一个接口,定义了一系列方法,方法的实现可以由子类实现或者自己实现。通常不会直接使用该类,而是通过继承或者实现接口来实现特定的功能。
**(2).具体构件(Concrete Component):**是抽象构件(Component)的子类,实现了对应的方法,它就是那个被装饰的类。
**(3).抽象装饰者(Decorator):**是Component的子类,它是具体装饰者角色共同实现的类(可以是接口、抽象类、类),并且持有一个Comonent类型的对象引用,它的主要作用就是把客户端的调用委派到被装饰类。
**(4).具体装饰角色(Concrete Component):**它是具体的装饰类,是Decorator的子类,当然也是Component的子类。它主要就是定义具体的装饰功能。
(1).创建一个抽象构件接口,定义一些方法。
(2).创建具体构件实现抽象构件,重写抽象构件中的方法(实现具体功能),这个具体构件就是基础构件了,也就是被装饰的类。
(3).创建抽象装饰者实现抽象构件,重写抽象构件中的方法,组合依赖一个抽象构件类型对象。
(4).创建具体构件继承(或实现)抽象装饰者,重写抽象装饰者中的方法,在重写的方法中调用组合的抽象构件类型角色的方法,然后再加上需要修饰的方法。
(1).场景
比如我们做一个蛋糕,里面可以加一些水果啊,巧克力啥的。
(2).具体代码
/**
* 抽象构件
*/
public interface Cake {
String getCakeDesc();
BigDecimal getPrice();
}
/**
* 具体构件(基础构件)
*/
public class CommonCake implements Cake{
@Override
public String getCakeDesc() {
return "这是一个普通蛋糕!";
}
@Override
public BigDecimal getPrice() {
return new BigDecimal("100");
}
}
/**
* 抽象装饰者
*/
public class Decorator implements Cake{
private Cake cake;
public Decorator(Cake cake){
this.cake = cake;
}
@Override
public String getCakeDesc() {
return this.cake.getCakeDesc();
}
@Override
public BigDecimal getPrice() {
return this.cake.getPrice();
}
}
/**
* 具体装饰者
*/
public class MangoCakeDecorator extends Decorator{
public MangoCakeDecorator(Cake cake) {
super(cake);
}
@Override
public String getCakeDesc(){
return super.getCakeDesc() + "加一个芒果!";
}
@Override
public BigDecimal getPrice(){
return super.getPrice().add(new BigDecimal("25"));
}
}
/**
* 具体装饰者
*/
public class GrapesCakeDecorator extends Decorator{
public GrapesCakeDecorator(Cake cake) {
super(cake);
}
@Override
public String getCakeDesc(){
return super.getCakeDesc() + "加一个葡萄!";
}
@Override
public BigDecimal getPrice(){
return super.getPrice().add(new BigDecimal("30"));
}
}
public class DecoratorTest {
public static void main(String[] args) {
Cake cake = new CommonCake();
cake = new Decorator(cake);
System.out.println(cake.getCakeDesc() + " 价格: " + cake.getPrice());
cake = new MangoCakeDecorator(cake);
System.out.println(cake.getCakeDesc() + " 价格: " + cake.getPrice());
cake = new MangoCakeDecorator(cake);
System.out.println(cake.getCakeDesc() + " 价格: " + cake.getPrice());
cake = new GrapesCakeDecorator(cake);
System.out.println(cake.getCakeDesc() + " 价格: " + cake.getPrice());
}
}
这是一个普通蛋糕! 价格: 100
这是一个普通蛋糕!加一个芒果! 价格: 125
这是一个普通蛋糕!加一个芒果!加一个芒果! 价格: 150
这是一个普通蛋糕!加一个芒果!加一个芒果!加一个葡萄! 价格: 180
示例2:
//创建一个Person接口
public interface Person {
void eat();
}
//声明Man类去实现Person接口,并且重写eat方法
public class Man implements Person{
@Override
public void eat() {
System.out.println("男人在吃饭");
}
}
//Decorator(这个类是关键,实现了接口并且有真实对象的引用)
//声明一个Decorator抽象类去实现Person接口
public abstract class Decorator implements Person {
protected Person person;
public Decorator(Person person) {
this.person = person;
}
@Override
public void eat() {
person.eat();
}
}
//声明ManDecoratorA类继承Decorator类
//下面的就是具体的添加额外功能的了具体子类,比如有些人吃饭前先洗手或者吃完饭后洗碗之类,当然具体什么事情根据业务决定
public class ManDecoratorA extends Decorator{
public ManDecoratorA(Person person) {
super(person);
}
@Override
public void eat() {
wash();
super.eat();
}
private void wash() {
System.out.println("吃饭前要洗手");
}
}
//声明ManDecoratorB类继承Decorator类
//下面的就是具体的添加额外功能的了具体子类,比如有些人吃饭前先洗手或者吃完饭后洗碗之类,当然具体什么事情根据业务决定
public class ManDecoratorB extends Decorator{
public ManDecoratorB (Person person){
super(person);
}
@Override
public void eat() {
super.eat();
washDishes();
}
private void washDishes() {
System.out.println("吃饭后洗要碗");
}
}
//测试函数
public class Test {
public static void main(String[] args) {
Person person = new Man();
person.eat();
System.out.println("-------");
ManDecoratorA man1 = new ManDecoratorA(person);
ManDecoratorB man2 = new ManDecoratorB(person);
man1.eat();
System.out.println("------");
man2.eat();
}
}
输出:
男人在吃饭
-------
吃饭前要洗手
男人在吃饭
------
男人在吃饭
吃饭后洗要碗
进程已结束,退出代码0
定义
组合模式(Composite Pattern)属于结构型模式,将对象组合成树形结构以表示“部分”—“整体”的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。
什么叫“部分”—“整体”的层次结构呢,举个例子:
假如我们在网上购物买了很多的东西,仓库收到订单后按照订单明细给我们的东西打包,由于东西很多他们用了很多的包装盒,并最终用一个巨大的盒子来包裹每一个订单的商品,如下图所示:
每一个盒子里面都可能包含许多的小盒子和商品,以此类推。因此“部分”—“整体”是一个相对的概念,假如在第三层的包装盒内部还有许多的小盒子和商品,那么这个第三层的包装盒对于整个订单的包裹来说他是“部分”,而对于他里面的盒子与商品来说,他又是“整体”。
再比如我们电脑中的文件夹里面,A文件夹里面包含了B、C、D文件夹以及一个demo.txt文件,B文件夹里面又包括了E、F文件夹和demo.ps和demo.word文件。这也是“部分”—“整体”的层次结构。
解析:
component :一个接口或抽象类,描述了树中简单项目和复杂项目所共有的操作。
leaf:叶节点是树的基本结构, 它不包含子项目。一般情况下, 叶节点最终会完成大部分的实际工作, 因为它们无法将工作指派给其他部分。
composite:是包含叶节点或其他容器等子项目的单位。 容器接收到请求后会将工作分配给自己的子项目, 处理中间结果, 然后将最终结果返回给客户端。
现在,假设我们要建立一个公司的销售部门层次结构。我们先把这个层次结构画一下:
代码:
我们先建一个部门的接口Department:
public interface Department {
void printDepartmentName(); //输出部门名称的方法
}
然后建立一个总的销售部门:SaleDepartment
public class SaleDepartment implements Department {
//部门名称
private String name;
//相当于一个Composite,下面可以拥有其他的子部门,而不是一个叶子节点
List departments = new ArrayList<>();
//用于增加子部门
public void addDepartment(Department department){
departments.add(department);
}
//删除子部门
public void remove(Department department){
departments.remove(department);
}
@Override
public void printDepartmentName() {
//打印本部门的名称
System.out.println(name);
//打印本部门下的子部门的名称
for (Department department:departments){
department.printDepartmentName();
}
}
public SaleDepartment(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
创建一个国内销售部门:DomesticSaleDepartment
public class DomesticSaleDepartment implements Department {
private String name;
//相当于一个Composite,下面可以拥有其他各个省份的子部门
List list = new ArrayList<>();
public DomesticSaleDepartment(String name) {
this.name = name;
}
//用于增加子部门
public void add(Department department){
list.add(department);
}
//用于删除子部门
public void remove(Department department){
list.remove(department);
}
@Override
public void printDepartmentName() {
System.out.println(name);
for (Department department:list){
department.printDepartmentName();
}
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
建立一个国际销售部门:InternationalSaleDepartment ,我们假设国外业务不多,就不细分了,作为一个叶子节点
public class InternationalSaleDepartment implements Department {
private String name;
public InternationalSaleDepartment(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public void printDepartmentName() {
System.out.println(name);
}
}
在建立一个浙江销售部门:ZheJiangSaleDepartment ,同样作为叶子节点
public class ZheJiangSaleDepartment implements Department {
private String name;
public ZheJiangSaleDepartment(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public void printDepartmentName() {
System.out.println(name);
}
}
测试类:CompositeDemo
public class CompositeDemo {
public static void main(String[] args) {
//创建一个总的销售部门作为根节点
Department saleDepartment = new SaleDepartment("销售部门");
//创建国内销售部门
DomesticSaleDepartment domesticSaleDepartment = new DomesticSaleDepartment("国内销售部门");
//创建国内浙江销售部门
ZheJiangSaleDepartment ZheJiangSaleDepartment = new ZheJiangSaleDepartment("浙江销售部门");
//将浙江销售部门加入到国内销售部门
domesticSaleDepartment.list.add(ZheJiangSaleDepartment);
//创建国际销售部门
InternationalSaleDepartment internationalSaleDepartment = new InternationalSaleDepartment("国际销售部门");
//将国内销售部门和国外销售部门全部加入到总的销售部门去
((SaleDepartment) saleDepartment).departments.add(domesticSaleDepartment);
((SaleDepartment) saleDepartment).departments.add(internationalSaleDepartment);
//打印部门名称
saleDepartment.printDepartmentName();
}
}
打印输出:
销售部门
国内销售部门
浙江销售部门
国际销售部门
使用场合
概述
外观(Facade)模式又叫作门面模式,是一种通过为多个复杂的子系统提供一个一致的接口,而使这些子系统更加容易被访问的模式。该模式对外有一个统一接口,外部应用程序不用关心内部子系统的具体细节,这样会大大降低应用程序的复杂度,提高了程序的可维护性。
外观模式是为了解决类与类之家的依赖关系的,像spring一样,可以将类和类之间的关系配置到配置文件中,而外观模式就是将他们的关系放在一个Facade类中,降低了类类之间的耦合度,该模式中没有涉及到接口,看下类图:(我们以一个计算机的启动过程为例)
外观模式的特点
外观(Facade)模式是“迪米特法则”的典型应用,它有以下主要优点:
降低了子系统与客户端之间的耦合度,使得子系统的变化不会影响调用它的客户类。
对客户屏蔽了子系统组件,减少了客户处理的对象数目,并使得子系统使用起来更加容易。
降低了大型软件系统中的编译依赖性,简化了系统在不同平台之间的移植过程,因为编译一个子系统不会影响其他的子系统,也不会影响外观对象。
外观(Facade)模式的主要缺点如下:
不能很好地限制客户使用子系统类,很容易带来未知风险。
增加新的子系统可能需要修改外观类或客户端的源代码,违背了“开闭原则”。
外观模式的应用场景
通常在以下情况下可以考虑使用外观模式。
对分层结构系统构建时,使用外观模式定义子系统中每层的入口点可以简化子系统之间的依赖关系。
当一个复杂系统的子系统很多时,外观模式可以为系统设计一个简单的接口供外界访问。
当客户端与多个子系统之间存在很大的联系时,引入外观模式可将它们分离,从而提高子系统的独立性和可移植性。
外观模式的结构与实现
外观(Facade)模式的结构比较简单,主要是定义了一个高层接口。
它包含了对各个子系统的引用,客户端可以通过它访问各个子系统的功能。现在来分析其基本结构和实现方法。
模式的结构
外观(Facade)模式包含以下主要角色。
1、外观(Facade)角色:为多个子系统对外提供一个共同的接口。
2、子系统(Sub System)角色:实现系统的部分功能,客户可以通过外观角色访问它。
3、客户(Client)角色:通过一个外观角色访问各个子系统的功能。
实现
public class CPU {
public void startup(){
System.out.println("cpu startup!");
}
public void shutdown(){
System.out.println("cpu shutdown!");
}
}
public class Memory {
public void startup(){
System.out.println("memory startup!");
}
public void shutdown(){
System.out.println("memory shutdown!");
}
}
public class Disk {
public void startup(){
System.out.println("disk startup!");
}
public void shutdown(){
System.out.println("disk shutdown!");
}
}
public class Computer {
private CPU cpu;
private Memory memory;
private Disk disk;
public Computer(){
cpu = new CPU();
memory = new Memory();
disk = new Disk();
}
public void startup(){
System.out.println("start the computer!");
cpu.startup();
memory.startup();
disk.startup();
System.out.println("start computer finished!");
}
public void shutdown(){
System.out.println("begin to close the computer!");
cpu.shutdown();
memory.shutdown();
disk.shutdown();
System.out.println("computer closed!");
}
}
//测试
public class User {
public static void main(String[] args) {
Computer computer = new Computer();
computer.startup();
computer.shutdown();
}
}
享元模式
享元模式,运用共享技术来有效地支持大量细粒度对象的复用。它通过共享已经存在的对象来大幅度减少需要创建的对象数量、避免大量相似类的开销,从而提高系统资源的利用率。
享元模式的优缺点
优点:
可以极大减少内存中对象的数量,使得相同或相似对象在内存中只保存一份,从而可以节约系统资源,提高系统性能。
享元模式的外部状态相对独立,而且不会影响其内部状态,从而使得享元对象可以在不同的环境中被共享。
缺点:
享元模式使得系统变得复杂,需要分离出内部状态和外部状态,这使得程序的逻辑复杂化。
为了使对象可以共享,享元模式需要将享元对象的部分状态外部化,而读取外部状态将使得运行时间变长。
享元模式的结构
1、FlyWeight:抽象的享元角色,产品的抽象类,定义了对象的外部状态和内部状态的接口或实现
2、ConcreteFlyWeight:具体的享元角色,产品的实现类,实现了抽象角色定义的相关业务
3、FlyWeightFactory:享元工厂类,内部提供一个池容器,储存ConcreteFlyWeight,同时提供从池中存取数据的操作
图书享元类(FlyWeight:抽象的享元角色)
public interface Book {
void read();
}
具体图书(ConcreteFlyWeight:具体的享元角色)
public class FairyBook implements Book {
private String bookName;
public FairyBook(String bookName) {
this.bookName = bookName;
}
public void read() {
System.out.println(bookName + hashCode());//用hashCode区分实例
}
}
图书享元工厂(FlyWeightFactory:享元工厂类)
public class BookFactory {
private Map bookMap = new HashMap();
public FairyBook getFairyBook(String bookName) {
Book book = null;
//根据书名来找书,如果有就直接取,没有就新建
if (bookMap.containsKey(bookName)) {
book = bookMap.get(bookName);
} else {
book = new FairyBook(bookName);
bookMap.put(bookName, book);
}
return (FairyBook) book;
}
}
客户端
BookFactory bookFactory = new BookFactory();
FairyBook fairyBook1 = bookFactory.getFairyBook("格林童话");
fairyBook1.read();
FairyBook fairyBook2 = bookFactory.getFairyBook("小王子");
fairyBook2.read();
FairyBook fairyBook3 = bookFactory.getFairyBook("格林童话");
fairyBook3.read();
输出
格林童话1704856573
小王子705927765
格林童话1704856573//与第一本是同个实例
代理模式是常见的设计模式之一,顾名思义,代理模式就是代理对象具备真实对象的功能,并代替真实对象完成相应操作,并能够在操作执行的前后,对操作进行增强处理。(为真实对象提供代理,然后供其他对象通过代理访问真实对象)
以租房为例,租客找房东租房,然后中间经过房屋中介,以此为背景,它的UML图如下:
可以看出房东类(RentHouse)和代理类(IntermediaryProxy)都实现了租房接口,这就是一个静态代理的前提,那就是真实类和代理类要实现同一个接口,在代理类中实现真实类的方法同时可以进行真实类方法的增强处理,在一个代理类中就可以完成对多个真实对象的注入工作。
注意:代理对象和目标对象要实现相同的接口,让后通过调用相同的方法来调用目标对象的方法
代码如下:IRentHouse
共同接口
public interface IRentHouse {
void rentHouse();
}
RentHouse 目标对象
public class RentHouse implements IRentHouse {
@Override
public void rentHouse() {
System.out.println("实现租房");
}
}
IntermediaryProxy 代理对象
public class IntermediaryProxy implements IRentHouse {
private IRentHouse iRent;
public IntermediaryProxy(IRentHouse iRentHouse) {
iRent=iRentHouse;
}
@Override
public void rentHouse() {
System.out.println("交中介费");
iRent.rentHouse();
System.out.println("中介负责维修管理");
}
}
测试
//client测试类
public class TestStaticProxy {
public static void main(String[] args) {
//定义租房
IRentHouse iRentHouse = new RentHouse();
//定义中介
IRentHouse intermediaryProxy = new IntermediaryProxy(iRentHouse);
//中介租房
intermediaryProxy.rentHouse();
}
}
从静态代理的代码中可以发现,静态代理的缺点显而易见,那就是当真实类的方法越来越多的时候,这样构建的代理类的代码量是非常大的,所以就引进动态代理.
动态代理允许使用一种方法的单个类(代理类)为具有任意数量方法的任意类(真实类)的多个方法调用提供服务,看到这句话,可以容易的联想到动态代理的实现与反射密不可分。
JAVA 反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为 java 语言的反射机制。
1.jdk动态代理(接口代理)
Jdk代理涉及到java.lang.reflect包中的InvocationHandler接口和Proxy类,核心方法是
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
jdk动态代理过程中实际上代理的是接口,是因为在创建代理实例的时候,依赖的是java.lang.reflect包中Proxy类的newProxyInstance方法,该方法的生效就恰恰需要这个参数;
public static Object newProxyInstance(ClassLoader loader,
Class>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException{
……
}
下面以案例来说明jdk动态代理的完整过程:涉及两套接口及其实现类,一个代理类,以及一个测试类
//接口1
public interface Animal {
void wakeup();
void sleep();
}
//实现类1
public class Cat implements Animal {
private String name;
public Cat() {
}
public Cat(String name) {
this.name = name;
}
@Override
public void wakeup() {
System.out.println("小猫"+name+"早晨醒来啦");
}
@Override
public void sleep() {
System.out.println("小猫"+name+"晚上睡觉啦");
}
}
//实现类2
public class Dog implements Animal {
private String name;
public Dog() {
}
public Dog(String name) {
this.name = name;
}
@Override
public void wakeup() {
System.out.println("小狗"+name+"早晨醒来啦");
}
@Override
public void sleep() {
System.out.println("小狗"+name+"晚上睡觉啦");
}
}
//接口2
public interface Person {
void wakeup();
void sleep();
}
//实现类1
public class Student implements Person{
private String name;
public Student() {
}
public Student(String name) {
this.name = name;
}
@Override
public void wakeup() {
System.out.println("学生"+name+"早晨醒来啦");
}
@Override
public void sleep() {
System.out.println("学生"+name+"晚上睡觉啦");
}
}
//实现类2
public class Doctor implements Person {
private String name;
public Doctor() {
}
public Doctor(String name) {
this.name = name;
}
@Override
public void wakeup() {
System.out.println("医生"+name+"早晨醒来啦");
}
@Override
public void sleep() {
System.out.println("医生"+name+"晚上睡觉啦");
}
}
//代理类
public class JDKDynamicProxy implements InvocationHandler {
private Object bean;
public JDKDynamicProxy(Object bean) {
this.bean=bean;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodname=method.getName();
if (methodname.equals("wakeup")){
System.out.println("早安~~~");
}else if(methodname.equals("sleep")){
System.out.println("晚安~~~");
}
return method.invoke(bean,args);
}
}
//测试类
public class TestJDKDynamicProxy {
public static void main(String[] args) {
JDKDynamicProxy proxy = new JDKDynamicProxy(new Student("张三"));
//创建代理实例
Person student = (Person) Proxy.newProxyInstance(proxy.getClass().getClassLoader(), new Class[]{Person.class}, proxy);
student.wakeup();
student.sleep();
proxy = new JDKDynamicProxy(new Doctor("王教授"));
Person doctor = (Person) Proxy.newProxyInstance(proxy.getClass().getClassLoader(), new Class[]{Person.class}, proxy);
doctor.wakeup();
doctor.sleep();
proxy = new JDKDynamicProxy(new Dog("旺旺"));
Animal dog = (Animal) Proxy.newProxyInstance(proxy.getClass().getClassLoader(), new Class[]{Animal.class}, proxy);
dog.wakeup();
dog.sleep();
proxy = new JDKDynamicProxy(new Cat("咪咪"));
Animal cat = (Animal) Proxy.newProxyInstance(proxy.getClass().getClassLoader(), new Class[]{Animal.class}, proxy);
cat.wakeup();
cat.sleep();
}
}
在执行过程中,为两个接口分别生成了编译以后的虚拟代理类$Proxy0.class 和 $Proxy1.class,下面以接口1的动态代理过程,讲述jdk动态代理的来龙去脉
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package com.sun.proxy;
import com.deer.test.com.deer.vo.Person;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
public final class $Proxy0 extends Proxy implements Person {
private static Method m1;
private static Method m4;
private static Method m2;
private static Method m3;
private static Method m0;
public $Proxy0(InvocationHandler var1) throws {
super(var1);
}
public final boolean equals(Object var1) throws {
try {
return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final void wakeup() throws {
try {
super.h.invoke(this, m4, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final String toString() throws {
try {
return (String)super.h.invoke(this, m2, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final void sleep() throws {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final int hashCode() throws {
try {
return (Integer)super.h.invoke(this, m0, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m4 = Class.forName("com.deer.test.com.deer.vo.Person").getMethod("wakeup");
m2 = Class.forName("java.lang.Object").getMethod("toString");
m3 = Class.forName("com.deer.test.com.deer.vo.Person").getMethod("sleep");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
整个调用过程如下(省去本人由于自身原因走的弯路,直接说梳理结果):
从测试类中可以看出,一共创建了四个代理实例,然后每个代理实例对应接口的实现类,道理相同,这里只分析Student类的实现过程
总结对比:
1.静态代理中,代理类和真实类实现的是同一个接口,重写同样的方法;jdk动态代理中,代理类和真实类关系不大,代理类实现无侵入式的代码扩展。
2.静态代理中当接口中方法增加的时候,在代理类代码量也会增加,显然是不妥的;jdk动态代理解决了这个问题,当业务增加,代理类的代码不会增加。
3.jdk动态代理实现的是jdk自带InvocationHandler接口,实现了这个接口的类也叫拦截器类,也叫代理类。
2.cglib动态代理
从上面可以看出,jdk动态代理的前提条件是,要有接口存在,那还有许多场景是没有接口的,这个时候就需要cglib动态代理了,CGLIB(Code Generation Library)是一个基于ASM的字节码生成库,它允许我们在运行时对字节码进行修改和动态生成。CGLIB通过继承方式实现代理。
以下案例中所用到的**被代理类**和和上面jdk动态代理一样
cglib动态代理过程中生成的是实现类的子类,cglib是如何凭空创造的实现类的子类的,下面是测试代码
//所需的代理类
public class CglibProxy implements MethodInterceptor {
private Enhancer enhancer=new Enhancer();
private Object bean;
public CglibProxy(Object bean) {
this.bean = bean;
}
public Object getProxy(){
//设置需要创建子类的类
enhancer.setSuperclass(bean.getClass());
enhancer.setCallback(this);
return enhancer.create();
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
String methodName = method.getName();
if (methodName.equals("wakeup")){
System.out.println("早安~~~");
}else if(methodName.equals("sleep")){
System.out.println("晚安~~~");
}
return method.invoke(bean,objects);
}
}
//测试类
public class TestCglibProxy {
public static void main(String[] args) {
//生成虚拟代理类的代码,本来虚拟代理子类是看不见的,
//下面这句话的作用就是把执行过程中cglib增强后的class字节码文件
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\\aop");
CglibProxy proxy = new CglibProxy(new Student("张三"));
Student student = (Student) proxy.getProxy();
student.wakeup();
student.sleep();
proxy = new CglibProxy(new Doctor("王教授"));
Doctor doctor = (Doctor) proxy.getProxy();
doctor.wakeup();
doctor.sleep();
proxy = new CglibProxy(new Dog("旺旺"));
Dog dog = (Dog) proxy.getProxy();
dog.wakeup();
dog.sleep();
proxy = new CglibProxy(new Cat("咪咪"));
Cat cat = (Cat) proxy.getProxy();
cat.wakeup();
cat.sleep();
}
}
附上代理Cat类时候,生成的Cat的子类:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
public class Cat$$EnhancerByCGLIB$$8ca2de8b extends Cat implements Factory {
private boolean CGLIB$BOUND;
public static Object CGLIB$FACTORY_DATA;
private static final ThreadLocal CGLIB$THREAD_CALLBACKS;
private static final Callback[] CGLIB$STATIC_CALLBACKS;
private MethodInterceptor CGLIB$CALLBACK_0;
private static Object CGLIB$CALLBACK_FILTER;
private static final Method CGLIB$sleep$0$Method;
private static final MethodProxy CGLIB$sleep$0$Proxy;
private static final Object[] CGLIB$emptyArgs;
private static final Method CGLIB$wakeup$1$Method;
private static final MethodProxy CGLIB$wakeup$1$Proxy;
private static final Method CGLIB$equals$2$Method;
private static final MethodProxy CGLIB$equals$2$Proxy;
private static final Method CGLIB$toString$3$Method;
private static final MethodProxy CGLIB$toString$3$Proxy;
private static final Method CGLIB$hashCode$4$Method;
private static final MethodProxy CGLIB$hashCode$4$Proxy;
private static final Method CGLIB$clone$5$Method;
private static final MethodProxy CGLIB$clone$5$Proxy;
static void CGLIB$STATICHOOK4() {
CGLIB$THREAD_CALLBACKS = new ThreadLocal();
CGLIB$emptyArgs = new Object[0];
Class var0 = Class.forName("com.example.demo.cglibproxy.vo.Cat$$EnhancerByCGLIB$$8ca2de8b");
Class var1;
Method[] var10000 = ReflectUtils.findMethods(new String[]{"equals", "(Ljava/lang/Object;)Z", "toString", "()Ljava/lang/String;", "hashCode", "()I", "clone", "()Ljava/lang/Object;"}, (var1 = Class.forName("java.lang.Object")).getDeclaredMethods());
CGLIB$equals$2$Method = var10000[0];
CGLIB$equals$2$Proxy = MethodProxy.create(var1, var0, "(Ljava/lang/Object;)Z", "equals", "CGLIB$equals$2");
CGLIB$toString$3$Method = var10000[1];
CGLIB$toString$3$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/String;", "toString", "CGLIB$toString$3");
CGLIB$hashCode$4$Method = var10000[2];
CGLIB$hashCode$4$Proxy = MethodProxy.create(var1, var0, "()I", "hashCode", "CGLIB$hashCode$4");
CGLIB$clone$5$Method = var10000[3];
CGLIB$clone$5$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/Object;", "clone", "CGLIB$clone$5");
var10000 = ReflectUtils.findMethods(new String[]{"sleep", "()V", "wakeup", "()V"}, (var1 = Class.forName("com.example.demo.cglibproxy.vo.Cat")).getDeclaredMethods());
CGLIB$sleep$0$Method = var10000[0];
CGLIB$sleep$0$Proxy = MethodProxy.create(var1, var0, "()V", "sleep", "CGLIB$sleep$0");
CGLIB$wakeup$1$Method = var10000[1];
CGLIB$wakeup$1$Proxy = MethodProxy.create(var1, var0, "()V", "wakeup", "CGLIB$wakeup$1");
}
final void CGLIB$sleep$0() {
super.sleep();
}
public final void sleep() {
MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
if (this.CGLIB$CALLBACK_0 == null) {
CGLIB$BIND_CALLBACKS(this);
var10000 = this.CGLIB$CALLBACK_0;
}
if (var10000 != null) {
var10000.intercept(this, CGLIB$sleep$0$Method, CGLIB$emptyArgs, CGLIB$sleep$0$Proxy);
} else {
super.sleep();
}
}
final void CGLIB$wakeup$1() {
super.wakeup();
}
public final void wakeup() {
MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
if (this.CGLIB$CALLBACK_0 == null) {
CGLIB$BIND_CALLBACKS(this);
var10000 = this.CGLIB$CALLBACK_0;
}
if (var10000 != null) {
var10000.intercept(this, CGLIB$wakeup$1$Method, CGLIB$emptyArgs, CGLIB$wakeup$1$Proxy);
} else {
super.wakeup();
}
}
final boolean CGLIB$equals$2(Object var1) {
return super.equals(var1);
}
public final boolean equals(Object var1) {
MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
if (this.CGLIB$CALLBACK_0 == null) {
CGLIB$BIND_CALLBACKS(this);
var10000 = this.CGLIB$CALLBACK_0;
}
if (var10000 != null) {
Object var2 = var10000.intercept(this, CGLIB$equals$2$Method, new Object[]{var1}, CGLIB$equals$2$Proxy);
return var2 == null ? false : (Boolean)var2;
} else {
return super.equals(var1);
}
}
final String CGLIB$toString$3() {
return super.toString();
}
public final String toString() {
MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
if (this.CGLIB$CALLBACK_0 == null) {
CGLIB$BIND_CALLBACKS(this);
var10000 = this.CGLIB$CALLBACK_0;
}
return var10000 != null ? (String)var10000.intercept(this, CGLIB$toString$3$Method, CGLIB$emptyArgs, CGLIB$toString$3$Proxy) : super.toString();
}
final int CGLIB$hashCode$4() {
return super.hashCode();
}
public final int hashCode() {
MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
if (this.CGLIB$CALLBACK_0 == null) {
CGLIB$BIND_CALLBACKS(this);
var10000 = this.CGLIB$CALLBACK_0;
}
if (var10000 != null) {
Object var1 = var10000.intercept(this, CGLIB$hashCode$4$Method, CGLIB$emptyArgs, CGLIB$hashCode$4$Proxy);
return var1 == null ? 0 : ((Number)var1).intValue();
} else {
return super.hashCode();
}
}
final Object CGLIB$clone$5() throws CloneNotSupportedException {
return super.clone();
}
protected final Object clone() throws CloneNotSupportedException {
MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
if (this.CGLIB$CALLBACK_0 == null) {
CGLIB$BIND_CALLBACKS(this);
var10000 = this.CGLIB$CALLBACK_0;
}
return var10000 != null ? var10000.intercept(this, CGLIB$clone$5$Method, CGLIB$emptyArgs, CGLIB$clone$5$Proxy) : super.clone();
}
public static MethodProxy CGLIB$findMethodProxy(Signature var0) {
String var10000 = var0.toString();
switch(var10000.hashCode()) {
case -1385928386:
if (var10000.equals("sleep()V")) {
return CGLIB$sleep$0$Proxy;
}
break;
case -508378822:
if (var10000.equals("clone()Ljava/lang/Object;")) {
return CGLIB$clone$5$Proxy;
}
break;
case 391780310:
if (var10000.equals("wakeup()V")) {
return CGLIB$wakeup$1$Proxy;
}
break;
case 1826985398:
if (var10000.equals("equals(Ljava/lang/Object;)Z")) {
return CGLIB$equals$2$Proxy;
}
break;
case 1913648695:
if (var10000.equals("toString()Ljava/lang/String;")) {
return CGLIB$toString$3$Proxy;
}
break;
case 1984935277:
if (var10000.equals("hashCode()I")) {
return CGLIB$hashCode$4$Proxy;
}
}
return null;
}
public Cat$$EnhancerByCGLIB$$8ca2de8b() {
CGLIB$BIND_CALLBACKS(this);
}
public Cat$$EnhancerByCGLIB$$8ca2de8b(String var1) {
super(var1);
CGLIB$BIND_CALLBACKS(this);
}
public static void CGLIB$SET_THREAD_CALLBACKS(Callback[] var0) {
CGLIB$THREAD_CALLBACKS.set(var0);
}
public static void CGLIB$SET_STATIC_CALLBACKS(Callback[] var0) {
CGLIB$STATIC_CALLBACKS = var0;
}
private static final void CGLIB$BIND_CALLBACKS(Object var0) {
Cat$$EnhancerByCGLIB$$8ca2de8b var1 = (Cat$$EnhancerByCGLIB$$8ca2de8b)var0;
if (!var1.CGLIB$BOUND) {
var1.CGLIB$BOUND = true;
Object var10000 = CGLIB$THREAD_CALLBACKS.get();
if (var10000 == null) {
var10000 = CGLIB$STATIC_CALLBACKS;
if (CGLIB$STATIC_CALLBACKS == null) {
return;
}
}
var1.CGLIB$CALLBACK_0 = (MethodInterceptor)((Callback[])var10000)[0];
}
}
public Object newInstance(Callback[] var1) {
CGLIB$SET_THREAD_CALLBACKS(var1);
Cat$$EnhancerByCGLIB$$8ca2de8b var10000 = new Cat$$EnhancerByCGLIB$$8ca2de8b();
CGLIB$SET_THREAD_CALLBACKS((Callback[])null);
return var10000;
}
public Object newInstance(Callback var1) {
CGLIB$SET_THREAD_CALLBACKS(new Callback[]{var1});
Cat$$EnhancerByCGLIB$$8ca2de8b var10000 = new Cat$$EnhancerByCGLIB$$8ca2de8b();
CGLIB$SET_THREAD_CALLBACKS((Callback[])null);
return var10000;
}
public Object newInstance(Class[] var1, Object[] var2, Callback[] var3) {
CGLIB$SET_THREAD_CALLBACKS(var3);
Cat$$EnhancerByCGLIB$$8ca2de8b var10000 = new Cat$$EnhancerByCGLIB$$8ca2de8b;
switch(var1.length) {
case 0:
var10000.();
break;
case 1:
if (var1[0].getName().equals("java.lang.String")) {
var10000.((String)var2[0]);
break;
}
throw new IllegalArgumentException("Constructor not found");
default:
throw new IllegalArgumentException("Constructor not found");
}
CGLIB$SET_THREAD_CALLBACKS((Callback[])null);
return var10000;
}
public Callback getCallback(int var1) {
CGLIB$BIND_CALLBACKS(this);
MethodInterceptor var10000;
switch(var1) {
case 0:
var10000 = this.CGLIB$CALLBACK_0;
break;
default:
var10000 = null;
}
return var10000;
}
public void setCallback(int var1, Callback var2) {
switch(var1) {
case 0:
this.CGLIB$CALLBACK_0 = (MethodInterceptor)var2;
default:
}
}
public Callback[] getCallbacks() {
CGLIB$BIND_CALLBACKS(this);
return new Callback[]{this.CGLIB$CALLBACK_0};
}
public void setCallbacks(Callback[] var1) {
this.CGLIB$CALLBACK_0 = (MethodInterceptor)var1[0];
}
static {
CGLIB$STATICHOOK4();
}
}
不难发现,cglib生成的增强子类,比jdk生成的接口代理类默认多实现了clone();而创建这种子类的核心代码就是:
public Object getProxy(){
//设置需要创建子类的类
enhancer.setSuperclass(bean.getClass());
enhancer.setCallback(this);
return enhancer.create();
}
该案例下,cglib的代理实现过程如下:
总结:cglib动态代理和jdk动态代理的区别显而易见,但是实现逻辑差不多,cglib代理类是通过实现MethodInterceptor,重写intercept方法,通过生成被代理类的子类来达到代理增强代码的目的;而Jdk代理是通过实现InvocationHandler,重写invoke方法,通过生成接口的代理类来达到代码增强的目的,所以jdk动态代理的实现需要接口,cglib则不需要,spring5.0以上以及springboot2.0以上默认采用cglib动态来实现AOP。
我们平时办理入职的流程是:填写入职登记表->打印简历->复印学历->复印身份证->签订劳动合同->建立花名册->办理工牌->安排工位。我平时在家里炒菜的流程是:洗锅->点火->热锅->上油->下原料->翻炒->放调料->出锅。以上这些都是模板模式的体现。
模板模式又叫模版方法模式(Template Method Pattern),是指定义一个算法的骨架,并允许子类为一个或多个步骤提供实现。模板模式使得子类可以在不改变算法结构的情况下,重新定义算法的某些步骤,属于行为型设计模式。模版模式适用于以下场景:
(1)一次性实现算法的不变部分,并将可变的行为留给子类来实现。
(2)各子类中公共的行为被提取出来并集中到一个公共的父类中,从而避免代码重复。
下面我们写一个汽车类,来看代码实现:
抽象模型:
public abstract class HummerModel {
//能发动
public abstract void start();
//能停下来
public abstract void stop();
//喇叭会出声音
public abstract void alarm();
//引擎会轰隆隆地响
public abstract void engineBoom();
//会跑吧
public final void run() {
//先发动汽车
this.start();
//引擎开始轰鸣
this.engineBoom();
//然后就开始跑了,跑的过程中遇到一条狗挡路,就按喇叭
this.alarm();
//到达目的地就停车
this.stop();
}
}
在抽象类中,我们定义了悍马模型都必须具有的特质:能够发动、停止,喇叭会响,引擎可以轰鸣,而且还可以停止。每个型号的悍马实现是不同的,H1型号的悍马如代码:
public class HummerH1Model extends HummerModel{
@Override
public void start() {
System.out.println("悍马H1启动.....");
}
@Override
public void stop() {
System.out.println("悍马H1停车.....");
}
@Override
public void alarm() {
System.out.println("悍马H1鸣笛.....");
}
@Override
public void engineBoom() {
System.out.println("悍马H1引擎Boom.....");
}
}
H2型号实现:
public class HummerH2Model extends HummerModel{
@Override
public void start() {
System.out.println("悍马H2启动.....");
}
@Override
public void stop() {
System.out.println("悍马H2停车.....");
}
@Override
public void alarm() {
System.out.println("悍马H2鸣笛.....");
}
@Override
public void engineBoom() {
System.out.println("悍马H2引擎Boom.....");
}
}
场景类:
class Client{
public static void main(String[] args) {
HummerModel hummerH2Model = new HummerH2Model();
//H2模型演示
hummerH2Model.run();
}
}
结果如下:
模版方法通用类图
模板方法模式非常简单,仅仅使用了Java的继承机制,但它是一个应用非常广泛的模式。其中,AbstractClass叫做抽象模板。
注意 :为了防止恶意的操作,一般模板方法都加上final关键字,不允许被覆写。
到目前为止,两个模型都运行的很好,但是突然有一天老大给我说车子不行了,一启动就滴滴滴响,客户说H1模型的汽车想让他响再响,那怎么解决呢?我们来看类图:
抽象类实现 :
public abstract class HummerModel {
//钩子方法,默认是回响的
protected boolean isAlarm() {
return true;
}
//能发动
protected abstract void start();
//能停下来
protected abstract void stop();
//喇叭会出声音
protected abstract void alarm();
//引擎会轰隆隆地响
protected abstract void engineBoom();
//会跑吧
protected final void run() {
//先发动汽车
this.start();
//引擎开始轰鸣
this.engineBoom();
//想叫再叫
if (isAlarm()) {
this.alarm();
}
//到达目的地就停车
this.stop();
}
}
在抽象类中,isAlarm()是一个实现方法。其作用是模板方法根据其返回值决定是否要响喇叭,子类可以覆写该返回值,由于H1型号的喇叭是想让它响就响,不想让它响就不响, 由人控制,来看代码:
public class HummerH1Model extends HummerModel{
private boolean isAlarm = true;
@Override
public void start() {
System.out.println("悍马H1启动.....");
}
@Override
public void stop() {
System.out.println("悍马H1停车.....");
}
@Override
public void alarm() {
System.out.println("悍马H1鸣笛.....");
}
@Override
public void engineBoom() {
System.out.println("悍马H1引擎Boom.....");
}
@Override
public boolean isAlarm() {
return this.isAlarm;
}
public void setAlarm(boolean isAlarm){
this.isAlarm = isAlarm;
}
}
场景类:
class Client{
public static void main(String[] args) {
System.out.println("-------H1型号悍马--------");
System.out.println("H1型号的悍马是否需要喇叭声响?0-不需要 1-需要");
String type = new Scanner(System.in).next();
HummerH1Model hummerH1Model = new HummerH1Model();
if (type.equals("0")){
hummerH1Model.setAlarm(false);
}
hummerH1Model.run();
}
}
结果如下:
看到没,H1型号的悍马是由客户自己控制是否要响喇叭,也就是说外界条件改变,影响到模板方法的执行。在我们的抽象类中isAlarm()的返回值就是影响了模板方法的执行结果,该方法就叫做钩子方法(Hook Method)。有了钩子方法模板方法模式才算完美,大家可以想想,由子类的一个方法返回值决定公共部分的执行结果,是不是很有吸引力呀!
模板方法模式就是在模板方法中按照一定的规则和顺序调用基本方法,具体到前面那个例子,就是run()方法按照规定的顺序(先调用start(),然后再调用engineBoom(),再调用 alarm(),最后调用stop() )调用本类的其他方法,并且由isAlarm()方法的返回值确定run()中的执行顺序变更。
一、什么是命令式
命令(Command)模式又叫作动作(Action)模式或事务(Transaction)模式,是一种对象的行为模式。将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤消的操作。
命令模式的本质:封装请求
设计意图:命令模式通过将请求封装到一个命令(Command)对象中,实现了请求调用者和具体实现者之间的解耦。
二、命令模式的适用性
在以下条件下可以考虑使用命令模式:
• 如果需要抽象出需要执行的动作,并参数化这些对象,可以选用命令模式。将这些需要执行的动作抽象成为命令,然后实现命令的参数化配置。
• 如果需要在不同的时刻指定、排列和执行请求,可以选用命令模式。将这些请求封装成为命令对象,然后实现将请求队列化。
• 如果需要支持取消操作,可以选用命令模式,通过管理命令对象,能很容易地实现命令的恢复和重做功能。
• 如果需要支持当系统崩溃时,能将系统的操作功能重新执行一遍,可以选用命令模式。将这些操作功能的请求封装成命令对象,然后实现日志命令,就可以在系统恢复以后,通过日志获取命令列表,从而重新执行一遍功能。
• 在需要事务的系统中,可以选用命令模式。命令模式提供了对事务进行建模的方法。命令模式有一个别名就是Transaction。
命令模式涉及的角色及其职责如下:
抽象命令(Command)角色:一般定义为接口,用来定义执行命令的接口。
具体命令(ConcreteCommand)角色:通常会持有接收者对象,并调用接收者对象的相应功能来完成命令要执行的操作。
接收者(Receiver)角色:真正执行命令的对象。任何类都可能成为接收者,只要它能够实现命令要求实现的相应功能。
调用者(Invoker)角色:要求命令对象执行请求,通常会持有命令对象,可以持有很多的命令对象。这个是客户端真正触发命令并要求命令执行相应操作的地方,也就是说相当于使用命令对象的入口。
客户端(Client)角色:创建具体的命令对象,并且设置命令对象的接收者。
先来看看抽象命令接口的定义。示例代码如下:
/**
* 命令接口
*/
public interface Command {
/**
* 执行命令
*/
public void execute();
}
接下来看看具体命令是如何实现的。示例代码如下:
public class ConcreteCommand implements Command{
/**
* 持有相应的接收者对象
*/
private Receiver receiver = null;
/**
* 构造方法,传入相应的接收者对象
*
* @param receiver 相应的接收者对象
*/
public ConcreteCommand(Receiver receiver) {
this.receiver = receiver;
}
/**
* 执行命令
*/
@Override
public void execute() {
// 通常会转调接收者对象的相应方法,让接收者来真正执行功能
receiver.action();
}
}
再来看看接收者的定义。示例代码如下:
/**
* 命令的接收者
*/
public class Receiver {
/**
* 示意方法,真正执行命令相应的操作
*/
public void action() {
System.out.println("接收者开始行动。。。");
}
}
下面该来看看调用者如何实现的了。示例代码如下
/**
* 命令的调用者
*/
public class Invoker {
/**
* 持有命令对象
*/
private Command command = null;
/**
* 设置调用者持有的命令对象
*
* @param command 命令对象
*/
public void setCommand(Command command) {
this.command = command;
}
/**
* 示意方法,调用命令执行请求
*/
public void invoke() {
command.execute();
}
}
再来看看客户端的实现
public class Client {
public static void main(String[] args) {
// 创建接收者
Receiver receiver = new Receiver();
// 创建命令对象,设定它的接收者
Command command = new ConcreteCommand(receiver);
// 创建调用者,把命令对象设置进去
Invoker invoker = new Invoker();
invoker.setCommand(command);
// 调用者调用命令
invoker.invoke();
}
}
四、命令模式的优点
更松散的耦合: 命令模式使得发起命令的对象——客户端,和具体实现命令的对象——接收者对象完全解耦,也就是说发起命令的对象完全不知道具体实现对象是谁,也不知道如何实现。
更动态的控制:命令模式把请求封装起来,可以动态地对它进行参数化、队列化和日志化等操作,从而使得系统更灵活。
很自然的复合命令:命令模式中的命令对象能够很容易地组合成复合命令,如宏命令,从而使系统操作更简单,功能更强大。
更好的扩展性: 由于发起命令的对象和具体的实现完全解耦,因此扩展新的命令就很容易,只需要实现新的命令对象,然后在装配的时候,把具体的实现对象设置到命令对象中,然后就可以使用这个命令对象,已有的实现完全不用变化。
五、总结
命令模式是对命令的封装。命令模式把发出命令的责任和执行命令的责任分割开,委派给不同的对象。
每一个命令都是一个操作:请求的一方发出请求要求执行一个操作;接收的一方收到请求,并执行操作。命令模式允许请求的一方和接收的一方独立开来,使得请求的一方不必知道接收请求的一方的接口,更不必知道请求是怎么被接收,以及操作是否被执行、何时被执行,以及是怎么被执行的。
访问者模式概念:封装作用于某对象结构中的各元素的操作,它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。
通俗的解释就是,系统中有一些固定结构的对象(元素),在其内部提供一个accept()方法用来接受访问者对象的访问,不同的访问者对同一元素的访问内容不同,所以使得相同的元素可以产生不同的元素结果。
比如在一个人事管理系统中,有多个工种的员工和多个老板,不同的老板对同一个员工的关注点是不同的,CTO可能关注的就是技术,CEO可能更注重绩效。
员工就是一个稳定的元素,老板就是变化的,对应概念就是:封装员工的一些操作,可以在不改变员工类的前提下,增加新的老板访问同一个员工。
在访问者模式中包含五个角色,抽象元素、具体元素、抽象访问者、具体访问者、结构元素。
抽象元素:定义一个接受访问的方法accept,参数为访问者对象。
具体元素:提供接受访问者访问的具体实现调用访问者的访问visit,并定义额外的数据操作方法。
抽象访问者:这个角色主要是定义对具体元素的访问方法visit,理论上来说方法数等于元素(固定类型的对象,也就是被访问者)个数。
具体访问者:实现对具体元素的访问visit方法,参数就是具体元素。
结构对象:创建一个数组用来维护元素,并提供一个方法访问所有的元素。
在一个公司有干活的工程师和管理者,也有抓技术的CTO和管绩效的CEO,CTO和CEO都会访问管理员和工程师,当公司来了新的老板,只需要增加访问者即可。工程师和管理者就是元素、公司就是结构体、CEO、CTO就是访问者。
抽象元素:
public interface ElementAbstract {
void accept(VisitorAbstract visitor);
}
具体元素-工程师:
public class ElementEngineer implements ElementAbstract {
private String name;
private int kpi;
ElementEngineer(String name){
this.name = name;
this.kpi = new Random().nextInt(10);
}
public String getName() {
return name;
}
public int getKpi() {
return kpi;
}
@Override
public void accept(VisitorAbstract visitor) {
visitor.visit(this);
}
public int getCodeLineTotal(){
return this.kpi * 1000000;
}
}
具体元素-管理者:
public class ElementManager implements ElementAbstract {
private String name;
private int kpi;
ElementManager(String name){
this.name = name;
this.kpi = new Random().nextInt(10);
}
public String getName() {
return name;
}
public int getKpi() {
return kpi;
}
@Override
public void accept(VisitorAbstract visitor) {
visitor.visit(this);
}
public int getProductNum(){
return this.kpi * 10;
}
}
抽象访问者:
public interface VisitorAbstract {
void visit(ElementEngineer engineer);
void visit(ElementManager manager);
}
具体访问者-CEO
public class VisitorCEO implements VisitorAbstract {
@Override
public void visit(ElementEngineer engineer) {
System.out.println("工程师:" + engineer.getName() + "KPI:" + engineer.getKpi());
}
@Override
public void visit(ElementManager manager) {
System.out.println("经理:" + manager.getName() + "KPI:" + manager.getKpi() + " 今年共完成项目:" + manager.getProductNum() + "个");
}
}
具体访问者-CTO
public class VisitorCTO implements VisitorAbstract {
@Override
public void visit(ElementEngineer engineer) {
System.out.println("工程师:" + engineer.getName() + " 今年代码量" + engineer.getCodeLineTotal() + "行");
}
@Override
public void visit(ElementManager manager) {
System.out.println("经理:" + manager.getName() + " 今年共完成项目:" + manager.getProductNum() + "个");
}
}
结构体:
public class Structure {
List list = new ArrayList<>();
public Structure addEmployee(ElementAbstract employee){
list.add(employee);
return this;
}
public void report(VisitorAbstract visitor){
list.forEach(employee -> {
employee.accept(visitor);
});
}
}
客户端:
public class Client {
public static void main(String[] args) {
//元素对象
ElementEngineer engineerZ = new ElementEngineer("小张");
ElementEngineer engineerW = new ElementEngineer("小王");
ElementEngineer engineerL = new ElementEngineer("小李");
ElementManager managerZ = new ElementManager("张总");
ElementManager managerW = new ElementManager("王总");
ElementManager managerL = new ElementManager("李总");
//结构体对象
Structure structure = new Structure();
structure.addEmployee(engineerZ).addEmployee(engineerW).addEmployee(engineerL).addEmployee(managerZ).addEmployee(managerW).addEmployee(managerL);
structure.report(new VisitorCTO());
System.out.println("---------------------------------------");
structure.report(new VisitorCEO());
}
}
访问者模式中有一个重要的概念叫:伪动态双分派。
我们一步一步解读它的含义,什么叫分派?根据对象的类型而对方法进行的选择,就是分派(Dispatch)。
发生在编译时的分派叫静态分派,例如重载(overload),发生在运行时的分派叫动态分派,例如重写(overwrite)。
其中分派又分为单分派和多分派。
单分派:依据单个变量进行方法的选择就叫单分派,Java 动态分派(重写)只根据方法的接收者一个变量进行分配,所以其是单分派。
多分派:依据多个变量进行方法的选择就叫多分派,Java 静态分派(重载)要根据方法的接收者与参数这两个变量进行分配,所以其是多分派。
理解了概念我们接着看我们的案例:
@Override
public void accept(VisitorAbstract visitor) {
visitor.visit(this);
}
我们案例中的accept方法,是由元素的运行时类型决定的,应该是属于动态单分派。我们接着看 visitor.visit(this)又是一次动态单分派,两次动态单分派实现了双分派的效果,所以称为伪动态双分派。这个概念理解就好,实际应用中知不知道这玩意都不影响。
迭代器模式官方解释就是提供一个对象来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示。何为聚合对象呢?最典型的就是集合类。
大白话也就是,集合中的数据是私有的,集合中不应该提供直接遍历的方法,要定义一个新的对象用于访问这个集合。
既然是一个专门用来遍历的对象,一个被遍历的聚合对象,很显然至少有两个对象,迭代器对象、聚合对象,由于遵循面向接口编程的原则,迭代器对象和聚合对象应该抽象出来接口,那自然而然就是应该有四个角色:
抽象聚合(InterfaceAggregate)角色:定义存储、添加、删除聚合元素以及创建迭代器对象的接口。
具体聚合(ConcreteAggregate)角色:实现抽象聚合类,返回一个具体迭代器的实例。
抽象迭代器(Iterator)角色:定义访问和遍历聚合元素的接口,通常包含 hasNext()、next() 等方法。
具体迭代器(Concretelterator)角色:实现抽象迭代器接口中所定义的方法,完成对聚合对象的遍历,记录遍历的当前位置。
基于四个角色我们举一个典型案例。
应该是有四个类
抽象聚合角色,用于定义增删改查元素的统一规范接口,和创建迭代器对象的方法
具体聚合角色,实现抽象聚合角色方法
抽象迭代器角色,定义遍历元素的统一规范接口
具体迭代器,实现抽象迭代器角色的方法。
抽象聚合角色:
public interface InterfaceAggregate {
/**
* 增加对象
* @param obj 对象
*/
void add(Object obj);
/**
* 移除对象
* @param obj 对象
*/
void remove(Object obj);
/**
* 调用迭代器
* @return 迭代器
*/
Iterator getIterator();
}
具体聚合角色:
public class ConcreteAggregate implements InterfaceAggregate{
private List
抽象迭代器角色:
public interface Iterator {
/**
* 删除对象
* @return 对象
*/
Object remove();
/**
* 调用下一个对象
* @return 对象
*/
E next();
/**
* 迭代器中是否还有下一个对象
* @return
*/
boolean hasNext();
/**
* 遍历迭代器中剩余的对象
* @param action
*/
default void forEachRemaining(Consumer super E> action) {
Objects.requireNonNull(action);
while (hasNext())
action.accept(next());
}
}
具体迭代器角色:
public class Concretelterator implements Iterator{
private List
客户端调用:
public class Client {
public static void main(String[] args) {
ConcreteAggregate concreteAggregate=new ConcreteAggregate();
concreteAggregate.add("老王");
concreteAggregate.add("小王");
concreteAggregate.add("小张");
System.out.println("Aggregate聚合对象有:");
Iterator iterator=concreteAggregate.getIterator();
while (iterator.hasNext()){
Object next = iterator.next();
System.out.println(next.toString());
}
//遍历剩下的角色
iterator.forEachRemaining(ele -> System.out.println(ele));
}
}
迭代器模式在Jdk中的集合类中有着广泛的应用,我们以ArrayList作为典型。
在ArrayList实现迭代器时,同样是有四个角色。
List抽象聚合类;
ArrayList具体聚合角色;
Iterator抽象迭代器;
ArrayList内部类Itr是具体迭代器;
我们可以看到ArrayList是把具体聚合角色和具体迭代器都写在一个类中,Itr作为具体迭代对象是以内部类的形式。
ArrayList其实和我们案例中的方法长的很像,只不过ArrayList中定义了更多的方法,而且ArrayList还有一个内部类ListItr。
测试方法
public class Client {
public static void main(String[] args) {
// jdk ArrayList迭代器
//创建一个元素类型为Integer的集合
Collection collection = new ArrayList<>();
//向集合中添加元素
collection.add("老王");
collection.add("小王");
collection.add("小张");
//获取该集合的迭代器
java.util.Iterator iteratorJdk= collection.iterator();
System.out.println("Arraylist聚合对象有:");
//调用迭代器的经过集合实现的抽象方法遍历集合元素
while(iteratorJdk.hasNext())
{
System.out.println(iteratorJdk.next());
}
//调用forEachRemaining()方法遍历集合元素
iteratorJdk.forEachRemaining(ele -> System.out.println(ele));
}
}
当一个对象是一个聚合对象且需要对外提供遍历方法时,可以使用迭代器模式,也即实际业务中定义的有聚合对象,里面存放了我们需要的业务数据,为了让业务数据的职责更清晰,我们就可以将编辑的方法提取出来,另外定义一个迭代器对象用于遍历数据。
迭代器方式提供了不同的方式遍历聚合对象,增加新的聚合类和迭代器类都是比较方便的,Java集合类中庞大的家族采用迭代器模式就是基于这种优点。
迭代器模式有设计模式的通用缺点——系统复杂性,迭代器模式将数据存储和数据遍历分开,增加了类的个数。
观察者模式是对象的行为模式,又叫发布-订阅(Publish/Subscribe)模式、模型-视图(Model/View)模式、源-监听器(Source/Listener)模式或从属者(Dependents)模式。观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态上发生变化时,会通知所有观察者对象,使它们能够自动更新自己。
一个软件系统里面包含了各种对象,就像一片欣欣向荣的森林充满了各种生物一样。在一片森林中,各种生物彼此依赖和约束,形成一个个生物链。一种生物的状态变化会造成其他一些生物的相应行动,每一个生物都处于别的生物的互动之中。同样,一个软件系统常常要求在某一个对象的状态发生变化的时候,某些其他的对象做出相应的改变。做到这一点的设计方案有很多,但是为了使系统能够易于复用,应该选择低耦合度的设计方案。减少对象之间的耦合有利于系统的复用,但是同时设计师需要使这些低耦合度的对象之间能够维持行动的协调一致,保证高度的协作。观察者模式是满足这一要求的各种设计方案中最重要的一种。
下面以一个简单的示意性实现为例,讨论观察者模式的结构。
观察者模式所涉及的角色有:
● 抽象主题(Subject)角色:抽象主题角色把所有对观察者对象的引用保存在一个聚集(比如ArrayList对象)里,每个主题都可以有任何数量的观察者。抽象主题提供一个接口,可以增加和删除观察者对象,抽象主题角色又叫做抽象被观察者(Observable)角色。
● 具体主题(ConcreteSubject)角色:将有关状态存入具体观察者对象;在具体主题的内部状态改变时,给所有登记过的观察者发出通知。具体主题角色又叫做具体被观察者(Concrete Observable)角色。
● 抽象观察者(Observer)角色:为所有的具体观察者定义一个接口,在得到主题的通知时更新自己,这个接口叫做更新接口。
● 具体观察者(ConcreteObserver)角色:存储与主题的状态自恰的状态。具体观察者角色实现抽象观察者角色所要求的更新接口,以便使本身的状态与主题的状态 像协调。如果需要,具体观察者角色可以保持一个指向具体主题对象的引用。
抽象主题角色类
public abstract class Subject {
/**
* 用来保存注册的观察者对象
*/
private List list = new ArrayList();
/**
* 注册观察者对象
* @param observer 观察者对象
*/
public void attach(Observer observer){
list.add(observer);
System.out.println("Attached an observer");
}
/**
* 删除观察者对象
* @param observer 观察者对象
*/
public void detach(Observer observer){
list.remove(observer);
}
/**
* 通知所有注册的观察者对象
*/
public void nodifyObservers(String newState){
for(Observer observer : list){
observer.update(newState);
}
}
}
具体主题角色类
public class ConcreteSubject extends Subject{
private String state;
public String getState() {
return state;
}
public void change(String newState){
state = newState;
System.out.println("主题状态为:" + state);
//状态发生改变,通知各个观察者
this.nodifyObservers(state);
}
}
抽象观察者角色类
public interface Observer {
/**
* 更新接口
* @param state 更新的状态
*/
public void update(String state);
}
具体观察者角色类
public class ConcreteObserver implements Observer {
//观察者的状态
private String observerState;
@Override
public void update(String state) {
/**
* 更新观察者的状态,使其与目标的状态保持一致
*/
observerState = state;
System.out.println("状态为:"+observerState);
}
}
客户端类
public class Client {
public static void main(String[] args) {
//创建主题对象
ConcreteSubject subject = new ConcreteSubject();
//创建观察者对象
Observer observer = new ConcreteObserver();
//将观察者对象登记到主题对象上
subject.attach(observer);
//改变主题对象的状态
subject.change("new state");
}
}
运行结果如下
在运行时,这个客户端首先创建了具体主题类的实例,以及一个观察者对象。然后,它调用主题对象的attach()方法,将这个观察者对象向主题对象登记,也就是将它加入到主题对象的聚集中去。
这时,客户端调用主题的change()方法,改变了主题对象的内部状态。主题对象在状态发生变化时,调用超类的notifyObservers()方法,通知所有登记过的观察者对象。
在观察者模式中,又分为推模型和拉模型两种方式。
● 推模型
主题对象向观察者推送主题的详细信息,不管观察者是否需要,推送的信息通常是主题对象的全部或部分数据。
● 拉模型
主题对象在通知观察者的时候,只传递少量信息。如果观察者需要更具体的信息,由观察者主动到主题对象中获取,相当于是观察者从主题对象中拉数据。一般这种模型的实现中,会把主题对象自身通过update()方法传递给观察者,这样在观察者需要获取数据的时候,就可以通过这个引用来获取了。
根据上面的描述,发现前面的例子就是典型的推模型,下面给出一个拉模型的实例。
拉模型的抽象观察者类
拉模型通常都是把主题对象当做参数传递。
public interface Observer {
/**
* 更新接口
* @param subject 传入主题对象,方面获取相应的主题对象的状态
*/
public void update(Subject subject);
}
拉模型的具体观察者类
public class ConcreteObserver implements Observer {
//观察者的状态
private String observerState;
@Override
public void update(Subject subject) {
/**
* 更新观察者的状态,使其与目标的状态保持一致
*/
observerState = ((ConcreteSubject)subject).getState();
System.out.println("观察者状态为:"+observerState);
}
}
拉模型的抽象主题类
拉模型的抽象主题类主要的改变是nodifyObservers()方法。在循环通知观察者的时候,也就是循环调用观察者的update()方法的时候,传入的参数不同了。
public abstract class Subject {
/**
* 用来保存注册的观察者对象
*/
private List list = new ArrayList();
/**
* 注册观察者对象
* @param observer 观察者对象
*/
public void attach(Observer observer){
list.add(observer);
System.out.println("Attached an observer");
}
/**
* 删除观察者对象
* @param observer 观察者对象
*/
public void detach(Observer observer){
list.remove(observer);
}
/**
* 通知所有注册的观察者对象
*/
public void nodifyObservers(){
for(Observer observer : list){
observer.update(this);
}
}
}
拉模型的具体主题类
跟推模型相比,有一点变化,就是调用通知观察者的方法的时候,不需要传入参数了。
public class ConcreteSubject extends Subject{
private String state;
public String getState() {
return state;
}
public void change(String newState){
state = newState;
System.out.println("主题状态为:" + state);
//状态发生改变,通知各个观察者
this.nodifyObservers();
}
}
■ 推模型是假定主题对象知道观察者需要的数据;而拉模型是主题对象不知道观察者具体需要什么数据,没有办法的情况下,干脆把自身传递给观察者,让观察者自己去按需要取值。
■ 推模型可能会使得观察者对象难以复用,因为观察者的update()方法是按需要定义的参数,可能无法兼顾没有考虑到的使用情况。这就意味着出现新情况的时候,就可能提供新的update()方法,或者是干脆重新实现观察者;而拉模型就不会造成这样的情况,因为拉模型下,update()方法的参数是主题对象本身,这基本上是主题对象能传递的最大数据集合了,基本上可以适应各种情况的需要。
在JAVA语言的java.util库里面,提供了一个Observable类以及一个Observer接口,构成JAVA语言对观察者模式的支持。
这个接口只定义了一个方法,即update()方法,当被观察者对象的状态发生变化时,被观察者对象的notifyObservers()方法就会调用这一方法。
public interface Observer {
void update(Observable o, Object arg);
}
被观察者类都是java.util.Observable类的子类。java.util.Observable提供公开的方法支持观察者对象,这些方法中有两个对Observable的子类非常重要:一个是setChanged(),另一个是notifyObservers()。第一方法setChanged()被调用之后会设置一个内部标记变量,代表被观察者对象的状态发生了变化。第二个是notifyObservers(),这个方法被调用时,会调用所有登记过的观察者对象的update()方法,使这些观察者对象可以更新自己。
public class Observable {
private boolean changed = false;
private Vector obs;
/** Construct an Observable with zero Observers. */
public Observable() {
obs = new Vector();
}
/**
* 将一个观察者添加到观察者聚集上面
*/
public synchronized void addObserver(Observer o) {
if (o == null)
throw new NullPointerException();
if (!obs.contains(o)) {
obs.addElement(o);
}
}
/**
* 将一个观察者从观察者聚集上删除
*/
public synchronized void deleteObserver(Observer o) {
obs.removeElement(o);
}
public void notifyObservers() {
notifyObservers(null);
}
/**
* 如果本对象有变化(那时hasChanged 方法会返回true)
* 调用本方法通知所有登记的观察者,即调用它们的update()方法
* 传入this和arg作为参数
*/
public void notifyObservers(Object arg) {
Object[] arrLocal;
synchronized (this) {
if (!changed)
return;
arrLocal = obs.toArray();
clearChanged();
}
for (int i = arrLocal.length-1; i>=0; i--)
((Observer)arrLocal[i]).update(this, arg);
}
/**
* 将观察者聚集清空
*/
public synchronized void deleteObservers() {
obs.removeAllElements();
}
/**
* 将“已变化”设置为true
*/
protected synchronized void setChanged() {
changed = true;
}
/**
* 将“已变化”重置为false
*/
protected synchronized void clearChanged() {
changed = false;
}
/**
* 检测本对象是否已变化
*/
public synchronized boolean hasChanged() {
return changed;
}
/**
* Returns the number of observers of this Observable object.
*
* @return the number of observers of this object.
*/
public synchronized int countObservers() {
return obs.size();
}
}
这个类代表一个被观察者对象,有时称之为主题对象。一个被观察者对象可以有数个观察者对象,每个观察者对象都是实现Observer接口的对象。在被观察者发生变化时,会调用Observable的notifyObservers()方法,此方法调用所有的具体观察者的update()方法,从而使所有的观察者都被通知更新自己。
这里给出一个非常简单的例子,说明怎样使用JAVA所提供的对观察者模式的支持。在这个例子中,被观察对象叫做Watched;而观察者对象叫做Watcher。Watched对象继承自java.util.Observable类;而Watcher对象实现了java.util.Observer接口。另外有一个Test类扮演客户端角色。
源代码
被观察者Watched类源代码
public class Watched extends Observable{
private String data = "";
public String getData() {
return data;
}
public void setData(String data) {
if(!this.data.equals(data)){
this.data = data;
setChanged();
}
notifyObservers();
}
}
观察者类源代码
public class Watcher implements Observer{
public Watcher(Observable o){
o.addObserver(this);
}
@Override
public void update(Observable o, Object arg) {
System.out.println("状态发生改变:" + ((Watched)o).getData());
}
}
测试类源代码
public class Test {
public static void main(String[] args) {
//创建被观察者对象
Watched watched = new Watched();
//创建观察者对象,并将被观察者对象登记
Observer watcher = new Watcher(watched);
//给被观察者状态赋值
watched.setData("start");
watched.setData("run");
watched.setData("stop");
}
}
Test对象首先创建了Watched和Watcher对象。在创建Watcher对象时,将Watched对象作为参数传入;然后Test对象调用Watched对象的setData()方法,触发Watched对象的内部状态变化;Watched对象进而通知实现登记过的Watcher对象,也就是调用它的update()方法。
中介者模式用一个中介对象来封装一系列对象的交互,从而把一批原来可能是交互关系复杂的对象转换成一组松散耦合的中间对象,以有利于维护和修改。
当对象之间的交互操作很多且每个对象的行为操作都依赖彼此时,为防止在修改一个对象的行为时,同时涉及很多其他对象的行为,可使用中介者模式。
降低复杂度:将多对多依赖转化成了一对多,降低了类间耦合
类间各司其职,符合迪米特法则
当类变多了以后,中介者就会越臃肿,变得复杂且难以维护
抽象中介者(Mediator)角色: 它是中介者的接口,提供了同事对象注册与转发同事对象信息的抽象方法。
具体中介者(ConcreteMediator)角色: 实现中介者接口,定义一个 List 来管理同事对象,协调各个同事角色之间的交互关系,因此它依赖于同事角色。
抽象同事类(Colleague)角色: 定义同事类的接口,保存中介者对象,提供同事对象交互的抽象方法,实现所有相互影响的同事类的公共功能。
具体同事类(Concrete Colleague)角色: 是抽象同事类的实现者,当需要与其他同事对象交互时,由中介者对象负责后续的交互。
public interface Mediator {
// 定义一个发送消息的接口 得到同事对象和发送消息
void send(String message,Colleague colleague);
}
public class ConcreteMediator implements Mediator{
private ConcreteColleague1 concreteColleague1;
private ConcreteColleague2 concreteColleague2;
public void setConcreteColleague1(ConcreteColleague1 concreteColleague1) {
this.concreteColleague1 = concreteColleague1;
}
public void setConcreteColleague2(ConcreteColleague2 concreteColleague2) {
this.concreteColleague2 = concreteColleague2;
}
@Override
public void send(String message, Colleague colleague) {
if (colleague == concreteColleague1){
concreteColleague2.notify(message);
}else {
concreteColleague1.notify(message);
}
}
}
抽象同事类(Colleague)角色
public abstract class Colleague {
protected Mediator mediator;
// 构造方法得到中介者对象
public Colleague(Mediator mediator){
this.mediator = mediator;
}
//发送消息的抽象方法
abstract void send(String message);
//接收消息之后行为的抽象方法
abstract void notify(String message);
}
具体同事类(Concrete Colleague1)
public class ConcreteColleague1 extends Colleague{
public ConcreteColleague1(Mediator mediator) {
super(mediator);
}
@Override
void send(String message) {
mediator.send(message,this);
}
@Override
void notify(String message) {
System.out.println("同事1得到消息:"+message);
}
}
具体同事类(Concrete Colleague2)
public class ConcreteColleague2 extends Colleague{
public ConcreteColleague2(Mediator mediator) {
super(mediator);
}
@Override
void send(String message) {
mediator.send(message,this);
}
@Override
void notify(String message) {
System.out.println("同事2得到消息:"+message);
}
}
测试
public class Test {
public static void main(String[] args) {
ConcreteMediator concreteMediator = new ConcreteMediator();
//让两个具体同事类人数中介者对象
ConcreteColleague1 concreteColleague1 = new ConcreteColleague1(concreteMediator);
ConcreteColleague2 concreteColleague2 = new ConcreteColleague2(concreteMediator);
//让这接着认识各个具体同事类对象
concreteMediator.setConcreteColleague1(concreteColleague1);
concreteMediator.setConcreteColleague2(concreteColleague2);
//具体同事类对象的发送消息都是通过中介者发送
concreteColleague1.send("你在干什么?");
concreteColleague2.send("我不想告诉你!");
}
}
备忘录模式又叫做快照模式(Snapshot Pattern)或Token模式,是对象的行为模式。备忘录对象是一个用来存储另外一个对象内部状态的快照的对象。备忘录模式的用意是在不破坏封装的条件下,将一个对象的状态捕捉(Capture)住,并外部化,存储起来,从而可以在将来合适的时候把这个对象还原到存储起来的状态。备忘录模式常常与命令模式和迭代子模式一同使用。
备忘录模式的结构图如下所示
备忘录模式所涉及的角色有三个:备忘录(Memento)角色、发起人(Originator)角色、负责人(Caretaker)角色。
备忘录角色又如下责任:
(1)将发起人(Originator)对象的内战状态存储起来。备忘录可以根据发起人对象的判断来决定存储多少发起人(Originator)对象的内部状态。
(2)备忘录可以保护其内容不被发起人(Originator)对象之外的任何对象所读取。
备忘录有两个等效的接口:
● 窄接口:负责人(Caretaker)对象(和其他除发起人对象之外的任何对象)看到的是备忘录的窄接口(narrow interface),这个窄接口只允许它把备忘录对象传给其他的对象。
● 宽接口:与负责人对象看到的窄接口相反的是,发起人对象可以看到一个宽接口(wide interface),这个宽接口允许它读取所有的数据,以便根据这些数据恢复这个发起人对象的内部状态。
发起人角色有如下责任:
(1)创建一个含有当前的内部状态的备忘录对象。
(2)使用备忘录对象存储其内部状态。
负责人角色有如下责任:
(1)负责保存备忘录对象。
(2)不检查备忘录对象的内容。
备忘录角色对任何对象都提供一个接口,即宽接口,备忘录角色的内部所存储的状态就对所有对象公开。因此这个实现又叫做“白箱实现”。
“白箱”实现将发起人角色的状态存储在一个大家都看得到的地方,因此是破坏封装性的。但是通过程序员自律,同样可以在一定程度上实现模式的大部分用意。因此白箱实现仍然是有意义的。
下面给出一个示意性的“白箱实现”。
发起人角色类,发起人角色利用一个新创建的备忘录对象将自己的内部状态存储起来。
public class Originator {
private String state;
/**
* 工厂方法,返回一个新的备忘录对象
*/
public Memento createMemento(){
return new Memento(state);
}
/**
* 将发起人恢复到备忘录对象所记载的状态
*/
public void restoreMemento(Memento memento){
this.state = memento.getState();
}
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
System.out.println("当前状态:" + this.state);
}
}
备忘录角色类,备忘录对象将发起人对象传入的状态存储起来。
public class Memento {
private String state;
public Memento(String state){
this.state = state;
}
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
}
负责人角色类,负责人角色负责保存备忘录对象,但是从不修改(甚至不查看)备忘录对象的内容。
public class Caretaker {
private Memento memento;
/**
* 备忘录的取值方法
*/
public Memento retrieveMemento(){
return this.memento;
}
/**
* 备忘录的赋值方法
*/
public void saveMemento(Memento memento){
this.memento = memento;
}
}
客户端角色类
public class Client {
public static void main(String[] args) {
Originator o = new Originator();
Caretaker c = new Caretaker();
//改变负责人对象的状态
o.setState("On");
//创建备忘录对象,并将发起人对象的状态储存起来
c.saveMemento(o.createMemento());
//修改发起人的状态
o.setState("Off");
//恢复发起人对象的状态
o.restoreMemento(c.retrieveMemento());
System.out.println(o.getState());
}
}
在上面的这个示意性的客户端角色里面,首先将发起人对象的状态设置成“On”,并创建一个备忘录对象将这个状态存储起来;然后将发起人对象的状态改成“Off”;最后又将发起人对象恢复到备忘录对象所存储起来的状态,即“On”状态。
系统的时序图更能够反映出系统各个角色被调用的时间顺序。如下图是将发起人对象的状态存储到白箱备忘录对象中去的时序图。
可以看出系统运行的时序是这样的:
(1)将发起人对象的状态设置成“On”。
(2)调用发起人角色的createMemento()方法,创建一个备忘录对象将这个状态存储起来。
(3)将备忘录对象存储到负责人对象中去。
将发起人对象恢复到备忘录对象所记录的状态的时序图如下所示:
可以看出,将发起人对象恢复到备忘录对象所记录的状态时,系统的运行时序是这样的:
(1)将发起人状态设置成“Off”。
(2)将备忘录对象从负责人对象中取出。
(3)将发起人对象恢复到备忘录对象所存储起来的状态,即“On”状态。
备忘录角色对发起人(Originator)角色对象提供一个宽接口,而为其他对象提供一个窄接口。这样的实现叫做“黑箱实现”。
在JAVA语言中,实现双重接口的办法就是将备忘录角色类设计成发起人角色类的内部成员类。
将Memento设成Originator类的内部类,从而将Memento对象封装在Originator里面;在外部提供一个标识接口MementoIF给Caretaker以及其他对象。这样,Originator类看到的是Menmento的所有接口,而Caretaker以及其他对象看到的仅仅是标识接口MementoIF所暴露出来的接口。
使用内部类实现备忘录模式的类图如下所示。
发起人角色类Originator中定义了一个内部的Memento类。由于此Memento类的全部接口都是私有的,因此只有它自己和发起人类可以调用。
package memento.sample2;
/**
* @author chen_dz
* @date :2012-6-2 上午10:11:08
*/
public class Originator {
private String state;
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
System.out.println("赋值状态:" + state);
}
/**
* 工厂方法,返还一个新的备忘录对象
*/
public MementoIF createMemento(){
return new Memento(state);
}
/**
* 发起人恢复到备忘录对象记录的状态
*/
public void restoreMemento(MementoIF memento){
this.setState(((Memento)memento).getState());
}
private class Memento implements MementoIF{
private String state;
/**
* 构造方法
*/
private Memento(String state){
this.state = state;
}
private String getState() {
return state;
}
private void setState(String state) {
this.state = state;
}
}
}
窄接口MementoIF,这是一个标识接口,因此它没有定义出任何的方法。
public interface MementoIF {
}
负责人角色类Caretaker能够得到的备忘录对象是以MementoIF为接口的,由于这个接口仅仅是一个标识接口,因此负责人角色不可能改变这个备忘录对象的内容。
public class Caretaker {
private MementoIF memento;
/**
* 备忘录取值方法
*/
public MementoIF retrieveMemento(){
return memento;
}
/**
* 备忘录赋值方法
*/
public void saveMemento(MementoIF memento){
this.memento = memento;
}
}
客户端角色类
public class Client {
public static void main(String[] args) {
Originator o = new Originator();
Caretaker c = new Caretaker();
//改变负责人对象的状态
o.setState("On");
//创建备忘录对象,并将发起人对象的状态存储起来
c.saveMemento(o.createMemento());
//修改发起人对象的状态
o.setState("Off");
//恢复发起人对象的状态
o.restoreMemento(c.retrieveMemento());
}
}
客户端首先
(1)将发起人对象的状态设置为“On”。
(2)调用createMemento()方法,创建一个备忘录对象将这个状态存储起来(此时createMemento()方法还回的明显类型是MementoIF接口,真实类型为Originator内部的Memento对象)。
(3)将备忘录对象存储到负责人对象中去。由于负责人对象拿到的仅是MementoIF接口,因此无法读出备忘录对象内部的状态。
(4)将发起人对象的状态设置为“Off”。
(5)调用负责人对象的retrieveMemento()方法将备忘录对象取出。注意此时仅能得到MementoIF接口,因此无法读出此对象的内部状态。
(6)调用发起人对象的restoreMemento()方法将发起人对象的状态恢复成备忘录对象所存储的起来的状态,即“On”状态。由于发起人对象的内部类Memento实现了MementoIF接口,这个内部类是传入的备忘录对象的真实类型,因此发起人对象可以利用内部类Memento的私有接口读出此对象的内部状态。
前面所给出的白箱和黑箱的示意性实现都是只存储一个状态的简单实现,也可以叫做只有一个检查点。常见的系统往往需要存储不止一个状态,而是需要存储多个状态,或者叫做有多个检查点。
备忘录模式可以将发起人对象的状态存储到备忘录对象里面,备忘录模式可以将发起人对象恢复到备忘录对象所存储的某一个检查点上。下面给出一个示意性的、有多重检查点的备忘录模式的实现。
发起人角色源代码
public class Originator {
private List states;
//检查点指数
private int index;
/**
* 构造函数
*/
public Originator(){
states = new ArrayList();
index = 0;
}
/**
* 工厂方法,返还一个新的备忘录对象
*/
public Memento createMemento(){
return new Memento(states , index);
}
/**
* 将发起人恢复到备忘录对象记录的状态上
*/
public void restoreMemento(Memento memento){
states = memento.getStates();
index = memento.getIndex();
}
/**
* 状态的赋值方法
*/
public void setState(String state){
states.add(state);
index++;
}
/**
* 辅助方法,打印所有状态
*/
public void printStates(){
for(String state : states){
System.out.println(state);
}
}
}
备忘录角色类,这个实现可以存储任意多的状态,外界可以使用检查点指数index来取出检查点上的状态。
public class Memento {
private List states;
private int index;
/**
* 构造函数
*/
public Memento(List states , int index){
this.states = new ArrayList(states);
this.index = index;
}
public List getStates() {
return states;
}
public int getIndex() {
return index;
}
}
负责人角色类
public class Caretaker {
private Originator o;
private List mementos = new ArrayList();
private int current;
/**
* 构造函数
*/
public Caretaker(Originator o){
this.o = o;
current = 0;
}
/**
* 创建一个新的检查点
*/
public int createMemento(){
Memento memento = o.createMemento();
mementos.add(memento);
return current++;
}
/**
* 将发起人恢复到某个检查点
*/
public void restoreMemento(int index){
Memento memento = mementos.get(index);
o.restoreMemento(memento);
}
/**
* 将某个检查点删除
*/
public void removeMemento(int index){
mementos.remove(index);
}
}
客户端角色源代码
public class Client {
public static void main(String[] args) {
Originator o = new Originator();
Caretaker c = new Caretaker(o);
//改变状态
o.setState("state 0");
//建立一个检查点
c.createMemento();
//改变状态
o.setState("state 1");
//建立一个检查点
c.createMemento();
//改变状态
o.setState("state 2");
//建立一个检查点
c.createMemento();
//改变状态
o.setState("state 3");
//建立一个检查点
c.createMemento();
//打印出所有检查点
o.printStates();
System.out.println("-----------------恢复检查点-----------------");
//恢复到第二个检查点
c.restoreMemento(2);
//打印出所有检查点
o.printStates();
}
}
可以看出,客户端角色通过不断改变发起人角色的状态,并将之存储在备忘录里面。通过指明检查点指数可以将发起人角色恢复到相应的检查点所对应的状态上。
将发起人的状态存储到备忘录对象中的活动序列图如下:
系统运行的时序是这样的:
(1)将发起人对象的状态设置成某个有效状态;
(2)调用负责人角色的createMemento()方法,负责人角色会负责调用发起人角色和备忘录角色,将发起人对象的状态存储起来。
将发起人对象恢复到某一个备忘录对象的检查点的活动序列图如下:
由于负责人角色的功能被增强了,因此将发起人对象恢复到备忘录对象所记录的状态时,系统运行的时序被简化了:
(1)调用负责人角色的restoreMemento()方法,将发起人恢复到某个检查点。
所谓“自述历史”模式(History-On-Self Pattern)实际上就是备忘录模式的一个变种。在备忘录模式中,发起人(Originator)角色、负责人(Caretaker)角色和备忘录(Memento)角色都是独立的角色。虽然在实现上备忘录类可以成为发起人类的内部成员类,但是备忘录类仍然保持作为一个角色的独立意义。在“自述历史”模式里面,发起人角色自己兼任负责人角色。
“自述历史”模式的类图如下所示:
备忘录角色有如下责任:
(1)将发起人(Originator)对象的内部状态存储起来。
(2)备忘录可以保护其内容不被发起人(Originator)对象之外的任何对象所读取。
发起人角色有如下责任:
(1)创建一个含有它当前的内部状态的备忘录对象。
(2)使用备忘录对象存储其内部状态。
客户端角色有负责保存备忘录对象的责任。
窄接口MementoIF,这是一个标识接口,因此它没有定义出任何的方法。
public interface MementoIF {
}
发起人角色同时还兼任负责人角色,也就是说它自己负责保持自己的备忘录对象。
public class Originator {
public String state;
/**
* 改变状态
*/
public void changeState(String state){
this.state = state;
System.out.println("状态改变为:" + state);
}
/**
* 工厂方法,返还一个新的备忘录对象
*/
public Memento createMemento(){
return new Memento(this);
}
/**
* 将发起人恢复到备忘录对象所记录的状态上
*/
public void restoreMemento(MementoIF memento){
Memento m = (Memento)memento;
changeState(m.state);
}
private class Memento implements MementoIF{
private String state;
/**
* 构造方法
*/
private Memento(Originator o){
this.state = o.state;
}
private String getState() {
return state;
}
}
}
客户端角色类
public class Client {
public static void main(String[] args) {
Originator o = new Originator();
//修改状态
o.changeState("state 0");
//创建备忘录
MementoIF memento = o.createMemento();
//修改状态
o.changeState("state 1");
//按照备忘录恢复对象的状态
o.restoreMemento(memento);
}
}
解释器模式是指给定一门语言,定义它的文法的一种表示(如:加减乘除表达式和正则表达式等),然后再定义一个解释器,该解释器用来解释我们的文法表示(表达式)。
解释器模式的结构与组合模式相似,不过其包含的组成元素比组合模式多,而且组合模式是对象结构型模式,而解释器模式是类行为型模式。
解释器模式中包含四个角色:
抽象解释器(Abstract Expression)角色:定义解释器的接口,约定解释器的解释操作,主要包含解释方法 interpret()。
终结符解释器(Terminal Expression)角色:是抽象表达式的子类,用来实现文法中与终结符相关的操作,文法中的每一个终结符都有一个具体终结表达式与之相对应。
非终结符解释器(Nonterminal Expression)角色:也是抽象表达式的子类,用来实现文法中与非终结符相关的操作,文法中的每条规则都对应于一个非终结符表达式。
环境(Context)角色:通常包含各个解释器需要的数据或是公共的功能,一般用来传递被所有解释器共享的数据,后面的解释器可以从这里获取这些值。
解释器模式类结构图如图所示:
接下来针对四个角色分别定义他们的实现。
抽象解释器:
public abstract class AbstractExpression {
public abstract boolean interpret(String info);
}
非终结符表达式:
public class NonTerminalExpression extends AbstractExpression{
private AbstractExpression address=null;
private AbstractExpression name=null;
private AbstractExpression id=null;
public NonTerminalExpression(AbstractExpression address, AbstractExpression name, AbstractExpression id) {
this.address = address;
this.name = name;
this.id = id;
}
@Override
public boolean interpret(String info) {
String s[]=info.split("-");
return address.interpret(s[0])&&name.interpret(s[1])&&id.interpret(s[2]);
}
}
终结符表达式:
public class TerminalExpression extends AbstractExpression{
private Set set =new HashSet();
public TerminalExpression(String[] data)
{
for(int i=0; i
上下文环境:
public class Context {
private String[] shuzis={"1","2","3","4","5","6","7","8","9","0"};
private String[] xiaoxiezimus={"a","b","c","d","e","f","g","h","i","j","k","l"};
private String[] daxiezimus={"A","B","C","D","E","F","G"};
private AbstractExpression infomation;
public Context()
{
AbstractExpression shuzi=new TerminalExpression(shuzis);
AbstractExpression xiaoxiezimu=new TerminalExpression(xiaoxiezimus);
AbstractExpression daxiezimu=new TerminalExpression(daxiezimus);
infomation=new NonTerminalExpression(shuzi,xiaoxiezimu,daxiezimu);
}
public void jieshi(String info)
{
boolean ok=infomation.interpret(info);
if(ok) System.out.println("正确! ["+info+"] 满足 [单个数字-单个小写-单个大写] 的条件");
else System.out.println("错误! ["+info+"] 不满足 [单个数字-单个小写-单个大写] 的条件");
}
}
客户端:
public class Client {
public static void main(String[] args) {
Context people=new Context();
people.jieshi("2-a-A");
people.jieshi("11-A-5");
people.jieshi("你-好-吖");
people.jieshi("2aA");
}
}
解释器模式在实际的软件开发中使用比较少,因为它会引起效率、性能以及维护等问题。
在JDK中的正则表达式中的Pattern类和Spring里面的ExpressionParse接口使用的是解释器模式的思想。
当一个语言需要解释执行,并且语言中的句子可以表示为一个抽象语法树的时候,如 XML 文档解释,整体来说还是一种应用较少的设计模式。
最近发现项目中的判断太多,大量的if-else结构,想着重构下,于是接触到了状态模式。
这种设计模式就是java多态的体现,没有想象的那么神奇。
状态模式:当一个对象的内在状态改变时允许改变其行为,这个对象看起来像是改变了其类。
即
1、有一个对象,它是有状态的。
2、这个对象在状态不同的时候,行为不一样。
3、这些状态是可以切换的,而非毫无关系。
图中包含三个角色。
Context:它就是那个含有状态的对象,它可以处理一些请求,这些请求最终产生的响应会与状态相关。
State:状态接口,它定义了每一个状态的行为集合,这些行为会在Context中得以使用。
ConcreteState:具体状态,实现相关行为的具体状态类。
其他解释
环境(Context)角色,也成上下文:定义客户端所感兴趣的接口,并且保留一个具体状态类的实例。这个具体状态类的实例给出此环境对象的现有状态。
抽象状态(State)角色:定义一个接口,用以封装环境(Context)对象的一个特定的状态所对应的行为。
具体状态(ConcreteState)角色:每一个具体状态类都实现了环境(Context)的一个状态所对应的行为。
个人还是喜欢把环境角色context带入接口的方法参数中,这样逻辑更清楚;
另外的方式是把context放入各个具体状态角色的构造方法中,当实例化context的时候,通过具体状态的构造方法加载此context实例到具体状态角色中
举例一
本例子模拟购物的整个过程,包括下单,付款,收货,评价,退款,结束等。
状态接口
public interface Event {
//下单
void order(Shopping shopping,Boolean isConfirm) ;
//付款
void pay(Shopping shopping,Boolean isConfirm);
//签收
void receive(Shopping shopping,Boolean isConfirm);
//评价
void evaluate(Shopping shopping,Boolean isConfirm);
//订单结束
void finish(Shopping shopping,Boolean isConfirm);
//退款
void refund(Shopping shopping,Boolean isConfirm);
}
抽象类主要处理异常的情况,即用户重复提交某个状态时出现
public class AbstractEvent implements Event {
@Override
public void order(Shopping shopping, Boolean isConfirm) {
System.out.println("亲亲,下单异常");
}
@Override
public void pay(Shopping shopping, Boolean isConfirm) {
System.out.println("亲亲,支付异常");
}
@Override
public void receive(Shopping shopping, Boolean isConfirm) {
System.out.println("亲亲,接收异常");
}
@Override
public void evaluate(Shopping shopping, Boolean isConfirm) {
System.out.println("亲亲,评价异常");
}
@Override
public void finish(Shopping shopping, Boolean isConfirm) {
System.out.println("亲亲,结束操作流程错误");
}
@Override
public void refund(Shopping shopping, Boolean isConfirm) {
System.out.println("您的订单已经退款,请不要重复提交");
}
}
具体状态角色 - 下单
public class Order extends AbstractEvent {
@Override
public void order(Shopping shopping, Boolean isConfirm) {
if(isConfirm){
System.out.println("恭喜您,下单成功");
shopping.setEvent(Shopping.PAY_STATE);//确定下单,进入支付页面
}else {
System.out.println("订单页面取消成功,回到主页面");
shopping.setEvent(Shopping.REFUND);
}
}
}
具体状态角色 - 支付
public class Pay extends AbstractEvent {
@Override
public void pay(Shopping shopping, Boolean isConfirm) {
if(isConfirm){
System.out.println("恭喜您,支付成功");
shopping.setEvent(Shopping.RECEIVE_STATE);//进入收货阶段
}else {
System.out.println("支付页面,取消订单成功");
shopping.setEvent(Shopping.REFUND);
}
}
}
具体状态角色 - 接收
public class Receive extends AbstractEvent {
@Override
public void receive(Shopping shopping, Boolean isConfirm) {
if(isConfirm){
System.out.println("亲,签收物品成功,期待下次与您再次相见");
shopping.setEvent(Shopping.EVALUATE_STATE);
}else {
System.out.println("亲,签收阶段退款成功");
shopping.setEvent(Shopping.REFUND);
}
}
}
具体状态角色 - 评价
public class Evaluate extends AbstractEvent {
@Override
public void evaluate(Shopping shopping, Boolean isConfirm) {
if(isConfirm){
System.out.println("评价商品成功");
shopping.setEvent(Shopping.FINISH_STATE);
}else {
System.out.println("评价阶段,退款成功");
shopping.setEvent(Shopping.REFUND);
}
}
}
具体状态角色 - 完成
public class Finish extends AbstractEvent {
@Override
public void finish(Shopping shopping, Boolean isConfirm) {
if(isConfirm){
System.out.println("订单结束,感谢您的光临");
}else {
System.out.println("亲爱的客人,请您分享使用心得");
}
}
}
具体状态角色 - 退款
public class Refund extends AbstractEvent {
@Override
public void refund(Shopping shopping, Boolean isConfirm) {
if(isConfirm){
System.out.println("抱歉客官,给您带来不好的购物体验了,已为您退款,请见谅");
shopping.setEvent(Shopping.FINISH_STATE);
}
}
}
环境角色,有状态的对象 - 购物对象
public class Shopping {
public static final Event ORDER_STATE = new Order();
public static final Event PAY_STATE = new Pay();
public static final Event RECEIVE_STATE = new Receive();
public static final Event EVALUATE_STATE = new Evaluate();
public static final Event FINISH_STATE = new Finish();
public static final Event REFUND = new Refund();
//状态记录,默认进入下单状态
private Event event = ORDER_STATE;
public Event getEvent() {
return event;
}
public void setEvent(Event event) {
this.event = event;
}
public void startOrder(Boolean isConfirm){
event.order(this,isConfirm);
}
public void startPay(Boolean isConfirm){
event.pay(this,isConfirm);
}
public void startReceive(Boolean isConfirm){
event.receive(this,isConfirm);
}
public void startEvaluate(Boolean isConfirm){
event.evaluate(this,isConfirm);
}
public void startFinish(Boolean isConfirm){
event.finish(this,isConfirm);
}
public void startRefund(Boolean isConfirm){
event.refund(this,isConfirm);
}
}
客户启动段
public class Client {
public static void main(String[] args) {
Shopping shopping = new Shopping();
//下单成功
shopping.startOrder(true);
shopping.startOrder(true);
shopping.startOrder(true);
shopping.startOrder(true);
shopping.startOrder(true);
shopping.startPay(true);
shopping.startReceive(false);
shopping.startRefund(true);
shopping.startEvaluate(true);
shopping.startFinish(true);
}
}
举例二
理论基础自带
环境角色,包含状态实例
//英雄类
public class Hero {
public static final RunState COMMON = new CommonState();//正常状态
public static final RunState SPEED_UP = new SpeedUpState();//加速状态
public static final RunState SPEED_DOWN = new SpeedDownState();//减速状态
public static final RunState SWIM = new SwimState();//眩晕状态
private RunState state = COMMON;//默认是正常状态
private Thread runThread;//跑动线程
//设置状态
public void setState(RunState state) {
this.state = state;
}
//停止跑动
public void stopRun() {
if (isRunning()) runThread.interrupt();
System.out.println("--------------停止跑动---------------");
}
//开始跑动
public void startRun() {
if (isRunning()) {
return;
}
final Hero hero = this;
runThread = new Thread(new Runnable() {
public void run() {
while (!runThread.isInterrupted()) {
state.run(hero);
}
}
});
System.out.println("--------------开始跑动---------------");
runThread.start();
}
private boolean isRunning(){
return runThread != null && !runThread.isInterrupted();
}
}
接口定义
public interface RunState {
void run(Hero hero);
}
具体状态模式
正常模式
public class CommonState implements RunState{
public void run(Hero hero) {
//正常跑动则不打印内容
}
}
加速
public class SpeedUpState implements RunState{
public void run(Hero hero) {
System.out.println("--------------加速跑动---------------");
try {
Thread.sleep(4000);//假设加速持续4秒
} catch (InterruptedException e) {}
hero.setState(Hero.COMMON);
System.out.println("------加速状态结束,变为正常状态------");
}
}
减速
public class SpeedDownState implements RunState{
public void run(Hero hero) {
System.out.println("--------------减速跑动---------------");
try {
Thread.sleep(4000);//假设减速持续4秒
} catch (InterruptedException e) {}
hero.setState(Hero.COMMON);
System.out.println("------减速状态结束,变为正常状态------");
}
}
控制
public class SwimState implements RunState{
public void run(Hero hero) {
System.out.println("--------------不能跑动---------------");
try {
Thread.sleep(2000);//假设眩晕持续2秒
} catch (InterruptedException e) {}
hero.setState(Hero.COMMON);
System.out.println("------眩晕状态结束,变为正常状态------");
}
}
客户启动端
public class Client{
public static void main(String[] args) throws InterruptedException {
Hero hero = new Hero();
hero.startRun();
hero.setState(Hero.SPEED_UP);
Thread.sleep(5000);
hero.setState(Hero.SPEED_DOWN);
Thread.sleep(5000);
hero.setState(Hero.SWIM);
Thread.sleep(5000);
hero.stopRun();
}
}
状态模式解决的问题:状态模式主要解决的是当控制一个对象状态的条件表达式过于复杂时的情况。把状态的判断逻辑转移到表示不同状态的一系列类中,可以把复杂的判断逻辑简化。
状态模式优点:
一、我们去掉了if else结构,使得代码的可维护性更强,不易出错,这个优点挺明显,如果试图让你更改跑动的方法,是刚才的一堆if else好改,还是分成了若干个具体的状态类好改呢?答案是显而易见的。
二、使用多态代替了条件判断,这样我们代码的扩展性更强,比如要增加一些状态,假设有加速20%,加速10%,减速10%等等等,会非常的容易。
三、状态是可以被共享的,这个在上面的例子当中有体现,看下Hero类当中的四个static final变量就知道了,因为状态类一般是没有自己的内部状态的,所有它只是一个具有行为的对象,因此是可以被共享的。
四、状态的转换更加简单安全,简单体现在状态的分割,因为我们把一堆if else分割成了若干个代码段分别放在几个具体的状态类当中,所以转换起来当然更简单,而且每次转换的时候我们只需要关注一个固定的状态到其他状态的转换。安全体现在类型安全,我们设置上下文的状态时,必须是状态接口的实现类,而不是原本的一个整数,这可以杜绝魔数以及不正确的状态码。
状态模式适用于某一个对象的行为取决于该对象的状态,并且该对象的状态会在运行时转换,又或者有很多的if else判断,而这些判断只是因为状态不同而不断的切换行为。
状态模式缺点:
1、会增加的类的数量。
2、使系统的复杂性增加。
尽管状态模式有着这样的缺点,但是往往我们牺牲复杂性去换取的高可维护性和扩展性是相当值得的,除非增加了复杂性以后,对于后者的提升会乎其微。
状态模式在项目当中也算是较经常会碰到的一个设计模式,但是通常情况下,我们还是在看到if else的情况下,对项目进行重构时使用,又或者你十分确定要做的项目会朝着状态模式发展,一般情况下,还是不建议在项目的初期使用。
一、策略模式的简介
策略模式(Strategy Mode)是一种行为型设计模式,它定义了算法家族,分别的封装起来,让它们之间可以相互替换;此模式让算法的变化,不会影响到使用算法的客户;这样做的好处是,当我们需要增加新的算法时,只需要添加一个新的算法类即可,而不需要修改原有的代码。
二、策略模式的概念
它定义了算法家族,分别封装起来,让它们之间可以相互替换。
三、策略模式的作用
策略模式的主要作用就是将算法的实现和客户端使用算法分离开来,使得算法变化而不影响客户端的代码;
策略模式的作用有以下几个方面:
通过定义一系列算法类并封装到具有共同接口的抽象类中,我们可以在不修改原有代码的情况下轻松地切换算法,从而满足不同的业务需求;
在没有使用策略模式的情况下,往往需要使用大量的条件语句来实现不同的业务逻辑,这样会使得代码变得复杂难以维护。而使用策略模式可以将不同的业务逻辑封装到不同的算法类中,从而避免了大量的条件语句;
由于每个算法类都是独立的,因此可以在其他项目中重用这些算法类,从而提高代码的复用性;
策略模式将算法的实现与使用算法的客户端代码分离开来,从而降低了它们之间的耦合度;
四、策略模式的优、缺点
策略模式的优点:
可以方便地切换算法
可以避免大量的条件语句
可以提高代码的复用性
可以降低耦合度
策略模式的缺点:
策略模式的类过多:每次具体策略都要对应一个策略类,也就造成了类的数量增加,从而导致“类爆炸”;
客户端必须了解所有的策略类:客户端使用策略类时,必须知道这个策略类的作用;
策略模式的对象数量过多:每个策略类都是一个对象,当系统中存在大量的策略类时,会增加对象的数量,从而占用更多的内存空间;
五、策略模式应用场景
策略模式应用场景包括以下几个
多选一的时候
游戏角色:魔法师、大剑士、武士等等
算法:加减乘除什么的
还有一些就不一一写出来了
六、代码案例
注意:这里以商场打折、满减等为案例
1)抽象接口类
// 抽象策略类
public interface DiscountStrategy {
double calculateDiscount(double price);
}
2)具体的策略类:减
// 具体策略类:满减
public class FullReduction implements DiscountStrategy {
public double calculateDiscount(double price) {
if (price >= 100) {
return price - 20;
}
return price;
}
}
3)具体策略类:打折
// 具体策略类:打折
public class Discount implements DiscountStrategy {
public double calculateDiscount(double price) {
return price * 0.8;
}
}
4)上下文类
// 上下文类
public class Context {
private DiscountStrategy discountStrategy;
public void setDiscountStrategy(DiscountStrategy discountStrategy) {
this.discountStrategy = discountStrategy;
}
public double calculatePrice(double price) {
return discountStrategy.calculateDiscount(price);
}
}
5)客户端Main代码测试
// 客户端代码
public class StrategyPatternDemo {
public static void main(String[] args) {
Context context = new Context();
// 使用满减策略
context.setDiscountStrategy(new FullReduction());
System.out.println("使用满减策略,总价为:" + context.calculatePrice(120));
// 使用打折策略
context.setDiscountStrategy(new Discount());
System.out.println("使用打折策略,总价为:" + context.calculatePrice(120));
}
}
测试结果:
使用满减策略,总价为:100.0
使用打折策略,总价为:96.0
总结:
策略模式是一种非常灵活和可扩展的设计模式,可以帮助我们更好地应对需求变化和提高代码质量
责任链模式:Chain of Responsibility Patten 。就是将链中的每一个结点看做是一个对象,每个结点处理请求均不同,且内部自动维护一个下一个结点对象。当请求从链条的首端出发时,会沿着链的路径依次传递给每一个结点的对象,直到有对象处理这个请求为止。就是说每个结点会处理一件事情,如果结点间出现异常,那么链路就会中断。一般比如说一个请假需要多个负责任审批,过五关斩六将等这些,都是责任链模式。
就拿这个springMvc的执行流程来说,全部流程就组成了一个链条。每一个步骤就是一个结点,每个结点都会有对应的处理方法,每个结点处理完成之后,就会进入下一个结点。一旦某个结点出现异常,那么当前的链路就会停止,当前请求中断。
mybatis的执行流程也是通过这个责任链模式,如首先会创建这个SqlSessionFactory,然后通过这个工厂创建一个SqlSession,这个SqlSession只是一个门面模式,会通过Executer执行增删改查的操作,然后一个Handler用于设置参数,一个Handler用于返回结果集,最后通过这个StatementHandler将执行结果获取。里面的整个步骤就相当于形成了一个链条,执行完当前结点就会进入下一个结点,如果出现异常,链条终止往下执行。
spring里面的这个过滤器链路的调用,以及拦截器的链路调用,也是采用这种责任链模式
sentinel里面的每一个规则对应一个槽点,如流控规则,授权规则,熔断规则,热点规则,系统规则等。里面也是利用这个责任链模式,每个插槽对应一个规则,每个规则处理一个事件。如果出现异常,那么就会进行对应的限流降级。
aop依赖与ioc,在生产bean并进行实例化之前,先通过bean的第一个后置处理器找到所有在类上面加@AspectJ这个注解的所有类,并在这个类的里面找到所有的befeore,after等注解的方法,每一个before,after等都会生成一个对应的advisor,每个advisor包括advise和pointcut,advise主要是用来作为一个增强器的使用,pointcut是为了进行匹配,匹配成功才进行最终的动态代理的生成。最后获取到所有的advisors,由于可能有大量的advisor,因此在bean的最后一个后置处理器才对这些所有的advisor进行处理,即在bean进行初始化之后才进行处理。最后会去循环遍历这些advisors,通过advisors里面封装的pointcut和生成的advisor进行比较,如果匹配成功,则说明bean需要创建动态代理。主要是通过责任链的方式实现。
假设有一个学校有一个采购审批的需要,采购项目需要给领导审批,不同金钱范围,对应的审批领导的等级不同,如下:
1,金额小于5000,由教学主任审批
2,金额小于等于5000,由院长审批
3,金额小于等于30000,由副校长任审批
4,金额大于30000,由校长审批
1,接下来进入这个编码环节。首先定义一个实体类ApproverRequest
public class ApproverRequest {
private int type = 0; //请求类型
private float price = 0.0f; //请求金额
private int id = 0;
//构造器
public ApproverRequest(int type, float price, int id) {
this.type = type;
this.price = price;
this.id = id;
}
public int getType() { return type; }
public float getPrice() { return price; }
public int getId() { return id; }
}
2,在写一个抽象类,用于定义全局,作为子类的规范。链条中所有的结点都需要继承子类,实现子类里面的抽象方法
public abstract class Approver {
//下一个调用者
Approver next ;
//需要传入的名字
String name ;
public Approver(String name){
this.name = name;
}
//设置下一个调用者
public void setNext(Approver next) {
this.next = next;
}
public abstract void processApprover(ApproveRequest approveRequest);
}
3,然后开始写一个链条中的第一个结点,由教学主任负责审批。如果金额太大,教学主任审批不了,那么就由这个院长审批
public class DepartmentApprover extends Approver {
public DepartmentApprover(String name) { super(name); }
@Override
public void processRequest(ApproveRequest approveRequest) {
if(approveRequest.getPrice() <= 5000) {
System.out.println(" 请求编号 id= " + approveRequest.getId() + " 被 " + this.name + " 处理");
}else {
approver.processRequest(approveRequest);
}
}
}
4,然后开始写一个链条中的第二个结点,由院长负责审批。如果金额太大,院长审批不了,那么就由这个副校长审批
public class CollegeApprover extends Approver {
public CollegeApprover(String name) {super(name); }
@Override
public void processRequest(ApproveRequest approveRequest) {
if(approveRequest.getPrice() < 5000 && approveRequest.getPrice() <= 10000) {
System.out.println(" 请求编号 id= " + approveRequest.getId() + " 被 " + this.name + " 处理");
}else {
approver.processRequest(purchaseRequest);
}
}
}
5,然后开始写一个链条中的第四个结点,由副长负责审批。
public class SchoolMasterApprover extends Approver {
public SchoolMasterApprover(String name) {super(name); }
@Override
public void processRequest(ApproveRequest approveRequest) {
if(approveRequest.getPrice() > 30000) {
System.out.println(" 请求编号 id= " + approveRequest.getId() + " 被 " + this.name + " 处理");
}else {
approver.processRequest(purchaseRequest);
}
}
}
6,最后写一个测试类
public class Test {
public static void main(String[] args) {
//创建一个请求
ApproveRequest approveRequest = new ApproveRequest(1, 31000, 1);
//创建相关的审批人
DepartmentApprover departmentApprover = new DepartmentApprover("张主任");
CollegeApprover collegeApprover = new CollegeApprover("李院长");
ViceSchoolMasterApprover viceSchoolMasterApprover = new ViceSchoolMasterApprover("王副校");
SchoolMasterApprover schoolMasterApprover = new SchoolMasterApprover("佟校长");
//需要将各个审批级别的下一个设置好
departmentApprover.setApprover(collegeApprover);
collegeApprover.setApprover(viceSchoolMasterApprover);
viceSchoolMasterApprover.setApprover(schoolMasterApprover);
//单向责任链这里可以不加
schoolMasterApprover.setApprover(departmentApprover);
departmentApprover.processRequest(approveRequest);
viceSchoolMasterApprover.processRequest(approveRequest);
}
}
1、将请求和处理分开,实现解耦,提高代码灵活性
2、简化对象、使对象不用知道链结构
3、性能可能受到影响,尤其链比较长的时候,要设置链的最大节点数