该笔记由本人整理的,大部分是基于《设计模式之禅》阅读整理的,纯在个人偏差理解,感谢观看,望考试顺利,事事顺心。
设计模式
GoF23种设计模式可分为三大类:
-
创建型(5个):解决对象创建问题。
-
单例模式
-
工厂方法模式
-
抽象工厂模式
-
建造者模式
-
原型模式
-
-
结构型(7个):一些类或对象组合在一起的经典结构。就是类和对象组成一个更大的结构,去解决某一个问题
-
代理模式
-
装饰模式
-
适配器模式
-
组合模式
-
享元模式
-
外观模式
-
桥接模式
-
-
行为型(11个):解决类或对象之间的交互问题。比如你对象A去调用对象B,对象B去调用对象C
-
策略模式(一个接口下有多个实现类)
-
模板方法模式(servlet中学过)
-
责任链模式(servlet过滤器中学过)
-
观察者模式
-
迭代子模式
-
命令模式
-
备忘录模式
-
状态模式
-
访问者模式
-
中介者模式
-
解释器模式
-
一、单一职责原则
单一职责原则,Single Responsibility Principle,简称SRP。
1.1 引入
只要做过项目,肯定要接触到用户、机构、角色管理这些模块,基本上使用的都是RBAC模型(Role-Based Access Control,基于角色的访问控制,通过分配和取消角色来完成用户权限的授予和取消,使动作主体(用户)与资源的行为(权限)分离),确实是一个很好的解决办法。我们这里要讲的是用户管理、修改用户的信息、增加机构(一个人属于多个机构)、增加角色等,用户有这么多的信息和行为要维护,我们就把这些写到一个接口中,都是用户管理类嘛,我们先来看它的类图。
我相信,即使是谁都能看出这个接口设计得有问题,用户的属性和用户的行为没有分开,应该把用户的信息抽取成一个BO(Business Object,业务对象),把行为抽取成一个Biz(Business Logic,业务逻辑)。
重新拆封成两个接口,IUserBO负责用户的属性,简单地说,IUserBO的职责就是收集和反馈用户的属性信息;IUserBiz负责用户的行为,完成用户信息的维护和变更。这个与我实际工作中用到的User类还是有差别的!别着急,我们先来看一看分拆成两个接口怎么使用。我们现在是面向接口编程,所以产生了这个UserInfo对象之后,可以通过转型,把它当IUserBO接口使用,也可以当IUserBiz接口使用。比如,纯粹要获得用户信息,就当是IUserBO的实现类,转型;要是希望维护用户的信息,就把它当作IUserBiz的实现类就成了,如代码清单1-1所示。
......
IUserInfo userInfo = new UserInfo();
//我要赋值了,我就认为它是一个纯粹的BO
IUserBO userBO = (IUserBO)userInfo;
userBO.setPassword("abc");
//我要执行动作了,我就认为是一个业务逻辑类
IUserBiz userBiz = (IUserBiz)userInfo;
userBiz.deleteUser();
......
但是我们来分析一下刚才的动作,为什么要把一个接口拆分成两个呢?其实,在实际的使用中,我们更倾向于使用两个不同的类或接口:一个是IUserBO,一个是IUserBiz,类图如图1-3所示。
以上我们把一个接口拆分成两个接口的动作,就是依赖了单一职责原则。
-
那什么是单一职责原则呢?
应该有且仅有一个原因引起类的变更。(万变不离其宗,唯一真理)
1.2 核心
There should never be more than one reason for a class to change
应该有且仅有一个原因引起类的变更
1.2.1 示例1
电话通话的时候有4个过程发生:拨号、通话、回应、挂机,那我们写一个接口,其类图如图1-4所示
public interface IPhone {
//拨通电话
public void dial(String phoneNumber);
//通话
public void chat(Object o);
//通话完毕,挂电话
public void hangup();
}
分析:
这个接口其实从平时生产环境中使用没有什么问题,但确实是不符合单一职责原则。因为它包含了两个职责:一个是协议管理(拨通电话、挂电话方法),一个是数据传送(通话方法)。协议接通的变化,会导致协议管理的实现变化、数据传送的变化(如电话不仅仅可以通话,还可以上网)会有导致这个接口或实现类的变化。这里有两个原因都引起了类的变化。这两个职责会相互影响吗?电话拨号,我只要能接通就成,甭管是电信的还是网通的协议;电话连接后还关心传递的是什么数据吗?通过这样的分析,我们发现类图上的IPhone接口包含了两个职责,而且这两个职责的变化不相互影响,那就考虑拆分成两个接口。
1.2.2 示例2
单一职责适用于接口、类,同时也适用于方法,什么意思呢?一个方法尽可能做一件事情,比如一个方法修改用户密码.
不好的设计:一个方法承担了很大职责,接收可变长度的参数,将参数修改到userBO这个对象上
好的设计:
通过类图可知,如果要修改用户名称,就调用changeUserName方法;
要修改家庭地址,就调用changeHomeAddress方法;
要修改单位电话,就调用changeOfficeTel方法。
每个方法的职责非常清晰明确,不仅开发简单,而且日后的维护也非常容易。
1.3 优点
● 类的复杂性降低,实现什么职责都有清晰明确的定义
● 可读性提高,复杂性降低,那当然可读性提高了
● 可维护性提高,可读性提高,那当然更容易维护了
● 变更引起的风险降低,变更是必不可少的,如果接口的单一职责做得好,一个接口修改只对相应的实现类有影响,对其他的接口无影响,这对系统的扩展性、维护性都有非常大的帮助。
注意 单一职责原则提出了一个编写程序的标准,用“职责”或“变化原因”来衡量接口或类设计得是否优良,但是“职责”和“变化原因”都是不可度量的,因项目而异,因环境而异。因此开头设计一个IPhone接口也可能是没有错的。但是,如果纯从“学究”理论上分析就有问题了。而且还要去考虑项目工期、成本、人员技术水平、硬件情况、网络情况。所以一般情况下单一职责原则是很难在实际项目中见到的。
二、李氏替换原则
李氏替换原则,Liskov Substitution Principle,简称LSP。
定义1: 如果对每一个类型为T1的对象O1,都有类型为T2的对象O2,使得所有以T1定义的所有程序P在所有的对象O1都替换成O2时,程序P的行为没有发生任何变化,那么类型T2是类型T1的子类型。
定义2:所有引用基类的地方必须能透明地使用其子类的对象。(即在使用父类的地方,将父类对象改写为子类对象时,不会对程序造成影响,输出结果不变)
以下归纳出的要求不过就是围绕这个展开的,就是各种情况下满足该定义如何实现。
为什么要使用李氏替换原则?
-
继承关系给程序带来侵入性(父类修改,子类都需要相应的改变)
-
保证程序升级后的兼容性,避免程序出错
如何规范地遵循里氏替换原则:
-
子类必须完全实现父类的抽象方法,但不能覆盖父类的非抽象方法
-
子类可以实现自己特有的方法
-
当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格。即返回值可以被缩小,不能被放大。
-
子类的实例可以替代任何父类的实例,但反之不成立
2.1 四点要求
2.1.1 子类必须完全实现父类的方法
例如,在设计游戏中,抽象出以下对象
我们来看以下士兵类的实现,在射击之前,会先输出相关的“开始杀人”提示
//士兵类实现
public class Soldier {
//定义士兵的枪支
private AbstractGun gun;
//给士兵一支枪
public void setGun(AbstractGun _gun){
this.gun = _gun;
}
public void killEnemy(){
System.out.println("士兵开始杀敌人..."); //重点!
gun.shoot();
}
}
//场景类
public class Client {
public static void main(String[] args) {
//产生三毛这个士兵
Soldier sanMao = new Soldier();
//给三毛一支枪
sanMao.setGun(new Rifle());
sanMao.killEnemy();
}
}
此时,再来了一个枪支实现类,是玩具枪,按照一般的思路,应该是继承抽象的枪支类,但由于玩具枪无法射击,所以就对射击方法进行空实现。那就没有问题了吗?
场景类:
public class Client {
public static void main(String[] args) {
//产生三毛这个士兵
Soldier sanMao = new Soldier();
sanMao.setGun(new ToyGun());
sanMao.killEnemy();
}
}
//结果输出:
士兵开始杀人...
-
这里士兵使用了玩具枪,玩具枪无法射击杀人,却还是输出了不合理的结果,为什么?因为虽然我们进行了空实现,但在士兵类中,调用射击方法之前,会输出相关射击方法相关的前置绑定的一些内容,如输出“士兵开始杀人了”。
-
怎么办?在士兵类的KillEnemy方法中加入判断,如果调用的具体枪支类instanceof是玩具枪类,就不输出这句话?这并不可行,因为士兵类下可能还有继承的类,这样修改父类,会导致所有的子类都会受到影响。所以回到开头的玩具枪类,有时候为了美观上的继承,如玩具枪类就该继承枪支类,对射击进行空实现,并不能完全解决问题,甚至会引来一些新的问题。怎么解决?用依赖关系来替代继承,类图如下。
-
上面的例子中,玩具枪类不能完整实现枪支抽象类的方法
即,子类不能完整实现父类的方法,或者或者父类的某些方法在子类中已经发生“畸变”,则建议断开父子继承关系,采用依赖、聚集、组合等关系代替继承。这就是李氏替换原则的第一点。子类必须完全实现父类的方法,否则不用继承关系。
另外,不能覆盖父类的非抽象方法。因为非抽象方法就是为了进行代码复用的,如果子类修改了,那么使用的时候,子类使用该方法时,就达不到父类原方法的效果。如果还有子子类继承了该子类,原本是为了使用祖宗类的该方法,结果其父类修改了,导致到不到指定的方法效果。导致后期运维等造成一定的麻烦。所以说不能覆盖父类的非抽象方法。
2.1.2 子类可以有自己的个性
即子类可以有自己特有的属性以及方法,这是李氏替换原则的解析内容之一,但该原则不能反过来用,即子类出现的地方,父类未必就可以胜任。 即,正着用:引用基类的地方必须能透明地使用其子类的对象。但反过来缺不行,即使用子类的地方,不一定能换成基类。
为什么反过来不行?
-
因为比如向上转型,转型后,如果调用的是子类中特有的方法,那么在静态绑定的时候,会去找转型后的即父类中找该方法,找不到,静态绑定时直接报错。不使用向上转型,在子类调用其特有方法处,直接将子类对象换成父类,父类中没有该特有方法,直接报错。
2.1.3 覆盖或实现父类的方法时,输出参数可以被放大
前置条件和后置条件,前置条件就是你要让我执行,就必须满足我的条件;后置条件就是我执行完了需要反馈,标准是什么。可以简单理解成,前置条件就是方法参数列表,后置条件就是返回值。
如果要满足李氏替换原则,使用父类的地方,将父类对象换成子类,对程序的结果不造成影响。那么覆盖或实现父类的方法时,输出参数可以被放大,不能被缩小,否则可能出现违背李氏替换原则的情况。
示例:参数类型放大的情况
//父类
public class Father {
public Collection doSomething(HashMap map){
System.out.println("父类被执行...");
return map.values();
}
}
//子类
public class Son extends Father {
//放大输入参数类型,这里的方法重载,不是重写,因为参数类型不一样
public Collection doSomething(Map map){
System.out.println("子类被执行...");
return map.values();
}
}
//场景类
public class Client {
public static void invoker(){
//父类存在的地方,子类就应该能够存在
Father f = new Father(); //替换处
HashMap map = new HashMap();
f.doSomething(map);
}
public static void main(String[] args) {
invoker();
}
}
-
替换处,父类对象直接换成子类new Son,不会对程序造成任何影响,输入的内容都行相同的,输出“父类被执行”,调用的仍是父类的方法。因为子类是重载,而且方法参数的类型放大了,就算父类处换成了子类对象(父类要执行,那么传入的参数一定的小类型的),那么优先匹配到的仍是继承过来的父类方法,即小类型参数的方法。
示例:参数类型放小的情况
//父类
public class Father {
public Collection doSomething(Map map){//参数类型大
System.out.println("父类被执行...");
return map.values();
}
}
public class Son extends Father {
//缩小输入参数范围
public Collection doSomething(HashMap map){//参数类型小
System.out.println("子类被执行...");
return map.values();
}
}
public class Client {
public static void invoker(){
//有父类的地方就有子类
Father f= new Father();
HashMap map = new HashMap();//传入小类型的参数
f.doSomething(map);
}
public static void main(String[] args) {
invoker();
}
}
-
当父类调用时,传入的是比其方法参数小类型的参数时,输出的是“父类被执行了”。此时,如果将父类对象换成子类对象,那么输出变成了“子类被调用”,结果改变了,违背了李氏替换原则。为什么?因为替换对象后,由于传入的是小类型的参数,此时子类会优先匹配到子类负载的参数类型放小的方法,此时不会去执行父类继承过来的方法,违背了原则。所以说,如果传入的参数是父类型的参数,即new Map()作为参数,是没问题的。所以说是参数类型变小,是可能违背李氏替换原则,放大永远不会违背。
2.1.4 覆写或实现父类的方法时,输出结果可以被缩小
这是什么意思呢,父类的一个方法的返回值是一个类型T,子类的相同方法(重载或覆写)的返回值为S,那么里氏替换原则就要求S必须小于等于T,也就是说,要么S和T是同一个类型,要么S是T的子类。
为什么呢?分两种情况,如果是覆写,父类和子类的同名方法的输入参数是相同的,两个方法的范围值S小于等于T,否则编译时直接报错,都不用看是否父类处可以无伤换成子类。如果是重载,则要求方法的输入参数类型或数量不相同,在里氏替换原则要求下,就是子类的输入参数宽于或等于父类的输入参数,也就是说你写的这个方法是不会被调用的,在父类使用处换成子类对象时,才不会调用的是子类的方法,导致违背原则,结果不一样了。参考上面讲的前置条件的例子。
2.2 优点
-
约束继承泛滥,它也是开闭原则的一种很好的体现。
-
提高了代码的重用性。
-
降低了系统的出错率。类的扩展不会给原类造成影响,降低了代码出错的范围和系统出错的概率。
-
加强程序的健壮性,同时变更时可以做到非常好的兼容性,提高程序的维护性、可扩展性,降低需求变更时引入的风险。
三、依赖倒置原则
简称DIP,就是面向接口编程,接口引用指向具体的实现类,或者在高层模块中,不存在创建子类对象的new代码,而是通过构造注入或者set注入进行接口引用的赋值,如spring容器的依赖注入就是最典型的依赖倒置。
四、接口隔离原则
4.1 概念
什么是接口隔离原则?
-
客户端不应该依赖它不需要的接口
-
类间的依赖关系应该建立在最小的接口上
两个定义剖析一下,先说第一种定义:“客户端不应该依赖它不需要的接口”,那依赖什么?依赖它需要的接口,客户端需要什么接口就提供什么接口,把不需要的接口剔除掉,那就需要对接口进行细化,保证其纯洁性;再看第二种定义:“类间的依赖关系应该建立在最小的接口上”,它要求是最小的接口,也是要求接口细化,接口纯洁,与第一个定义如出一辙,只是一个事物的两种不同描述。
简单说,就是接口中的方法要尽量少,不要建立臃肿的接口,拆开接口分多个,要进行细化,增加灵活性。
注意:根据接口隔离原则拆分接口时,首先必须满足单一职责原则,即不能无限拆分,过细了。
五、迪米特法则
简称LoD,也称为最少知识原则LKP。虽然名字不同,但描述的是同一个规则:一个对象应该对其他对象有最少的了解。通俗地讲,一个类应该对自己需要耦合或调用的类知道得最少,你(被耦合或调用的类)的内部是如何复杂都和我没关系,那是你的事情,我就知道你提供的这么多public方法,我就调用这么多,其他的我一概不关心。
5.1 要点解析
5.1.1 只和朋友交流
每个对象都必然会与其他对象有耦合关系,两个对象之间的耦合就成为朋友关系,这种关系的类型有很多,例如组合、聚合、依赖等。 朋友类的定义是这样的:出现在成员变量、方法的输入输出参数中的类称为成员朋友类,而出现在方法体内部的类不属于朋友类,
上代码,老师让体育委员清点女学生人数
代码1
//老师类
public class Teacher {
//老师对学生发布命令,清一下女生
public void commond(GroupLeader groupLeader){
List listGirls = new ArrayList();
//初始化女生
for(int i=0;i<20;i++){
listGirls.add(new Girl());
}
//告诉体育委员开始执行清查任务
groupLeader.countGirls(listGirls);
}
}
//体育委员类
public class GroupLeader {
//清查女生数量
public void countGirls(List<Girl> listGirls){
System.out.println("女生数量是:"+listGirls.size());
}
}
//女生类
public class Girl {
}
//场景类
public class Client {
public static void main(String[] args) {
Teacher teacher= new Teacher();
//老师发布命令
teacher.commond(new GroupLeader());
}
}
//运行结果:输出“女生数量是:20”
-
该代码设计,违背了迪米特法则。因为女生类不是老师类的朋友类,但老师类和女生类存在依赖,即女生类不是老师类的成员变量和方法参数,缺在其方法中声明了女生类的数组,new创建了女生类,即和女生类不是朋友类,缺依赖了该女生类,破坏了老师类的健壮性。方法是类的一个行为,类竟然不知道自己的行为与其他类产生依赖关系,这是不允许的,严重违反了迪米特法则。
修改为代码2
代码2
类图修改,老师类不依赖女生类
public class Teacher {
//老师对学生发布命令,清一下女生
public void commond(GroupLeader groupLeader){
//告诉体育委员开始执行清查任务
groupLeader.countGirls();
}
}
public class GroupLeader {
private List<Girl> listGirls;
//传递全班的女生进来
public GroupLeader(List<Girl> _listGirls){
this.listGirls = _listGirls;
}
//清查女生数量
public void countGirls(){
System.out.println("女生数量是:"+this.listGirls.size());
}
}
public class Client {
public static void main(String[] args) {
//产生一个女生群体
List<Girl> listGirls = new ArrayList<Girl>();
//初始化女生
for(int i=0;i<20;i++){
listGirls.add(new Girl());
}
Teacher teacher= new Teacher();
//老师发布命令
teacher.commond(new GroupLeader(listGirls));
}
}
-
把Teacher中对List<Girl>的初始化移动到了场景类中,同时在GroupLeader中增加了对Girl的注入,避开了Teacher类对陌生类Girl的访问,降低了系统间的耦合,提高了系统的健壮性。
注意 一个类只和朋友交流,不与陌生类交流,不要出现getA().getB().getC().getD()这种情况(在一种极端的情况下允许出现这种访问,即每一个点号后面的返回类型都相同),类与类之间的关系是建立在类间的,而不是方法间,因此一个方法尽量不引入一个类中不存在的对象,当然,JDK API提供的类除外。
5.1.2 朋友间也是有距离的
即,两个类虽然是朋友类,但对方法进行一些组合操作,不能在朋友类中,而是在自己类中进行组合调用操作。
上代码,场景:安装软件
代码1
//导向类
public class Wizard {
private Random rand = new Random(System.currentTimeMillis());
//第一步
public int first(){
System.out.println("执行第一个方法...");
return rand.nextInt(100);//返回随机数
}
//第二步
public int second(){
System.out.println("执行第二个方法...");
return rand.nextInt(100);
}
//第三个方法
public int third(){
System.out.println("执行第三个方法...");
return rand.nextInt(100);
}
}
//InstallSoftware类
public class InstallSoftware {
public void installWizard(Wizard wizard){//存在依赖关系,即InstallSoftware和导向类是朋友类
int first = wizard.first();
//根据first返回的随机数结果,看是否需要执行下一步second,模拟用户的选择操作
if(first>50){
int second = wizard.second();
if(second>50){
int third = wizard.third();
if(third >50){
wizard.first();
}
}
}
}
}
//场景类
public class Client {
public static void main(String[] args) {
InstallSoftware invoker = new InstallSoftware();
invoker.installWizard(new Wizard());
}
}
Wizard类把太多的方法暴露给InstallSoftware类,两者的朋友关系太亲密了,耦合关系变得异常牢固。如果要将Wizard类中的first方法返回值的类型由int改为boolean,就需要修改InstallSoftware类,从而把修改变更的风险扩散开了。具体表现在InstallSoftware类的installWizard方法中,统筹组合调用了 一些导向类的方法,这整过程(调用多个导向类的方法)是可以封装在导向类中的,然后再提供给InstallSoftware类的。代码修改如下。
代码2
修改后的导向类实现过程
public class Wizard {
private Random rand = new Random(System.currentTimeMillis());
//第一步
private int first(){
System.out.println("执行第一个方法...");
return rand.nextInt(100);
}
//第二步
private int second(){
System.out.println("执行第二个方法...");
return rand.nextInt(100);
}
//第三个方法
private int third(){
System.out.println("执行第三个方法...");
return rand.nextInt(100);
}
//软件安装过程,封装到了自己的类中,统筹调用自己的一些方法
public void installWizard(){
int first = this.first();
//根据first返回的结果,看是否需要执行second
if(first>50){
int second = this.second();
if(second>50){
int third = this.third();
if(third >50){
this.first();
}
}
}
}
}
修改后的InstallSoftware类
public class InstallSoftware {
public void installWizard(Wizard wizard){
//直接调用朋友类的一个封装方法即可,不用调用多个朋友类的方法
wizard.installWizard();
}
}
-
将三个步骤的访问权限修改为private,同时把InstallSoftware中的方法installWizad移动到Wizard方法中。通过这样的重构后,Wizard类就只对外公布了一个public方法,即使要修改first方法的返回值,影响的也仅仅只是Wizard本身,其他类不受影响,这显示了类的高内聚特性。
一个类公开的public属性或方法越多,修改时涉及的面也就越大,变更引起的风险扩散也就越大。因此,为了保持朋友类间的距离,在设计时需要反复衡量:是否还可以再减少public方法和属性,是否可以修改为private、package-private(包类型,在类、方法、变量前不加访问权限,则默认为包类型)、protected等访问权限,是否可以加上final关键字等。
注意 迪米特法则要求类“羞涩”一点,尽量不要对外公布太多的public方法和非静态的public变量,尽量内敛,多使用private、package-private、protected等访问权限。
5.1.3 是自己的就是自己的
在实际应用中经常会出现这样一个方法:放在本类中也可以,放在其他类中也没有错,那怎么去衡量呢?你可以坚持这样一个原则:如果一个方法放在本类中,既不增加类间关系,也对本类不产生负面影响,那就放置在本类中
(
5.1.4 谨慎使用Serializable
在实际应用中,这个问题是很少出现的,即使出现也会立即被发现并得到解决。是怎么回事呢?举个例子来说,在一个项目中使用RMI(Remote Method Invocation,远程方法调用)方式传递一个VO(Value Object,值对象),这个对象就必须实现Serializable接口(仅仅是一个标志性接口,不需要实现具体的方法),也就是把需要网络传输的对象进行序列化,否则就会出现NotSerializableException异常。突然有一天,客户端的VO修改了一个属性的访问权限,从private变更为public,访问权限扩大了,如果服务器上没有做出相应的变更,就会报序列化失败,就这么简单。但是这个问题的产生应该属于项目管理范畴,一个类或接口在客户端已经变更了,而服务器端却没有同步更新,难道不是项目管理的失职吗?
)
5.2 总结
迪米特法则的核心观念就是类间解耦,弱耦合,只有弱耦合了以后,类的复用率才可以提高。其要求的结果就是产生了大量的中转或跳转类,导致系统的复杂性提高,同时也为维护带来了难度。读者在采用迪米特法则时需要反复权衡,既做到让结构清晰,又做到高内聚低耦合。
六、开闭原则
太简单了,就一句话,对扩展开放,对修改关闭。不多说。
详细暂时略。
七、二十三种设计模式
7.1 单例模式
7.1.1 概念
单例模式就是确保某个类在系统中只有一个实例,并提供一个全局访问点来访问这个实例.
7.1.2 要点
单例模式的实现通常包含以下几个要点:
-
单例类的构造函数是私有的,防止外部代码创建实例。(即构造方法被private修饰)
-
单例类中通常包含一个静态的私有成员变量,用于保存该类的唯一实例。
-
单例类提供一个公共的静态方法,用于创建或获取该类的唯一实例。(方法被public static 修饰)
单例模式又可以分为常说的饿汉式(饥渴型)单例模式和懒汉式(懒狗)单例模式。
7.1.3 饿汉式单例
特点:在类加载时就创建实例/对象,无论是否使用,实例都会一直存在。因此,不会出现线程安全问题(因为还没有开始加载的时候就创建好了对象,线程都没有机会去争夺)。比较饥饿,饿怕了,所以很勤奋,需要/保证 该实例一直存在。
代码示例1(牢固掌握)
public class SingletonHungry {
//成员变量,设置为静态的,在类加载的时候就把对象创建好了
private static SingletonHungry s = new SingletonHungry();
// 1.构造方法私有化
private SingletonHungry(){}
// 2.内部提供一个公共的静态方法给外界进行调用和访问
public static SingletonHungry getInstance(){ // 2.该单例对象必须由单例类自行创建
return s;
}
}
//测试类
public class TestSingletonHungry{
public static void main(String[] args) {
// 因为是static修饰的直接通过类名.方法名()进行调用(如果是非static修饰还需要创建对象,比较麻烦)
SingletonHungry i1 = SingletonHungry.getInstance();// 调用方法获取的每一次都是同一个对象
SingletonHungry i2 = SingletonHungry.getInstance();
System.out.println("获取的对象是否相同:"+(i1==i2)); // 结果为true
// 判断获取的对象是不是同一个对象,通过比较地址值
}
}
代码示例2
public class SingletonHungry {
//成员变量,设置为静态的
private static SingletonHungry s;
//在静态代码块中创建对象,照样也是类加载时执行
static {
s = new SingletonHungry();
}
// 1.构造方法私有化
private SingletonHungry(){}
// 2.内部提供一个公共的静态方法给外界进行调用和访问
public static SingletonHungry getInstance(){ // 2.该单例对象必须由单例类自行创建
return s;
}
}
//测试类
public class TestSingletonHungry{
public static void main(String[] args) {
// 因为是static修饰的直接通过类名.方法名()进行调用(如果是非static修饰还需要创建对象,比较麻烦)
SingletonHungry i1 = SingletonHungry.getInstance();// 调用方法获取的每一次都是同一个对象
SingletonHungry i2 = SingletonHungry.getInstance();
System.out.println("获取的对象是否相同:"+(i1==i2)); // 结果为true
// 判断获取的对象是不是同一个对象,通过比较地址值
}
}
补充:
什么是类加载的线程安全特性
-
类加载的线程安全特性是指在类加载过程中,JVM会保证每个类只会被加载一次,并且在加载过程中提供了线程安全的机制,避免了多个线程同时加载同一个类的问题,确保了类的唯一性和安全性。这个特性可以被利用来实现单例模式中的线程安全。
7.1.4 懒汉式单例
懒汉式:在需要的时候才创建,类加载时不创建对象
特点:在多线程操作时会出现线程安全问题,但是可以使用线程同步的三种方式进行解决,(1. 同步代码块;2. 同步方法;3. 同步锁)。比较懒,在需要时才创建。
代码1:存在线程安全问题
public class SingletonLazyNoSafety {
// 成员变量
private static SingletonLazyNoSafety s;
// 1.构造方法私有化
public SingletonLazyNoSafety(){}
// 3.内部提供一个公共静态的方法供外界进行访问
public static SingletonLazyNoSafety getInstance(){
if(s == null){// 如果s为null,就创建一个对象
s = new SingletonLazyNoSafety();// 2.该单例对象必须由单例类自行创建
}
return s; // 如果s已经被创建就直接返回s
}
}
//测试类
public class TestSingletonHungry{
public static void main(String[] args) {
// 因为时static修饰的直接通过类名进行调用
SingletonLazyNoSafety i1 = SingletonLazyNoSafety.getInstance();
SingletonLazyNoSafety i2 = SingletonLazyNoSafety.getInstance();
// 判断是不是同一个对象
System.out.println("获取的对象是否相同:"+(i1==i2)); // 输出结果为true
}
}
该代码存在线程安全问题,如一个线程执行到s = new SingletonLazyNoSafety(),但还没有创建对象(对象初始化是需要时间的),此时第二个线程B也在执行,执行到 s == null的判断,判断为false,那么线程B继续运行下去,最终,线程A、线程B获得了一个对象,内存中就出现了2个对象!(可以通过多种方式实现线程安全,代码2提供一种简单的方法)
代码2:通过同步代码块和双重校验锁(reids笔记中讲过,不多说)保证线程安全
public class SingletonLazySafety {
// 成员变量
private static SingletonLazySafety s;
// 1.构造方法私有化
public SingletonLazySafety(){}
// 3.内部提供一个公共静态的方法供外界进行访问
public static SingletonLazySafety getInstance(){
if (s == null) { // 双重校验锁 如果对象s为null(即没有被创就继续执行if里面的内容)
// 使用同步代码块实现线程安全
synchronized (SingletonLazySafety.class) {
if(s == null){// 如果s为null,就创建一个对象
s = new SingletonLazySafety();// 2.该单例对象必须由单例类自行创建
}
}
}
return s; // 如果s已经被创建就直接返回s
}
}
//测试类
public class TestSingletonHungry{
// 测试类
public static void main(String[] args) {
// 因为时static修饰的直接通过类名进行调用
SingletonLazySafety i1 = SingletonLazySafety.getInstance();
SingletonLazySafety i2 = SingletonLazySafety.getInstance();
System.out.println("获取的对象是否相同:"+(i1==i2));
}
}
7.1.5 扩展--有上限多例模式
如果一个类可以产生多个对象,对象的数量不受限制,则是非常容易实现的,直接使用new关键字就可以了。
如果只需要一个对象,使用单例模式就可以了。
但是如果要求一个类只能产生两三个对象呢?该怎么实现?
直接上代码,以皇帝抽象的实例为例
public class Emperor {
//定义最多能产生的实例数量
private static int maxNumOfEmperor = 2;
//每个皇帝都有名字,使用一个ArrayList来容纳,每个对象的私有属性
private static ArrayList<String> nameList=new ArrayList<String>();
//定义一个列表,容纳所有的皇帝实例
private static ArrayList<Emperor> emperorList=new ArrayList<Emperor>();
//当前皇帝序列号
private static int countNumOfEmperor =0;
//产生所有的对象
static{
for(int i=0;i<maxNumOfEmperor;i++){
emperorList.add(new Emperor("皇"+(i+1)+"帝"));
}
}
private Emperor(){
//世俗和道德约束你,目的就是不产生第二个皇帝
}
//传入皇帝名称,建立一个皇帝对象
private Emperor(String name){
nameList.add(name);
}
//随机获得一个皇帝对象
public static Emperor getInstance(){
Random random = new Random();
//随机拉出一个皇帝,只要是个精神领袖就成
countNumOfEmperor = random.nextInt(maxNumOfEmperor);
return emperorList.get(countNumOfEmperor);
}
//皇帝发话了
public static void say(){
System.out.println(nameList.get(countNumOfEmperor));
}
}
//在Emperor中使用了两个ArrayList分别存储实例和实例变量。当然,如果考虑到线程安全问题可以使用Vector来代替。臣子参拜皇帝的过程如代码清单7-6所示。
//测试类,臣子参拜皇帝的过程
public class Minister {
public static void main(String[] args) {
//定义5个大臣
int ministerNum =5;
for(int i=0;i<ministerNum;i++){
Emperor emperor = Emperor.getInstance();
System.out.print("第"+(i+1)+"个大臣参拜的是:");
emperor.say();
}
}
}
//大臣参拜皇帝的结果如下所示。
//第1个大臣参拜的是:皇1帝
//第2个大臣参拜的是:皇2帝
//第3个大臣参拜的是:皇1帝
//第4个大臣参拜的是:皇1帝
//第5个大臣参拜的是:皇2帝
-
其实就是在加载时,创建好指定数量的实例,获取时,随机获取即可。这样,我们可以在设计时决定在内存中有多少个实例,方便系统进行扩展,修正单例可能存在的性能问题。例如,读取文件,我们可以在系统启动时完成初始化工作,在内存中启动固定数据的reader实例,然后在需要读取文件时就可以快速响应。
7.2 工厂方法模式
详见spring6笔记6.3
7.3 抽象工厂模式
详见spring6笔记6.4
7.4 模板方法模式
7.4.1 定义
Define the skeleton of an algorithm in an operation,deferring some steps to subclasses.TemplateMethod lets subclasses redefine certain steps of an algorithm without changing the algorithm'sstructure.
定义一个操作中的算法的框架,而将一些步骤延迟到子类中。使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
7.4.2 通用类图
-
基本方法
基本方法也叫做基本操作,是由子类自由实现的方法,并且在模板方法被调用。
-
模板方法
模板方法可以有一个或几个,一般是一个具体方法,也就是一个框架,实现对基本方法的调度,完成固定的逻辑。
7.4.3 通用代码
抽象模板类
public abstract class AbstractClass {
//基本方法
protected abstract void doSomething();
//基本方法
protected abstract void doAnything();
//模板方法
public void templateMethod(){
/*
* 调用基本方法(固定的逻辑,如固定调用顺序),完成相关的逻辑
*/
this.doAnything();
this.doSomething();
}
}
具体模板类
public class ConcreteClass1 extends AbstractClass {
//实现基本方法,自定义实现
protected void doAnything() {
//自定义业务逻辑处理
}
protected void doSomething() {
//自定义业务逻辑处理
}
}
public class ConcreteClass2 extends AbstractClass {
//实现基本方法,自定义实现
protected void doAnything() {
//自定义业务逻辑处理
}
protected void doSomething() {
//自定义业务逻辑处理
}
}
场景类
public class Client {
public static void main(String[] args) {
AbstractClass class1 = new ConcreteClass1();
AbstractClass class2 = new ConcreteClass2();
//调用模板方法
class1.templateMethod();
class2.templateMethod();
}
}
注意,抽象模板中的基本方法尽量设计为protected类型,符合迪米特法则,不需要暴露的属性或方法尽量不要设置为protected类型。实现类若非必要,尽量不要扩大父类中的访问权限
7.4.4 优缺点
优点
-
封装不变部分,扩展可变部分
把认为是不变部分的算法封装到父类实现,而可变部分的则可以通过继承来继续扩展。
-
提取公共部分代码,便于维护
将相同的部分代码提取到抽象父类中,可以提高代码的复用性。
-
行为由父类控制,子类实现
基本方法是由子类实现的,因此子类可以通过扩展的方式增加相应的功能,符合开闭原则。
缺点
-
类爆炸:对每个不同的实现都需要定义一个子类,这会导致类的个数增加,系统更加庞大,设计也更加抽象,间接地增加了系统实现的复杂度。
-
父类中的抽象方法由子类实现,子类执行的结果会影响父类的结果,这导致一种反向的控制结构,它提高了代码阅读的难度。
-
由于继承关系自身的缺点,如果父类添加新的抽象方法,则所有子类都要改一遍。
7.4.5 模板方法模式的扩展
父类调用子类的一种方式,详见设计模式之禅
7.5 建造者模式!
7.5.1 定义
建造者模式(Builder Pattern)也叫做生成器模式,其定义如下:
Separate the construction of a complex object from its representation so that the sameconstruction process can create different representations.
将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。(封装一个复杂对象构造过程,并允许按步骤构造。)
解释:就是将复杂对象的创建过程拆分成多个简单对象的创建过程,并将这些简单对象组合起来构建出复杂对象。
7.5.2 通用类图
7.5.3 角色
-
Product产品类
表示被创建的复杂对象。它通常包含多个部分或者组成,并由具体的建造者逐步构建而成。
-
Builder抽象建造者
定义了建造复杂对象所需要的各个部分的创建方法。它通常包括多个构建方法和一个返回产品的方法。
-
ConcreteBuilder具体建造者
实现抽象类定义的所有方法,并且返回一个组建好的对象。
-
Director导演类/指挥者类
负责控制建造者的构建顺序,指挥建造者如何构建复杂对象
7.5.4 通用代码
略。
7.5.5 具体示例
7.5.5.1 示例1(不太好)
假设肯德基套餐主要由汉堡、薯条和饮料三种组成,每个组件都有不同种类和大小,并且每个套餐的组合方式也不同。下面以肯德徳套餐为例,解释建造者模式。
产品类(Product)
@Data
public class Meal {
//汉堡包
private String burger;
//薯条
private String fries;
//饮料
private String drink;
}
抽象建造者(Builder)
public abstract class MealBuilder {
protected Meal meal=new Meal();
//构建汉堡
public abstract void buildBurger();
//构建薯条
public abstract void buildFries();
//构建饮料
public abstract void buildDrink();
public Meal getMeal(){
return meal;
}
}
具体建造者(ConcreteBuilder)
//鸡肉汉堡套餐
public class ChickenMealBuilder extends MealBuilder{
@Override
public void buildBurger() {
meal.setBurger("鸡肉汉堡");
}
@Override
public void buildFries() {
meal.setFries("中份薯条");
}
@Override
public void buildDrink() {
meal.setDrink("大杯果汁");
}
}
//牛肉汉堡套餐
public class BeefBurgerMealBuilder extends MealBuilder {
@Override
public void buildBurger() {
meal.setBurger("牛肉汉堡");
}
@Override
public void buildFries() {
meal.setFries("大份薯条");
}
@Override
public void buildDrink() {
meal.setDrink("中杯可乐");
}
}
//虾肉汉堡套餐
public class ShrimpMealBuilder extends MealBuilder {
@Override
public void buildBurger() {
meal.setBurger("虾肉汉堡");
}
@Override
public void buildFries() {
meal.setFries("小份薯条");
}
@Override
public void buildDrink() {
meal.setDrink("大杯芬达");
}
}
指导者(Director)
public class MealDirector {
private MealBuilder mealBuilder;
public void setMealBuilder(MealBuilder mealBuilder){
this.mealBuilder=mealBuilder;
}
public Meal getMeal(){
return mealBuilder.getMeal();
}
//制作套餐
public void constructMeal(){
mealBuilder.buildBurger();
mealBuilder.buildFries();
mealBuilder.buildDrink();
}
}
我觉得建造者模式如果侧重“不同的执行顺序”好玩一点,但考试就喜欢考上面这种。
7.5.5.2 示例2
结合模板方法模式。突出内部顺序。
7.5.6 优缺点
优点
-
灵活:可以分步骤地构建复杂对象,使得构建过程更加灵活。
-
解耦:可以隔离复杂对象的创建和使用,客户端不必关心对象的创建细节。
-
易扩展:增加新的具体建造者很方便,可以扩展构建器功能,符合开闭原则。
缺点:
-
增加工作量:需要额外的代码来创建和管理具体建造者类,增加了程序员的工作量。
-
效率低:相比于其他创建型模式,在运行时效率较低,特别是对象太复杂时。
7.6 代理模式
7.6.1 定义
Provide a surrogate or placeholder for another object to control access to it.(为其他对象提供一种代理以控制对这个对象的访问)
7.6.2 通用类图
代理模式的通用类图如图12-3所示。代理类和被代理类实现同一个接口/继承一个父类,然后代理类中关联被代理类(即将其作为其成员变量)
7.6.3 角色
代理模式也叫做委托模式,它是一项基本设计技巧。许多其他的模式,如状态模式、策略模式、访问者模式本质上是在更特殊的场合采用了委托模式
-
Subject抽象主题角色
抽象主题类可以是抽象类也可以是接口,是一个最普通的业务类型定义,无特殊要求。
-
RealSubject具体主题角色
也叫做被委托角色、被代理角色。它才是冤大头,是业务逻辑的具体执行者。
-
Proxy代理主题角色
也叫做委托类、代理类。它负责对真实角色的应用,把所有抽象主题类定义的方法限制委托给真实主题角色实现,并且在真实主题角色处理完毕前后做预处理和善后处理工作。
7.6.4 通用代码示例
//抽象主题类
public interface Subject {
//定义一个方法
public void request();
}
//真实主题类
public class RealSubject implements Subject {
//实现方法
public void request() {
//业务逻辑处理
}
}
//代理类
public class Proxy implements Subject {
//要代理哪个实现类
private Subject subject = null;
//通过构造函数传递代理者
public Proxy(Subject _subject){
this.subject = _subject;
}
//实现接口中定义的方法
public void request() {
this.before();
this.subject.request();
this.after();
}
//预处理
private void before(){
//do something
}
//善后处理
private void after(){
//do something
}
}
具体代码示例,游戏代练
类图
//游戏者接口
public interface IGamePlayer {
//登录游戏
public void login(String user,String password);
//杀怪,网络游戏的主要特色
public void killBoss();
//升级
public void upgrade();
}
//游戏者,实现接口
public class GamePlayer implements IGamePlayer {
private String name = "";
//通过构造函数传递名称
public GamePlayer(String _name){
this.name = _name;
}
//打怪,最期望的就是杀老怪
public void killBoss() {
System.out.println(this.name + "在打怪!");
}
//进游戏之前你肯定要登录吧,这是一个必要条件
public void login(String user, String password) {
System.out.println("登录名为"+user+"的用户"+this.name+"登录成功!");
}
//升级,升级有很多方法,花钱买是一种,做任务也是一种
public void upgrade() {
System.out.println(this.name + " 又升了一级!");
}
}
//代练者,实现接口,具体实现内容是调用被代理者的方法
public class GamePlayerProxy implements IGamePlayer {
private IGamePlayer gamePlayer = null;
//通过构造函数传递要对谁进行代练
public GamePlayerProxy(IGamePlayer _gamePlayer){
this.gamePlayer = _gamePlayer;
}
//代练杀怪
public void killBoss() {
this.gamePlayer.killBoss();
}
//代练登录
public void login(String user, String password) {
this.gamePlayer.login(user, password);
}
//代练升级
public void upgrade() {
this.gamePlayer.upgrade();
}
}
//客户端类
public class Client {
public static void main(String[] args) {
//定义一个痴迷的玩家
IGamePlayer player = new GamePlayer("张三");
//然后再定义一个代练者
IGamePlayer proxy = new GamePlayerProxy(player);
//开始打游戏,记下时间戳
System.out.println("开始时间是:2009-8-25 10:45");
proxy.login("zhangSan", "password");
//开始杀怪
proxy.killBoss();
//升级
proxy.upgrade();
//记录结束游戏时间
System.out.println("结束时间是:2009-8-26 03:40");
}
}
//结果
开始时间是:2009-8-25 10:45
登录名为zhangSan 的用户 张三登录成功!
张三在打怪!
张三 又升了一级!
结束时间是:2009-8-26 03:40
7.6.5 代理模式的优缺点
优点
-
可以控制对象的访问和权限。
-
可以为对象提供额外的功能,例如缓存和延迟加载。
-
可以降低系统的耦合度,使得修改和扩展更加容易。
缺点
-
代理模式会造成系统设计中类的数量增加
-
在客户端和目标对象之间增加一个代理对象,会造成请求处理速度变慢
-
增加了系统的复杂度
7.6.6 代理模式的扩展
7.6.6.1 普通代理
普通代理就是我们要知道代理的存在,也就是类似的GamePlayerProxy这个类的存在,然后才能访问;强制代理则是调用者直接调用真实角色,而不用关心代理是否存在,其代理的产生是由真实角色决定的。
首先说普通代理,它的要求就是客户端只能访问代理角色,而不能访问真实角色。(就是不需要new一个真实角色传给代理对象,其实就是在代理类中的构造方法中new创建真实角色,这样想要访问真实角色,只能通过代理角色)
类图
//普通代理的游戏者
public class GamePlayer implements IGamePlayer {
private String name = "";
//构造函数限制谁能创建对象(重点!!!),并同时传递姓名
public GamePlayer(IGamePlayer _gamePlayer,String _name) throws Exception{
if(_gamePlayer == null ){
throw new Exception("不能创建真实角色!");
}else{
this.name = _name;
}
}
//打怪,最期望的就是杀老怪
public void killBoss() {
System.out.println(this.name + "在打怪!");
}
//进游戏之前你肯定要登录吧,这是一个必要条件
public void login(String user, String password) {
System.out.println("登录名为"+user + "的用户" + this.name + "登录成功!");
}
//升级,升级有很多方法,花钱买是一种,做任务也是一种
public void upgrade() {
System.out.println(this.name + " 又升了一级!");
}
}
//普通代理的代理者
public class GamePlayerProxy implements IGamePlayer {
private IGamePlayer gamePlayer = null;
//通过构造函数传递要对谁进行代练
public GamePlayerProxy(String name){
try {
gamePlayer = new GamePlayer(this,name);//重点!!!在代理者的构造方法中创建被代理者
} catch (Exception e) {
// TODO 异常处理
}
}
//代练杀怪
public void killBoss() {
this.gamePlayer.killBoss();
}
//代练登录
public void login(String user, String password) {
this.gamePlayer.login(user, password);
}
//代练升级
public void upgrade() {
this.gamePlayer.upgrade();
}
}
//场景类
public class Client {
public static void main(String[] args) {
//然后再定义一个代练者
IGamePlayer proxy = new GamePlayerProxy("张三");
//开始打游戏,记下时间戳
System.out.println("开始时间是:2009-8-25 10:45");
proxy.login("zhangSan", "password");
//开始杀怪
proxy.killBoss();
//升级
proxy.upgrade();
//记录结束游戏时间
System.out.println("结束时间是:2009-8-26 03:40");
}
}
7.6.6.2 强制代理
强制代理在设计模式中比较另类,为什么这么说呢?一般的思维都是通过代理找到真实的角色,但是强制代理却是要“强制”,你必须通过真实角色查找到代理角色,否则你不能访问。
略。。。后面补充笔记。
7.6.6.3 静态代理
代理类和代理类是关联关系。即我们最开始的示例就是静态代理。
静态代理的缺点
-
类爆炸。假设系统中有1000个接口,那么每个接口都需要代理类,这样类会急剧膨胀。同时,不好维护,若增强代码的部分需求变动了,那么,每个代理类中的每个代理方法中可能都要再全部增加或修改同样的代码部分,不好维护。
-
冗余,代码没有复用
7.6.6.4 动态代理!!!
在程序运行阶段,在内存中动态生成代理类,被称为动态代理,目的是为了减少代理类的数量。同时解决代码复用的问题。
在内存当中动态生成类的技术常见的包括:
-
JDK动态代理技术:只能代理接口。(JDK内置的一套用于动态代理的API,只能代理接口)
-
CGLIB动态代理技术:CGLIB(Code Generation Library)是一个开源项目。是一个强大的,高性能,高质量的Code生成类库,它可以在运行期扩展Java类与实现Java接口。它既可以代理接口,又可以代理类,底层是通过继承的方式实现的。性能比JDK动态代理要好。(底层还借助了别人的框架,一个小而快的字节码处理框架ASM。)
-
Javassist动态代理技术:Javassist是一个开源的分析、编辑和创建Java字节码的类库。是由东京工业大学的数学和计算机科学系的 Shigeru Chiba (千叶滋)所创建的。它已加入了开放源代码JBoss 应用服务器项目,通过使用Javassist对字节码操作,为JBoss实现动态"AOP"框架(面向切面编程)。(MyBatis中时,我们用的是这个)
spring框架底层用的是JDK配合CGLIB
具体示例很麻烦,可以去看我的spring的笔记或者具体针对代理的笔记。(都在桌面)
7.7 原型模式
7.7.1 定义
原型模式(Prototype Pattern)的简单程度仅次于单例模式和迭代器模式。正是由于简单,使用的场景才非常地多,其定义如下:
Specify the kinds of objects to create using a prototypical instance,and create new objects bycopying this prototype.
用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
简单说,就是实现Cloneable接口,通过克隆来创建对象代替new创建对象。解决多线程并发时效率或者数据一致等问题。
7.7.2 通用类图
7.7.3 通用代码
public class PrototypeClass implements Cloneable{
//覆写父类Object方法
@Override
public PrototypeClass clone(){
PrototypeClass prototypeClass = null;
try {
prototypeClass = (PrototypeClass)super.clone();
} catch (CloneNotSupportedException e) {
//异常处理
}
return prototypeClass;
}
}
-
Java提供了一个Cloneable接口来标示这个对象是可拷贝的,为什么说是“标示”呢?翻开JDK的帮助看看Cloneable是一个方法都没有的,这个接口只是一个标记作用,在JVM中具有这个标记的对象才有可能被拷贝。那怎么才能从“有可能被拷贝”转换为“可以被拷贝”呢?方法是覆盖clone()方法,是的,你没有看错是重写clone()方法,覆写了Object类中的clone方法.
7.7.4 具体示例
略。书中写得挺好。后期补充。
7.7.5 优缺点
-
性能优良
原型模式是在内存二进制流的拷贝,要比直接new一个对象性能好很多,特别是要在一个循环体内产生大量的对象时,原型模式可以更好地体现其优点。
-
逃避构造函数的约束
这既是它的优点也是缺点,直接在内存中拷贝,构造函数是不会执行的(参见13.4节)。优点就是减少了约束,缺点也是减少了约束,需要大家在实际应用时考虑。
7.7.6 原型模式的使用场景
-
资源优化场景
类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等。
-
性能和安全要求的场景
通过new产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式。
-
一个对象多个修改者的场景
一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式拷贝多个对象供调用者使用。
在实际项目中,原型模式很少单独出现,一般是和工厂方法模式一起出现,通过clone的方法创建一个对象,然后由工厂方法提供给调用者。原型模式已经与Java融为一体,大家可以随手拿来使用
7.7.7 原型模式的注意事项
7.7.7.1 构造函数不会被执行
略
7.7.7.2 浅拷贝和深拷贝!
很重要!略。没空,后面再补上笔记。
7.7.7.3 clone与final
略。
7.8 中介者模式
7.8.1 定义
Define an object that encapsulates how a set of objectsinteract.Mediator promotes loose coupling by keeping objects from referring to each otherexplicitly,and it lets you vary their interaction independently.
用一个中介对象封装一系列的对象交互,中介者使各对象不需要显示地相互作用,从而使其耦合松散,而且可以独立地改变它们之间的交互。
简单说,就是解决各个类相互关联的问题(存储了其他的类作为属性,然后高度使用)。那么就定义一个中介者,存储各个类的引用,凡是设计多个类的联调的方法就定义在中介者类中,凡是只涉及自己类的调用方法就定义在自己类中,解耦。
7.8.2 通用类图
7.8.3 角色
-
Mediator 抽象中介者角色
抽象中介者角色定义统一的接口,用于各同事角色之间的通信。
-
Concrete Mediator 具体中介者角色
具体中介者角色通过协调各同事角色实现协作行为,因此它必须依赖于各个同事角色。
-
Colleague 同事角色
每一个同事角色都知道中介者角色,而且与其他的同事角色通信的时候,一定要通过中介者角色协作。每个同事类的行为分为两种:一种是同事本身的行为,比如改变对象本身的状态,处理自己的行为等,这种行为叫做自发行为(Self-Method),与其他的同事类或中介者没有任何的依赖;第二种是必须依赖中介者才能完成的行为,叫做依赖方法(DepMethod)。
7.8.4 通用代码
通用抽象中介者
public abstract class Mediator {
//存储定义的同事类
protected ConcreteColleague1 c1;
protected ConcreteColleague2 c2;
//通过getter/setter方法把同事类注入进来
public ConcreteColleague1 getC1() {
return c1;
}
public void setC1(ConcreteColleague1 c1) {
this.c1 = c1;
}
public ConcreteColleague2 getC2() {
return c2;
}
public void setC2(ConcreteColleague2 c2) {
this.c2 = c2;
}
//中介者模式的业务逻辑,需要联调的方法,即同时需要调用到同事类c1、c2的方法
public abstract void doSomething1();
public abstract void doSomething2();
}
通用中介者
public class ConcreteMediator extends Mediator {
@Override
public void doSomething1() {
//调用同事类的方法,只要是public方法都可以调用
super.c1.selfMethod1();
super.c2.selfMethod2();
}
public void doSomething2() {
super.c1.selfMethod1();
super.c2.selfMethod2();
}
}
抽象同事类
public abstract class Colleague {
protected Mediator mediator;//存储中介者
public Colleague(Mediator _mediator){
this.mediator = _mediator;
}
}
具体同事类
public class ConcreteColleague1 extends Colleague {
//通过构造函数传递中介者
public ConcreteColleague1(Mediator _mediator){
super(_mediator);
}
//自有方法 self-method
public void selfMethod1(){
//处理自己的业务逻辑
}
//依赖方法 dep-method
public void depMethod1(){
//处理自己的业务逻辑
//自己不能处理的业务逻辑,委托给中介者处理,即需要联调的,调用到其他同事类的
super.mediator.doSomething1();
}
}
public class ConcreteColleague2 extends Colleague {
//通过构造函数传递中介者
public ConcreteColleague2(Mediator _mediator){
super(_mediator);
}
//自有方法 self-method
public void selfMethod2(){
//处理自己的业务逻辑
}
//依赖方法 dep-method
public void depMethod2(){
//处理自己的业务逻辑
//自己不能处理的业务逻辑,委托给中介者处理
super.mediator.doSomething2();
}
}
7.8.5 具体示例
进销存实例。三个模块的示意图如下。
7.8.5.1 不使用中介者模式
类图
代码
采购管理
public class Purchase {
//采购IBM电脑(联调方法,高耦合)
public void buyIBMcomputer(int number){
//访问库存
Stock stock = new Stock();
//访问销售
Sale sale = new Sale();
//电脑的销售情况
int saleStatus = sale.getSaleStatus();
if(saleStatus>80){ //销售情况良好
System.out.println("采购IBM电脑:"+number + "台");
stock.increase(number);
}else{ //销售情况不好
int buyNumber = number/2; //折半采购
System.out.println("采购IBM电脑:"+buyNumber+ "台");
}
}
//不再采购IBM电脑(自己单独的方法)
public void refuseBuyIBM(){
System.out.println("不再采购IBM电脑");
}
}
库存管理
public class Stock {
//刚开始有100台电脑
private static int COMPUTER_NUMBER =100;
//库存增加(自己的方法)
public void increase(int number){
COMPUTER_NUMBER = COMPUTER_NUMBER + number;
System.out.println("库存数量为:"+COMPUTER_NUMBER);
}
//库存降低(自己的方法)
public void decrease(int number){
COMPUTER_NUMBER = COMPUTER_NUMBER - number;
System.out.println("库存数量为:"+COMPUTER_NUMBER);
}
//获得库存数量(自己的方法)
public int getStockNumber(){
return COMPUTER_NUMBER;
}
//存货压力大了,就要通知采购人员不要采购,销售人员要尽快销售 (联调方法,高耦合)
public void clearStock(){
Purchase purchase = new Purchase();
Sale sale = new Sale();
System.out.println("清理存货数量为:"+COMPUTER_NUMBER);
//要求折价销售
sale.offSale();
//要求采购人员不要采购
purchase.refuseBuyIBM();
}
}
销售管理
public class Sale {
//销售IBM电脑(联调方法,高耦合)
public void sellIBMComputer(int number){
//访问库存
Stock stock = new Stock();
//访问采购
Purchase purchase = new Purchase();
if(stock.getStockNumber()<number){ //库存数量不够销售
purchase.buyIBMcomputer(number);
}
System.out.println("销售IBM电脑"+number+"台");
stock.decrease(number);
}
//反馈销售情况,0~100之间变化,0代表根本就没人卖,100代表非常畅销,出一个卖一个
public int getSaleStatus(){
Random rand = new Random(System.currentTimeMillis());
int saleStatus = rand.nextInt(100);
System.out.println("IBM电脑的销售情况为:"+saleStatus);
return saleStatus;
}
//折价处理
public void offSale(){
//库房有多少卖多少
Stock stock = new Stock();
System.out.println("折价销售IBM电脑"+stock.getStockNumber()+"台");
}
}
场景类
public class Client {
public static void main(String[] args) {
//采购人员采购电脑
System.out.println("------采购人员采购电脑--------");
Purchase purchase = new Purchase();
purchase.buyIBMcomputer(100);
//销售人员销售电脑
System.out.println("\n------销售人员销售电脑--------");
Sale sale = new Sale();
sale.sellIBMComputer(1);
//库房管理人员管理库存
System.out.println("\n------库房管理人员清库处理--------");
Stock stock = new Stock();
stock.clearStock();
}
}
问题
这三个类是彼此关联的。每个类都与其他两个类产生了关联关系。迪米特法则认为“每个类只和朋友类交流”,这个朋友类并非越多越好,朋友类越多,耦合性越大,要想修改一个就得修改一片,这不是面向对象设计所期望的,况且这还是仅三个模块的情况,属于比较简单的一个小项目。我们把进销存扩展一下。
这是一个蜘蛛网的结构,别说是编写程序了,就是给人看估计也能让一大批人昏倒!倘若使用上面的架构方式,每个对象都需要和其他几个对象交流,对象越多,每个对象要交流的成本也就越大了,只是维护这些对象的交流就能让一大批程序员望而却步!从这方面来说,我们已经发现设计的缺陷了。
7.8.5.2 使用中介者模式
类图
代码
抽象中介者
public abstract class AbstractMediator {
//存储各个同时类
protected Purchase purchase;
protected Sale sale;
protected Stock stock;
//构造函数
public AbstractMediator(){
purchase = new Purchase(this);
sale = new Sale(this);
stock = new Stock(this);
}
//中介者最重要的方法叫做事件方法,处理多个对象之间的关系
public abstract void execute(String str,Object...objects);
}
具体中介者
public class Mediator extends AbstractMediator {
//中介者最重要的方法
public void execute(String str,Object...objects){
if(str.equals("purchase.buy")){ //采购电脑
this.buyComputer((Integer)objects[0]);
}else if(str.equals("sale.sell")){ //销售电脑
this.sellComputer((Integer)objects[0]);
}else if(str.equals("sale.offsell")){ //折价销售
this.offSell();
}else if(str.equals("stock.clear")){ //清仓处理
this.clearStock();
}
}
//定义处理多个对象之间关系的方法
//采购电脑
private void buyComputer(int number){
int saleStatus = super.sale.getSaleStatus();
if(saleStatus>80){ //销售情况良好
System.out.println("采购IBM电脑:"+number + "台");
super.stock.increase(number);
}else{ //销售情况不好
int buyNumber = number/2; //折半采购
System.out.println("采购IBM电脑:"+buyNumber+ "台");
}
}
//销售电脑
private void sellComputer(int number){
if(super.stock.getStockNumber()<number){ //库存数量不够销售
super.purchase.buyIBMcomputer(number);
}
super.stock.decrease(number);
}
//折价销售电脑
private void offSell(){
System.out.println("折价销售IBM电脑"+stock.getStockNumber()+"台");
}
//清仓处理
private void clearStock(){
//要求清仓销售
super.sale.offSale();
//要求采购人员不要采购
super.purchase.refuseBuyIBM();
}
}
抽象同事类
public abstract class AbstractColleague {
protected AbstractMediator mediator;//存储中介者类
public AbstractColleague(AbstractMediator _mediator){
this.mediator = _mediator;
}
}
具体同事类
修改后的采购管理
public class Purchase extends AbstractColleague{
public Purchase(AbstractMediator _mediator){
super(_mediator);
}
//将只关系自己的方法留下,涉及到其他同事类的方法移到中介者类中,然后这里的方法体调用中介者类的那个方法
//采购IBM电脑(调用中介者类)
public void buyIBMcomputer(int number){
super.mediator.execute("purchase.buy", number);
}
//不再采购IBM电脑
public void refuseBuyIBM(){
System.out.println("不再采购IBM电脑");
}
}
修改后的库存管理
public class Stock extends AbstractColleague {
public Stock(AbstractMediator _mediator){
super(_mediator);
}
//刚开始有100台电脑
private static int COMPUTER_NUMBER =100;
//库存增加
public void increase(int number){
COMPUTER_NUMBER = COMPUTER_NUMBER + number;
System.out.println("库存数量为:"+COMPUTER_NUMBER);
}
//库存降低
public void decrease(int number){
COMPUTER_NUMBER = COMPUTER_NUMBER - number;
System.out.println("库存数量为:"+COMPUTER_NUMBER);
}
//获得库存数量
public int getStockNumber(){
return COMPUTER_NUMBER;
}
//存货压力大了,就要通知采购人员不要采购,销售人员要尽快销售
public void clearStock(){
System.out.println("清理存货数量为:"+COMPUTER_NUMBER);
super.mediator.execute("stock.clear");
}
}
修改后的销售管理
public class Sale extends AbstractColleague {
public Sale(AbstractMediator _mediator){
super(_mediator);
}
//销售IBM电脑
public void sellIBMComputer(int number){
super.mediator.execute("sale.sell", number);
System.out.println("销售IBM电脑"+number+"台");
}
//反馈销售情况,0~100变化,0代表根本就没人买,100代表非常畅销,出一个卖一个
public int getSaleStatus(){
Random rand = new Random(System.currentTimeMillis());
int saleStatus = rand.nextInt(100);
System.out.println("IBM电脑的销售情况为:"+saleStatus);
return saleStatus;
}
//折价处理
public void offSale(){
super.mediator.execute("sale.offsell");
}
}
修改后的场景类
public class Client {
public static void main(String[] args) {
//中介者类
AbstractMediator mediator = new Mediator();
//采购人员采购电脑
System.out.println("------采购人员采购电脑--------");
Purchase purchase = new Purchase(mediator);//传入中介者类
purchase.buyIBMcomputer(100);
//销售人员销售电脑
System.out.println("\n------销售人员销售电脑--------");
Sale sale = new Sale(mediator);
sale.sellIBMComputer(1);
//库房管理人员管理库存
System.out.println("\n------库房管理人员清库处理--------");
Stock stock = new Stock(mediator);
stock.clearStock();
}
}
7.8.6 优缺点
-
中介者模式的优点
中介者模式的优点就是减少类间的依赖,把原有的一对多的依赖变成了一对一的依赖,同事类只依赖中介者,减少了依赖,当然同时也降低了类间的耦合。
-
中介者模式的缺点
中介者模式的缺点就是中介者会膨胀得很大,而且逻辑复杂,原本N个对象直接的相互依赖关系转换为中介者和同事类的依赖关系,同事类越多,中介者的逻辑就越复杂。
7.8.7 中介者模式的实际应用
中介者模式也叫做调停者模式,是什么意思呢?一个对象要和N多个对象交流,就像对象间的战争,很混乱。这时,需要加入一个中心,所有的类都和中心交流,中心说怎么处理就怎么处理,我们举一些在开发和生活中经常会碰到的例子。
-
机场调度中心
大家在每个机场都会看到有一个“××机场调度中心”,它就是具体的中介者,用来调度每一架要降落和起飞的飞机。比如,某架飞机(同事类)飞到机场上空了,就询问调度中心(中介者)“我是否可以降落”以及“降落到哪个跑道”,调度中心(中介者)查看其他飞机(同事类)情况,然后通知飞机降落。如果没有机场调度中心,飞机飞到机场了,飞行员要先看看有没有飞机和自己一起降落的,有没有空跑道,停机位是否具备等情况,这种局面是难以想象的!
-
MVC框架
大家都应该使用过Struts,MVC框架,其中的C(Controller)就是一个中介者,叫做前端控制器(Front Controller),它的作用就是把M(Model,业务逻辑)和V(View,视图)隔离开,协调M和V协同工作,把M运行的结果和V代表的视图融合成一个前端可以展示的页面,减少M和V的依赖关系。MVC框架已经成为一个非常流行、成熟的开发框架,这也是中介者模式的优点的一个体现。
-
媒体网关
媒体网关也是一个典型的中介者模式,比如使用MSN时,张三发消息给李四,其过程应该是这样的:张三发送消息,MSN服务器(中介者)接收到消息,查找李四,把消息发送到李四,同时通知张三,消息已经发送。在这里,MSN服务器就是一个中转站,负责协调两个客户端的信息交流,与此相反的就是IPMsg(也叫飞鸽),它没有使用中介者,而直接使用了UDP广播的方式,每个客户端既是客户端也是服务器端。
-
中介服务
现在中介服务非常多,比如租房中介、出国中介,这些也都是中介模式的具体体现,比如你去租房子,如果没有房屋中介,你就必须一个一个小区去找,看看有没有空房子,有没有适合自己的房子,找到房子后还要和房东签合约,自己检查房屋的家具、水电煤等;有了中介后,你就省心多了,找中介,然后安排看房子,看中了,签合约,中介帮你检查房屋家具、水电煤等等。这也是中介模式的实际应用。
7.9 命令模式
7.9.1 定义
命令模式是一个高内聚的模式,其定义为:Encapsulate a request as an object,therebyletting you parameterize clients with different requests,queue or log requests,and support undoableoperations.
将一个请求封装成一个对象,从而让你使用不同的请求把客户端参数化,对请求排队或者记录请求日志,可以提供命令的撤销和恢复功能。
简单说,就是将 原子化的操作 封装成命令,对应一个业务或者大操作/大命令,提高代码的复用率,同时降低耦合度。
7.9.2 通用类图
7.9.3 角色
-
Receive接收者角色
该角色就是干活的角色,命令传递到这里是应该被执行的,具体到我们上面的例子中就是Group的三个实现类。
-
Command命令角色
需要执行的所有命令都在这里声明。
-
Invoker调用者角色
接收到命令,并执行命令。在例子中,我(项目经理)就是这个角色。
7.9.4 通用代码
略。
7.9.5 具体示例
引入略。原书写得挺好的。
美工组、代码组、需求组 和 客户交流,接收命令
类图
代码
抽象命令类
public abstract class Command {
//存储所有接收者对象,即把三个组都定义好,子类可以直接使用
protected RequirementGroup rg = new RequirementGroup(); //需求组
protected PageGroup pg = new PageGroup(); //美工组
protected CodeGroup cg = new CodeGroup(); //代码组
//只有一个方法,你要我做什么事情
public abstract void execute();
}
具体命令类
增加需求的命令
public class AddRequirementCommand extends Command {
//执行增加一项需求的命令
public void execute() {
//找到需求组
super.rg.find();
//增加一份需求
super.rg.add();
//给出计划
super.rg.plan();
}
}
删除页面的命令
public class DeletePageCommand extends Command {
//执行删除一个页面的命令
public void execute() {
//找到页面组
super.pg.find();
//删除一个页面
super.rg.delete();
//给出计划
super.rg.plan();
}
}
调用者类
负责人
public class Invoker {
//什么命令
private Command command;
//客户发出命令
public void setCommand(Command command){
this.command = command;
}
//执行客户的命令
public void action(){
this.command.execute();
}
}
场景类
增加一项需求
public class Client {
public static void main(String[] args) {
//定义我们的接头人
Invoker xiaoSan = new Invoker(); //接头人就是小三
//客户要求增加一项需求
System.out.println("------------客户要求增加一项需求---------------");
//客户给我们下命令来
Command command = new AddRequirementCommand();
//接头人接收到命令
xiaoSan.setCommand(command);
//接头人执行命令
xiaoSan.action();
}
}
删除一个页面
public class Client {
public static void main(String[] args) {
//定义我们的接头人
Invoker xiaoSan = new Invoker(); //接头人就是小三
//客户要求增加一项需求
System.out.println("------------客户要求删除一个页面---------------");
//客户给我们下命令来
//Command command = new AddRequirementCommand();
Command command = new DeletePageCommand();
//接头人接收到命令
xiaoSan.setCommand(command);
//接头人执行命令
xiaoSan.action();
}
}
7.9.6 命令模式的优点
优点
-
类间解耦
调用者角色与接收者角色之间没有任何依赖关系,调用者实现功能时只需调用Command抽象类的execute方法就可以,不需要了解到底是哪个接收者执行。
-
可扩展性
Command的子类可以非常容易地扩展,而调用者Invoker和高层次的模块Client不产生严重的代码耦合。
-
命令模式结合其他模式会更优秀
命令模式可以结合责任链模式,实现命令族解析任务;结合模板方法模式,则可以减少Command子类的膨胀问题。
缺点
命令模式也是有缺点的,类爆炸。请看Command的子类:如果有N个命令,问题就出来了,Command的子类就可不是几个,而是N个,这个类膨胀得非常大,这个就需要读者在项目中慎重考虑使用。
7.9.7 命令模式的使用场景
只要你认为是命令的地方就可以采用命令模式,例如,在GUI开发中,一个按钮的点击是一个命令,可以采用命令模式;模拟DOS命令的时候,当然也要采用命令模式;触发-反馈机制的处理等。
7.9.8 扩展
7.9.8.1 反悔问题
略。
7.10 责任链模式
7.10.1 定义
责任链模式是一种行为型设计模式。Avoid coupling the sender of a request to its receiver by giving more than one object a chance tohandle the request.Chain the receiving objects and pass the request along the chain until an objecthandles it.(使多个对象都有机会处理请求,从而避免了请求的发送者和接受者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有对象处理它为止。)
7.10.2 通用类图
7.10.3 通用代码
抽象处理者
public abstract class Handler {
private Handler nextHandler;//存储下一个处理者
//每个处理者都必须对请求做出处理
public final Response handleMessage(Request request){
Response response = null;
//判断是否是自己的处理级别
if(this.getHandlerLevel().equals(request.getRequestLevel())){
response = this.echo(request);
}else{ //不属于自己的处理级别
//判断是否有下一个处理者
if(this.nextHandler != null){
response = this.nextHandler.handleMessage(request);
}else{
//没有适当的处理者,业务自行处理
}
}
return response;
}
//设置下一个处理者是谁
public void setNext(Handler _handler){
this.nextHandler = _handler;
}
//每个处理者都有一个处理级别
protected abstract Level getHandlerLevel();
//每个处理者都必须实现处理任务
protected abstract Response echo(Request request);
}
-
抽象的处理者实现三个职责:一是定义一个请求的处理方法handleMessage,唯一对外开放的方法;二是定义一个链的编排方法setNext,设置下一个处理者;三是定义了具体的请求者必须实现的两个方法:定义自己能够处理的级别getHandlerLevel和具体的处理任务echo。
-
注意!在责任链模式中一个请求发送到链中后,前一节点消费部分消息,然后交由后续节点继续处理,最终可以有处理结果也可以没有处理结果,读者可以不用理会什么纯的、不纯的责任链模式。同时,请读者注意handlerMessage方法前的final关键字,可以阅读第10章的模板方法模式。
我们定义三个具体的处理者,以便可以组成一个链
public class ConcreteHandler1 extends Handler {
//定义自己的处理逻辑
protected Response echo(Request request) {
//完成处理逻辑
return null;
}
//设置自己的处理级别
protected Level getHandlerLevel() {
//设置自己的处理级别
return null;
}
}
public class ConcreteHandler2 extends Handler {
//定义自己的处理逻辑
protected Response echo(Request request) {
//完成处理逻辑
return null;
}
//设置自己的处理级别
protected Level getHandlerLevel() {
//设置自己的处理级别
return null;
}
}
public class ConcreteHandler3 extends Handler {
//定义自己的处理逻辑
protected Response echo(Request request) {
//完成处理逻辑
return null;
}
//设置自己的处理级别
protected Level getHandlerLevel() {
//设置自己的处理级别
return null;
}
}
在处理者中涉及三个类:Level类负责定义请求和处理级别,Request类负责封装请求,Response负责封装链中返回的结果,该三个类都需要根据业务产生,读者可以在实际应用中完成相关的业务填充。
//代码清单16-16 模式中有关框架代码
public class Level {
//定义一个请求和处理等级
}
public class Request {
//请求的等级
public Level getRequestLevel(){
return null;
}
}
public class Response {
//处理者返回的数据
}
在场景类或高层模块中对链进行组装,并传递请求,返回结果
代码清单16-17 场景类
public class Client {
public static void main(String[] args) {
//声明所有的处理节点
Handler handler1 = new ConcreteHandler1();
Handler handler2 = new ConcreteHandler2();
Handler handler3 = new ConcreteHandler3();
//设置链中的阶段顺序1-->2-->3
handler1.setNext(handler2);
handler2.setNext(handler3);
//提交请求,返回结果
Response response = handler1.handlerMessage(new Request());
}
}
在实际应用中,一般会有一个封装类对责任模式进行封装,也就是替代Client类,直接返回链中的第一个处理者,具体链的设置不需要高层次模块关系,这样,更简化了高层次模块的调用,减少模块间的耦合,提高系统的灵活性
7.10.4 具体实例
中国古代对妇女制定了“三从四德”的道德规范,“三从”是指“未嫁从父、既嫁从夫、夫死从子”。也就是说,一位女性在结婚之前要听从于父亲,结婚之后要听从于丈夫,如果丈夫死了还要听从于儿子。
7.10.4.1类图
7.10.4.2代码实现
Handler抽象类
public abstract class Handler {
public final static int FATHER_LEVEL_REQUEST = 1;
public final static int HUSBAND_LEVEL_REQUEST = 2;
public final static int SON_LEVEL_REQUEST = 3;
//当前实现类能处理的级别
private int level =0;
//责任传递,下一个人责任人是谁
private Handler nextHandler;
//创建时,每个类都要说明一下自己能处理哪些请求
public Handler(int _level){
this.level = _level;
}
//一个女性(女儿、妻子或者是母亲)要求逛街,你要处理这个请求
public final void HandleMessage(IWomen women){
if(women.getType() == this.level){
this.response(women);
}else{
if(this.nextHandler != null){ //有后续环节,才把请求往后递送
this.nextHandler.HandleMessage(women);
}else{ //已经没有后续处理人了,不用处理了
System.out.println("---没地方请示了,按不同意处理---\n");
}
}
}
/*
* 如果不属于你处理的请求,你应该让她找下一个环节的人,如女儿出嫁了,
* 还向父亲请示是否可以逛街,那父亲就应该告诉女儿,应该找丈夫请示
*/
public void setNext(Handler _handler){
this.nextHandler = _handler;
}
//有请示那当然要回应
protected abstract void response(IWomen women);
}
}
Handler的继承类
//父亲类
public class Father extends Handler {
//父亲只处理女儿的请求
public Father(){
super(Handler.FATHER_LEVEL_REQUEST);
}
//父亲的答复
protected void response(IWomen women) {
System.out.println("--------女儿向父亲请示-------");
System.out.println(women.getRequest());
System.out.println("父亲的答复是:同意\n");
}
}
//丈夫类
public class Husband extends Handler {
//丈夫只处理妻子的请求
public Husband(){
super(Handler.HUSBAND_LEVEL_REQUEST);
}
//丈夫请示的答复
protected void response(IWomen women) {
System.out.println("--------妻子向丈夫请示-------");
System.out.println(women.getRequest());
System.out.println("丈夫的答复是:同意\n");
}
}
//儿子类
public class Son extends Handler {
//儿子只处理母亲的请求
public Son(){
super(Handler.SON_LEVEL_REQUEST);
}
//儿子的答复
protected void response(IWomen women) {
System.out.println("--------母亲向儿子请示-------");
System.out.println(women.getRequest());
System.out.println("儿子的答复是:同意\n");
}
}
其他类
//女性接口
public interface IWomen {
//获得个人状况
public int getType();
//获得个人请示,你要干什么?出去逛街?约会?还是看电影?
public String getRequest();
}
//女性类
public class Women implements IWomen{
/*
* 通过一个int类型的参数来描述妇女的个人状况(即当前请求的类型)
* 1--未出嫁
* 2--出嫁
* 3--夫死
*/
private int type=0;
//妇女的请示
private String request = "";
//构造函数传递过来请求
public Women(int _type,String _request){
this.type = _type;
//为了便于显示,在这里做了点处理
switch(this.type){
case 1:
this.request = "女儿的请求是:" + _request;
break;
case 2:
this.request = "妻子的请求是:" + _request;
break;
case 3:
this.request = "母亲的请求是:" + _request;
}
}
//获得自己的状况
public int getType(){
return this.type;
}
//获得妇女的请求
public String getRequest(){
return this.request;
}
}
//场景类
public class Client {
public static void main(String[] args) {
//随机挑选几个女性
Random rand = new Random();
ArrayList<IWomen> arrayList = new ArrayList();
for(int i=0;i<5;i++){
arrayList.add(new Women(rand.nextInt(4),"我要出去逛街"));
}
//定义三个请示对象
Handler father = new Father();
Handler husband = new Husband();
Handler son = new Son();
//设置请示顺序
father.setNext(husband);
husband.setNext(son);
for(IWomen women:arrayList){
father.HandleMessage(women);
}
}
}
-
在Client中设置请求的传递顺序,先向父亲请示,不是父亲应该解决的问题,则由父亲传递到丈夫类解决,若不是丈夫类解决的问题则传递到儿子类解决,最终的结果必然有一个返回。
7.10.5 应用场景
-
当程序需要使用不同方式处理不同种类请求, 而且请求类型和顺序预先未知时,可以使用责任链模式。
-
当必须按顺序执行多个处理者时, 可以使用该模式。
-
如果所需处理者及其顺序必须在运行时进行改变, 可以使用责任链模式。
7.10.6 责任链模式的优点
责任链模式非常显著的优点是将请求和处理分开。请求者可以不用知道是谁处理的,处理者可以不用知道请求的全貌两者解耦,提高系统的灵活性。
7.10.7 责任链模式的缺点
-
一是性能问题,每个请求都是从链头遍历到链尾,特别是在链比较长的时候,性能是一个非常大的问题。
-
二是调试不很方便,特别是链条比较长,环节比较多的时候,由于采用了类似递归的方式,调试的时候逻辑可能比较复杂。
链中节点数量需要控制,避免出现超长链的情况,一般的做法是在Handler中设置一个最大节点数量,在setNext方法中判断是否已经是超过其阈值,超过则不允许该链建立,避免无意识地破坏系统性能。
7.11 装饰模式
7.11.1 定义
Attach additionalresponsibilities to an object dynamically keeping the same interface.Decorators provide a flexiblealternative to subclassing for extending functionality.
动态地给一个对象添加一些额外的职责。就增加功能来说,装饰模式相比生成子类更为灵活。
值得注意的是,代理模式和装饰模式有很多相似之处,要注意区分。代理模式是主要是为了控制访问而存在的, 而装饰模式是为了扩展功能而存在的。代码模式也能扩展功能,但只是 在原代理对象的方法中扩展功能(添加增强代码),而装饰模式,扩展功能是专门再弄出一个功能类出来。
7.11.2 通用类图
7.11.3 角色
-
Component抽象构件
Component是一个接口或者是抽象类,就是定义我们最核心的对象,也就是最原始的对象。注意 在装饰模式中,必然有一个最基本、最核心、最原始的接口或抽象类充当Component抽象构件。
-
ConcreteComponent 具体构件
ConcreteComponent是最核心、最原始、最基本的接口或抽象类的实现,你要装饰的就是它。
-
Decorator装饰角色
一般是一个抽象类,做什么用呢?实现接口或者抽象方法,它里面可不一定有抽象的方法呀,在它的属性里必然有一个private变量指向Component抽象构件。
-
具体装饰角色
ConcreteDecoratorA和ConcreteDecoratorB是两个具体的装饰类,你要把你最核心的、最原始的、最基本的东西装饰成其他东西
7.11.4 通用代码
抽象构件
public abstract class Component {
//抽象的方法
public abstract void operate();
}
具体构件
public class ConcreteComponent extends Component {
//具体实现
@Override
public void operate() {
System.out.println("do Something");
}
}
抽象装饰者
public abstract class Decorator extends Component {
private Component component = null;
//通过构造函数传递被修饰者
public Decorator(Component _component){
this.component = _component;
}
//委托给被修饰者执行
@Override
public void operate() {
this.component.operate();
}
}
-
这个类是为了方便扩展类创建的,扩展类继承该类,然后进行扩展即可。
具体的装饰类
//装饰类1/扩展类1
public class ConcreteDecorator1 extends Decorator {
//定义被修饰者
public ConcreteDecorator1(Component _component){
super(_component);
}
//定义自己的修饰方法
private void method1(){
System.out.println("method1 修饰");
}
//重写父类的Operation方法
public void operate(){
this.method1();
super.operate();
}
}
//装饰类2/扩展类2
public class ConcreteDecorator2 extends Decorator {
//定义被修饰者
public ConcreteDecorator2(Component _component){
super(_component);
}
//定义自己的修饰方法
private void method2(){
System.out.println("method2修饰");
}
//重写父类的Operation方法
public void operate(){
super.operate();
this.method2();
}
}
场景类
public class Client {
public static void main(String[] args) {
Component component = new ConcreteComponent();
//第一次修饰
component = new ConcreteDecorator1(component);
//第二次修饰
component = new ConcreteDecorator2(component);
//修饰后运行
component.operate();
}
}
7.11.5 具体实例
略。后期补。
7.11.6 优缺点
7.11.6.1 优点
-
装饰类和被装饰类可以独立发展,而不会相互耦合。换句话说,Component类无须知道Decorator类,Decorator类是从外部来扩展Component类的功能,而Decorator也不用知道具体的构件(因为只要写增强代码即可,即在具体构件执行的前后增加代码即可)。
-
装饰模式是继承关系的一个替代方案。我们看装饰类Decorator,不管装饰多少层,返回的对象还是Component,实现的还是is-a的关系。
-
扩展性非常好。装饰模式可以动态地扩展一个实现类的功能,这不需要多说,装饰模式的定义就是如此。
7.11.6.2 缺点
记住一点就足够了:多层的装饰是比较复杂的。为什么会复杂呢?你想想看,就像剥洋葱一样,你剥到了最后才发现是最里层的装饰出现了问题,想象一下工作量吧,因此,尽量减少装饰类的数量,以便降低系统的复杂度。
7.12 策略模式
7.12.1 定义
策略模式(Strategy Pattern)是一种比较简单的模式,也叫做政策模式(PolicyPattern)。其定义如下:
Define a family of algorithms,encapsulate each one,and make them interchangeable.
定义一组算法,将每个算法都封装起来,并且使它们之间可以互换。
简单说,就是一个算法,有多种实现类,然后再创建出一个类,来存储一种算法实现。换算法实现的时候,只要set改程新的算法实现类即可。
7.12.2 通用类图
7.12.3 角色
-
Context封装角色
它也叫做上下文角色,起承上启下封装作用,屏蔽高层模块对策略、算法的直接访问,封装可能存在的变化。
-
Strategy抽象策略角色
策略、算法家族的抽象,通常为接口,定义每个策略或算法必须具有的方法和属性。各位看官可能要问了,类图中的AlgorithmInterface是什么意思,嘿嘿,algorithm是“运算法则”的意思,结合起来意思就明白了吧。
-
ConcreteStrategy具体策略角色
实现抽象策略中的操作,该类含有具体的算法。
7.12.4 通用代码
抽象的策略角色
public interface Strategy {
//策略模式的运算法则
public void doSomething();
}
具体策略角色
//实现1
public class ConcreteStrategy1 implements Strategy {
public void doSomething() {
System.out.println("具体策略1的运算法则");
}
}
//实现2
public class ConcreteStrategy2 implements Strategy {
public void doSomething() {
System.out.println("具体策略2的运算法则");
}
}
封装角色
public class Context {
//抽象策略
private Strategy strategy = null;
//构造函数设置具体策略
public Context(Strategy _strategy){
this.strategy = _strategy;
}
//封装后的策略方法
public void doAnythinig(){
this.strategy.doSomething();
}
}
高层模块(场景类)
public class Client {
public static void main(String[] args) {
//声明一个具体的策略
Strategy strategy = new ConcreteStrategy1();
//声明上下文对象
Context context = new Context(strategy);
//执行封装后的方法
context.doAnythinig();
//更换策略
context = new Context(new ConcreteStrategy2());
//执行
context.doAnything();
}
}
7.12.5 具体示例
7.12.5.1 具体示例1
略。
7.12.5.2 具体示例2
先给出一道小学的题目:输入3个参数,进行加减法运算,参数中两个是int型的,剩下的一个参数是String型的,只有“+”、“-”两个符号可以选择,不要考虑什么复杂的校验,我们做的是白箱测试,输入的就是标准的int类型和合规的String类型。使用策略模式。
抽象策略角色
interface Calculator {
public int exec(int a,int b);
}
具体策略
public class Add implements Calculator {
//加法运算
public int exec(int a, int b) {
return a+b;
}
}
public class Sub implements Calculator {
//减法运算
public int exec(int a, int b) {
return a-b;
}
}
上下文
public class Context {
private Calculator cal = null;
public Context(Calculator _cal){
this.cal = _cal;
}
public int exec(int a,int b,String symbol){
return this.cal.exec(a, b);
}
}
场景类
public class Client {
//加符号
public final static String ADD_SYMBOL = "+";
//减符号
public final static String SUB_SYMBOL = "-";
public static void main(String[] args) {
//输入的两个参数是数字
int a = Integer.parseInt(args[0]);
String symbol = args[1]; //符号
int b = Integer.parseInt(args[2]);
System.out.println("输入的参数为:"+Arrays.toString(args));
//上下文
Context context = null;
//判断初始化哪一个策略
if(symbol.equals(ADD_SYMBOL)){
context = new Context(new Add());
}else if(symbol.equals(SUB_SYMBOL)){
context = new Context(new Sub());
}
System.out.println("运行结果为:"+a+symbol+b+"="+context.exec(a,b,symbol));
}
}
这里,使用扩展的策略枚举更好!!!。
7.12.6 优缺点
7.12.6.1 优点
-
算法可以自由切换
这是策略模式本身定义的,只要实现抽象策略,它就成为策略家族的一个成员,通过封装角色对其进行封装,保证对外提供“可自由切换”的策略。
-
避免使用多重条件判断
如果没有策略模式,我们想想看会是什么样子?一个策略家族有5个策略算法,一会要使用A策略,一会要使用B策略,怎么设计呢?使用多重的条件语句?多重条件语句不易维护,而且出错的概率大大增强。使用策略模式后,可以由其他模块决定采用何种策略,策略家族对外提供的访问接口就是封装类,简化了操作,同时避免了条件语句判断。
-
扩展性良好
这甚至都不用说是它的优点,因为它太明显了。在现有的系统中增加一个策略太容易了,只要实现接口就可以了,其他都不用修改,类似于一个可反复拆卸的插件,这大大地符合了OCP原则。
7.12.6.2 缺点
-
策略类数量增多,即类爆炸
每一个策略都是一个类,复用的可能性很小,类数量增多。
-
所有的策略类都需要对外暴露
上层模块必须知道有哪些策略,然后才能决定使用哪一个策略(上层模块中需要知道具体new哪个具体策略实现类),这与迪米特法则是相违背的,我只是想使用了一个策略,我凭什么就要了解这个策略呢?那要你的封装类还有什么意义?这是原装策略模式的一个缺点,幸运的是,我们可以使用其他模式来修正这个缺陷,如工厂方法模式、代理模式或享元模式。
7.12.7 策略模式的使用场景
-
多个类只有在算法或行为上稍有不同的场景。
-
算法需要自由切换的场景。
例如,算法的选择是由使用者决定的,或者算法始终在进化,特别是一些站在技术前沿的行业,连业务专家都无法给你保证这样的系统规则能够存在多长时间,在这种情况下策略模式是你最好的助手。
-
需要屏蔽算法规则的场景。
现在的科技发展得很快,人脑的记忆是有限的(就目前来说是有限的),太多的算法你只要知道一个名字就可以了,传递相关的数字进来,反馈一个运算结果,万事大吉。
7.12.8 策略模式的扩展
策略枚举!!! 略。
7.13 适配器模式
7.13.1 定义
Convert the interface of a class into another interface clients expect.Adapter lets classes worktogether that couldn't otherwise because of incompatible interfaces.
将一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口不匹配而无法在一起工作的两个类能够在一起工作。
简单说,将同一个事物的不同抽象柔和在一起,如两个用户类UserBean,两个公司的抽象实现不同,但存储的数据是一样的,那么创建出一个适配器,继承这两个类(或者一个实现一个继承),然后将其中一种抽象融合进另一种(怎么融合?其实就是解析其中一个类中的数据)。
适配器模式又叫做变压器模式,也叫做包装模式(Wrapper),但是包装模式可不止一个,还包括了第17章讲解的装饰模式。
7.13.2 通用类图
7.13.3 角色
-
Target目标角色
该角色定义把其他类转换为何种接口,也就是我们的期望接口。
-
Adaptee源角色
你想把谁转换成目标角色,这个“谁”就是源角色,它是已经存在的、运行良好的类或对象,经过适配器角色的包装,它会成为一个崭新、靓丽的角色。
-
Adapter适配器角色
适配器模式的核心角色,其他两个角色都是已经存在的角色,而适配器角色是需要新建立的,它的职责非常简单:把源角色转换为目标角色,怎么转换?通过继承或是类关联的方式
7.13.4通用代码
略
7.13.5 具体示例
需求
人员信息管理的对象是所有员工的所有信息。一个公司的设计如下。非常简单,有一个对象UserInfo存储用户的所有信息(实际系统上还有很多子类,不多说了),也就是BO(Business Object,业务对象)。类图以及代码如下。
//人员信息接口
public interface IOuterUser {
//基本信息,比如名称、性别、手机号码等
public Map getUserBaseInfo();
//工作区域信息
public Map getUserOfficeInfo();
//用户的家庭信息
public Map getUserHomeInfo();
}
//实现
public class OuterUser implements IOuterUser {
/*
* 用户的基本信息
*/
public Map getUserBaseInfo() {
HashMap baseInfoMap = new HashMap();
baseInfoMap.put("userName", "这个员工叫混世魔王...");
baseInfoMap.put("mobileNumber", "这个员工电话是...");
return baseInfoMap;
}
/*
* 员工的家庭信息
*/
public Map getUserHomeInfo() {
HashMap homeInfo = new HashMap();
homeInfo.put("homeTelNumbner", "员工的家庭电话是...");
homeInfo.put("homeAddress", "员工的家庭地址是...");
return homeInfo;
}
/*
* 员工的工作信息,比如,职位等
*/
public Map getUserOfficeInfo() {
HashMap officeInfo = new HashMap();
officeInfo.put("jobPosition","这个人的职位是BOSS...");
officeInfo.put("officeTelNumber", "员工的办公电话是...");
return officeInfo;
}
}
现在,要让两个系统/实现(存储了数据,在不同的公司中) 进行交互,怎么办?我们不可能为了这一小小的功能而对任何一方已经运行良好系统进行大手术,那怎么办?我们可以转化,先拿到对方的数据对象,然后转化为我们自己的数据对象,中间加一层转换处理,这就是适配器模式。
类图
代码
中转角色
public class OuterUserInfo extends OuterUser implements IUserInfo {
private Map baseInfo = super.getUserBaseInfo(); //员工的基本信息
private Map homeInfo = super.getUserHomeInfo(); //员工的家庭信息
private Map officeInfo = super.getUserOfficeInfo(); //工作信息
//以下方法,是将另一个类融入另一个类的对应方法中,即解析另一个类存储的数据,解析完后以当前类的设计进行存储
/*
* 家庭地址
*/
public String getHomeAddress() {
String homeAddress = (String)this.homeInfo.get("homeAddress");
System.out.println(homeAddress);
return homeAddress;
}
/*
* 家庭电话号码
*/
public String getHomeTelNumber() {
String homeTelNumber = (String)this.homeInfo.get("homeTelNumber");
System.out.println(homeTelNumber);
return homeTelNumber;
}
/*
*职位信息
*/
public String getJobPosition() {
String jobPosition = (String)this.officeInfo.get("jobPosition");
System.out.println(jobPosition);
return jobPosition;
}
/*
* 手机号码
*/
public String getMobileNumber() {
String mobileNumber = (String)this.baseInfo.get("mobileNumber");
System.out.println(mobileNumber);
return mobileNumber;
}
/*
* 办公电话
*/
public String getOfficeTelNumber() {
String officeTelNumber = (String)this.officeInfo.get("officeTelNumber");
System.out.println(officeTelNumber);
return officeTelNumber;
}
/*
* 员工的名称
*/
public String getUserName() {
String userName = (String)this.baseInfo.get("userName");
System.out.println(userName);
return userName;
}
}
场景类
//查看原本类中存储的信息
public class Client {
public static void main(String[] args) {
//没有与外系统连接的时候,是这样写的
IUserInfo youngGirl = new UserInfo();
//从数据库中查到101个
for(int i=0;i<101;i++){
youngGirl.getMobileNumber();
}
}
}
//查看存储在另一个类中的人员信息场景
public class Client {
public static void main(String[] args) {
//老板一想不对呀,兔子不吃窝边草,还是找借用人员好点
//我们只修改了这句话
IUserInfo youngGirl = new OuterUserInfo();
//从数据库中查到101个
for(int i=0;i<101;i++){
youngGirl.getMobileNumber();
}
}
}
7.14 迭代器模式
7.14.1 定义
Provide a way to access the elements of an aggregate object sequentially without exposing itsunderlying representation.
它提供一种方法访问一个容器对象中各个元素,而又不需暴露该对象的内部细节。(优点)
简单说,就是创建当前容器对象存储在迭代器中,进行遍历等操作。
7.14.2 通用类图
7.14.3 角色
-
Iterator抽象迭代器
抽象迭代器负责定义访问和遍历元素的接口,而且基本上是有固定的3个方法:first()获得第一个元素,next()访问下一个元素,isDone()是否已经访问到底部(Java叫做hasNext()方法)。
-
ConcreteIterator具体迭代器
具体迭代器角色要实现迭代器接口,完成容器元素的遍历。
-
Aggregate抽象容器
容器角色负责提供创建具体迭代器角色的接口,必然提供一个类似createIterator()这样的方法,在Java中一般是iterator()方法。
-
Concrete Aggregate具体容器
具体容器实现容器接口定义的方法,创建出容纳迭代器的对象。
7.14.4 通用代码
抽象迭代器
public interface Iterator {
//遍历到下一个元素
public Object next();
//是否已经遍历到尾部
public boolean hasNext();
//删除当前指向的元素
public boolean remove();
}
具体迭代器
public class ConcreteIterator implements Iterator {
private Vector vector = new Vector();
//定义当前游标
public int cursor = 0;
@SuppressWarnings("unchecked")
public ConcreteIterator(Vector _vector){
this.vector = _vector;
}
//判断是否到达尾部
public boolean hasNext() {
if(this.cursor == this.vector.size()){
return false;
}else{
return true;
}
}
//返回下一个元素
public Object next() {
Object result = null;
if(this.hasNext()){
result = this.vector.get(this.cursor++);
}else{
result = null;
}
return result;
}
//删除当前元素
public boolean remove() {
this.vector.remove(this.cursor);
return true;
}
}
-
开发系统时,迭代器的删除方法应该完成两个逻辑:一是删除当前元素,二是当前游标指向下一个元素
抽象容器
public interface Aggregate {
//是容器必然有元素的增加
public void add(Object object);
//减少元素
public void remove(Object object);
//由迭代器来遍历所有的元素
public Iterator iterator();
}
具体容器
public class ConcreteAggregate implements Aggregate {
//容纳对象的容器
private Vector vector = new Vector();
//增加一个元素
public void add(Object object) {
this.vector.add(object);
}
//返回迭代器对象
public Iterator iterator() {
return new ConcreteIterator(this.vector);
}
//删除一个元素
public void remove(Object object) {
this.remove(object);
}
}
场景类
public class Client {
public static void main(String[] args) {
//声明出容器
Aggregate agg = new ConcreteAggregate();
//产生对象数据放进去
agg.add("abc");
agg.add("aaa");
agg.add("1234");
//遍历一下
Iterator iterator = agg.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}
}
}
太经典了,其他的不多说。
7.15 组合模式
7.15.1 定义
Compose objects into tree structures to represent part-whole hierarchies.Composite lets clientstreat individual objects and compositions of objects uniformly.
将对象组合成树形结构以表示“部分-整体”的层次结构,使得用户对单个对象和组合对象的使用具有一致性。
7.15.2 通用类图
7.15.3 角色
-
Component抽象构件角色
定义参加组合对象的共有方法和属性,可以定义一些默认的行为或属性,比如我们例子中的getInfo就封装到了抽象类中。
-
Leaf叶子构件
叶子对象,其下再也没有其他的分支,也就是遍历的最小单位。
-
Composite树枝构件
树枝对象,它的作用是组合树枝节点和叶子节点形成一个树形结构。
7.15.4 通用代码
抽象构件
public abstract class Component {
//个体和整体都具有的共享
public void doSomething(){
//编写业务逻辑
}
}
树枝构件
public class Composite extends Component {
//构件容器
private ArrayList<Component> componentArrayList = new ArrayList<Component>();
//增加一个叶子构件或树枝构件
public void add(Component component){
this.componentArrayList.add(component);
}
//删除一个叶子构件或树枝构件
public void remove(Component component){
this.componentArrayList.remove(component);
}
//获得分支下的所有叶子构件和树枝构件
public ArrayList<Component> getChildren(){
return this.componentArrayList;
}
}
-
组合模式的重点就在树枝构件
树叶构件
public class Leaf extends Component {
/*
* 可以覆写父类方法
* public void doSomething(){
*
* }
*/
}
场景类
public class Client {
public static void main(String[] args) {
//创建一个根节点
Composite root = new Composite();
root.doSomething();
//创建一个树枝构件
Composite branch = new Composite();
//创建一个叶子节点
Leaf leaf = new Leaf();
//建立整体
root.add(branch);
branch.add(leaf);
}
//通过递归遍历树
public static void display(Composite root){
for(Component c:root.getChildren()){
if(c instanceof Leaf){ //叶子节点
c.doSomething();
}else{ //树枝节点
display((Composite)c);
}
}
}
}
-
组合模式是对依赖倒转原则的破坏,但是它还有其他类型的变形,面向对象就是这么多的形态和变化
7.15.5 具体实例
略。
7.15.6 优缺点
优点
-
高层模块调用简单(结构简单)
一棵树形机构中的所有节点都是Component,局部和整体对调用者来说没有任何区别,也就是说,高层模块不必关心自己处理的是单个对象还是整个组合结构,简化了高层模块的代码。
-
节点自由增加
使用了组合模式后,我们可以看看,如果想增加一个树枝节点、树叶节点是不是都很容易,只要找到它的父节点就成,非常容易扩展,符合开闭原则,对以后的维护非常有利。
缺点
组合模式有一个非常明显的缺点,看到我们在场景类中的定义,提到树叶和树枝使用时的定义了吗?直接使用了实现类!这在面向接口编程上是很不恰当的,与依赖倒置原则冲突
7.16.7 组合模式的使用场景
● 维护和展示部分-整体关系的场景,如树形菜单、文件和文件夹管理。
● 从一个整体中能够独立出部分模块或功能的场景。
7.16.8 组合模式的扩展
真实组合模式:使用关系型数据库来存储这些信息,你可以从数据库中直接提取出哪些人要分配到树枝,哪些人要分配到树叶,树枝与树枝、树叶的关系等
安全组合模式:就是增、删节点的操作在树枝构件中定义
透明组合模式:就是增、删节点的操作直接在抽象构建中定义
组合模式的遍历。
详细略。
7.16 观察者模式
7.16.1 定义
观察者模式(Observer Pattern)也叫做发布订阅模式(Publish/subscribe),它是一个在项目中经常使用的模式,其定义如下:
Define a one-to-many dependency between objects so that when one object changes state,all itsdependents are notified and updated automatically.
定义对象间一种一对多的依赖关系,使得每当一个对象改变状态,则所有依赖于它的对象都会得到通知并被自动更新。
简单说,就是被观察者中存储了观察者对象(可以一个或者数组属性存多个),当被观察者的某个方法执行时,会通知其观察者,就是调用观察者的方法。
7.16.2 通用类图
7.16.3 角色与通用代码
被观察者
public abstract class Subject {
//定义一个观察者数组
private Vector<Observer> obsVector = new Vector<Observer>();
//增加一个观察者
public void addObserver(Observer o){
this.obsVector.add(o);
}
//删除一个观察者
public void delObserver(Observer o){
this.obsVector.remove(o);
}
//通知所有观察者,就是调用观察者的方法
public void notifyObservers(){
for(Observer o:this.obsVector){
o.update();
}
}
}
具体被观察者
public class ConcreteSubject extends Subject {
//具体的业务
public void doSomething(){
/*
* do something
*/
//执行完被观察者自身的行为后,通知观察者
//通知观察者
super.notifyObservers();
}
}
观察者
public interface Observer {
//更新方法
public void update();
}
具体观察者
public class ConcreteObserver implements Observer {
//实现更新方法
public void update() {
System.out.println("接收到信息,并进行处理!");
}
}
场景类
public class Client {
public static void main(String[] args) {
//创建一个被观察者
ConcreteSubject subject = new ConcreteSubject();
//定义一个观察者
Observer obs= new ConcreteObserver();
//添加观察者,即观察者去观察被观察者
subject.addObserver(obs);
//观察者开始活动了
subject.doSomething();
}
}
7.16.4 具体实例
李斯等人派人监视韩非子
类图
代码实现
被观察者接口
public interface Observable {
//增加一个观察者
public void addObserver(Observer observer);
//删除一个观察者
public void deleteObserver(Observer observer);
//既然要观察,我发生改变了他也应该有所动作,通知观察者
public void notifyObservers(String context);
}
被观察者实现类
public class HanFeiZi implements Observable ,IHanFeiZi{
//定义个变长数组,存放所有的观察者
private ArrayList<Observer> observerList = new ArrayList<Observer>();
//增加观察者
public void addObserver(Observer observer){
this.observerList.add(observer);
}
//删除观察者
public void deleteObserver(Observer observer){
this.observerList.remove(observer);
}
//通知所有的观察者
public void notifyObservers(String context){
for(Observer observer:observerList){
observer.update(context);
}
}
//韩非子要吃饭了,被监视
public void haveBreakfast(){
System.out.println("韩非子:开始吃饭了...");
//通知所有的观察者
this.notifyObservers("韩非子在吃饭");
}
//韩非子开始娱乐了,被监视
public void haveFun(){
System.out.println("韩非子:开始娱乐了...");
//通知所有的观察者
this.notifyObservers("韩非子在娱乐");
}
}
观察者接口
public interface Observer {
//一但发现被观察者有动静,自己也要行动起来
public void update(String context);
}
具体的观察者
public class LiSi implements Observer{
//首先李斯是个观察者,一旦韩非子有活动,他就知道,他就要向老板汇报
public void update(String str){
System.out.println("李斯:观察到韩非子活动,开始向老板汇报了...");
this.reportToQinShiHuang(str);
System.out.println("李斯:汇报完毕...\n");
}
//汇报给秦始皇
private void reportToQinShiHuang(String reportContext){
System.out.println("李斯:报告,秦老板!韩非子有活动了-->"+reportContext);
}
}
public class WangSi implements Observer{
//王斯,看到韩非子有活动
public void update(String str){
System.out.println("王斯:观察到韩非子活动,自己也开始活动了...");
this.cry(str);
System.out.println("王斯:哭死了...\n");
}
//一看韩非子有活动,他就痛哭
private void cry(String context){
System.out.println("王斯:因为"+context+",--所以我悲伤呀!");
}
}
public class LiuSi implements Observer{
//刘斯,观察到韩非子活动后,自己也得做一些事
public void update(String str){
System.out.println("刘斯:观察到韩非子活动,开始动作了...");
this.happy(str);
System.out.println("刘斯:乐死了\n");
}
//一看韩非子有变化,他就快乐
private void happy(String context){
System.out.println("刘斯:因为" +context+",--所以我快乐呀!" );
}
}
场景类
public class Client {
public static void main(String[] args) {
//三个观察者产生出来
Observer liSi = new LiSi();
Observer wangSi = new WangSi();
Observer liuSi = new LiuSi();
//定义出韩非子
HanFeiZi hanFeiZi = new HanFeiZi();
//我们后人根据历史,描述这个场景,有三个人在观察韩非子
hanFeiZi.addObserver(liSi);
hanFeiZi.addObserver(wangSi);
hanFeiZi.addObserver(liuSi);
//然后这里我们看看韩非子在干什么
hanFeiZi.haveBreakfast();
}
}
运行结果如下所示:
韩非子:开始吃饭了...
李斯:观察到韩非子活动,开始向老板汇报了...
李斯:报告,秦老板!韩非子有活动了--->韩非子在吃饭
李斯:汇报完毕...
王斯:观察到韩非子活动,自己也开始活动了...
王斯:因为韩非子在吃饭——所以我悲伤呀!
王斯:哭死了...
刘斯:观察到韩非子活动,开始动作了...
刘斯:因为韩非子在吃饭——所以我快乐呀!
7.16.5 优缺点
优点
-
观察者和被观察者之间是抽象耦合
如此设计,则不管是增加观察者还是被观察者都非常容易扩展,而且在Java中都已经实现的抽象层级的定义,在系统扩展方面更是得心应手。
-
建立一套触发机制
根据单一职责原则,每个类的职责是单一的,那么怎么把各个单一的职责串联成真实世界的复杂的逻辑关系呢?比如,我们去打猎,打死了一只母鹿,母鹿有三个幼崽,因失去了母鹿而饿死,尸体又被两只秃鹰争抢,因分配不均,秃鹰开始斗殴,然后羸弱的秃鹰死掉,生存下来的秃鹰,则因此扩大了地盘……这就是一个触发机制,形成了一个触发链。观察者模式可以完美地实现这里的链条形式。
缺点
开发效率和运行效率可能存在问题,一个被观察者,多个观察者,开发和调试就会比较复杂,而且在Java中消息的通知默认是顺序执行,一个观察者卡壳,会影响整体的执行效率。在这种情况下,一般考虑采用异步的方式。
7.16.6 观察者模式的使用场景
-
关联行为场景。需要注意的是,关联行为是可拆分的,而不是“组合”关系。
-
事件多级触发场景。
-
跨系统的消息交换场景,如消息队列的处理机制。
7.16.7 观察者模式的扩展
7.16.7.1 Java中的观察者模式
Java从一开始诞生就提供了一个可扩展的父类,即java.util.Observable作为抽象固定行为类,定义好了增、删观察者等方法。
//优化后的被观察者
public class HanFeiZi extends Observable,IHanFeiZi{
//韩非子要吃饭了
public void haveBreakfast(){
System.out.println("韩非子:开始吃饭了...");
//通知所有的观察者
super.setChanged();
super.notifyObservers("韩非子在吃饭");
}
//韩非子开始娱乐了
public void haveFun(){
System.out.println("韩非子:开始娱乐了...");
//通知所有的观察者
super.setChanged();
this.notifyObservers("韩非子在娱乐");
}
}
//优化后的观察者
public class LiSi implements Observer{
//首先李斯是个观察者,一旦韩非子有活动,他就知道,他就要向老板汇报
public void update(Observable observable,Object obj){
System.out.println("李斯:观察到韩非子活动,开始向老板汇报了...");
this.reportToQinShiHuang(obj.toString());
System.out.println("李斯:汇报完毕...\n");
}
//汇报给秦始皇
private void reportToQinShiHuang(String reportContext){
System.out.println("李斯:报告,秦老板!韩非子有活动了--->"+reportContext);
}
}
7.16.7.2 项目中真实的观察者模式
为什么要说“真实”呢?因为我们刚刚讲的那些是太标准的模式了,在系统设计中会对观察者模式进行改造或改装,主要在以下3个方面。
-
观察者和被观察者之间的消息沟通
被观察者状态改变会触发观察者的一个行为,同时会传递一个消息给观察者,这是正确的,在实际中一般的做法是:观察者中的update方法接受两个参数,一个是被观察者,一个是DTO(Data Transfer Object,据传输对象),DTO一般是一个纯洁的JavaBean,由被观察者生成,由观察者消费。当然,如果考虑到远程传输,一般消息是以XML格式传递。
-
观察者响应方式
我们这样来想一个问题,观察者是一个比较复杂的逻辑,它要接受被观察者传递过来的信息,同时还要对他们进行逻辑处理,在一个观察者多个被观察者的情况下,性能就需要提到日程上来考虑了,为什么呢?如果观察者来不及响应,被观察者的执行时间是不是也会被拉长?那现在的问题就是:观察者如何快速响应?有两个办法:一是采用多线程技术,甭管是被观察者启动线程还是观察者启动线程,都可以明显地提高系统性能,这也就是大家通常所说的异步架构;二是缓存技术,甭管你谁来,我已经准备了足够的资源给你了,我保证快速响应,这当然也是一种比较好方案,代价就是开发难度很大,而且压力测试要做的足够充分,这种方案也就是大家说的同步架构。
-
被观察者尽量自己做主
这是什么意思呢?被观察者的状态改变是否一定要通知观察者呢?不一定吧,在设计的时候要灵活考虑,否则会加重观察者的处理逻辑,一般是这样做的,对被观察者的业务逻辑doSomething方法实现重载,如增加一个doSomething(boolean isNotifyObs)方法,决定是否通知观察者,而不是在消息到达观察者时才判断是否要消费。
7.16.7.3 订阅发布模型
观察者模式也叫做发布/订阅模型(Publish/Subscribe),如果你做过EJB(EnterpriseJavaBean)的开发,这个你绝对不会陌生。EJB2是个折腾死人不偿命的玩意儿,写个Bean要实现,还要继承,再加上那一堆的配置文件,小项目还凑合,你要知道用EJB开发的基本上都不是小项目,到最后是每个项目成员都在骂EJB这个忽悠人的东西;但是EJB3是个非常优秀的框架,还是算比较轻量级,写个Bean只要加个Annotaion就成了,配置文件减少了,而且也引入了依赖注入的概念,虽然只是EJB2的翻版,但是毕竟还是前进了一步。在EJB中有3个类型的Bean: Session Bean、Entity Bean和MessageDriven Bean,我们这里来说一下MessageDriven Bean(一般简称为MDB),消息驱动Bean,消息的发布者(Provider)发布一个消息,也就是一个消息驱动Bean,通过EJB容器(一般是Message Queue消息队列)通知订阅者做出回应,从原理上看很简单,就是观察者模式的升级版,或者说是观察则模式的BOSS版。
7.16.8 最佳实践
观察者模式在实际项目和生活中非常常见,我们举几个经常发生的例子来说明。
-
文件系统
比如,在一个目录下新建立一个文件,这个动作会同时通知目录管理器增加该目录,并通知磁盘管理器减少1KB的空间,也就说“文件”是一个被观察者,“目录管理器”和“磁盘管理器”则是观察者。
-
猫鼠游戏
夜里猫叫一声,家里的老鼠撒腿就跑,同时也吵醒了熟睡的主人,这个场景中,“猫”就是被观察者,老鼠和人则是观察者。
-
ATM取钱
比如你到ATM机器上取钱,多次输错密码,卡就会被ATM吞掉,吞卡动作发生的时候,会触发哪些事件呢?第一,摄像头连续快拍,第二,通知监控系统,吞卡发生;第三,初始化ATM机屏幕,返回最初状态。一般前两个动作都是通过观察者模式来完成的,后一个动作是异常来完成。
-
广播收音机
电台在广播,你可以打开一个收音机,或者两个收音机来收听,电台就是被观察者,收音机就是观察者。
7.17 门面模式
7.17.1 定义
门面模式(Facade Pattern)也叫做外观模式,是一种比较常用的封装模式,其定义如下:
Provide a unified interface to a set of interfaces in a subsystem.Facade defines a higher-levelinterface that makes the subsystem easier to use.
要求一个子系统的外部与其内部的通信必须通过一个统一的对象进行。门面模式提供一个高层次的接口,使得子系统更易于使用。
简单说,就是将多个内部的接口进行封装,外部只要调用这个封装方法,提供简单信息(参数)即可,其他复杂的操作在封装方法内部进行。
7.17.2 通用类图
类图就这么简单,但是它代表的意义可是异常复杂,Subsystem Classes是子系统所有类的简称,它可能代表一个类,也可能代表几十个对象的集合。甭管多少对象,我们把这些对象全部圈入子系统的范畴。
再简单地说,门面对象是外界访问子系统内部的唯一通道,不管子系统内部是多么杂乱无章,只要有门面对象在,就可以做到“金玉其外,败絮其中”
7.17.3 角色
-
Facade门面角色
客户端可以调用这个角色的方法。此角色知晓子系统的所有功能和责任。一般情况下,本角色会将所有从客户端发来的请求委派到相应的子系统去,也就说该角色没有实际的业务逻辑,只是一个委托类。
-
subsystem子系统角色
可以同时有一个或者多个子系统。每一个子系统都不是一个单独的类,而是一个类的集合。子系统并不知道门面的存在。对于子系统而言,门面仅仅是另外一个客户端而已。
7.17.4 通用代码
子系统
//子系统1
public class ClassA {
public void doSomethingA(){
//业务逻辑
}
}
//子系统2
public class ClassB {
public void doSomethingB(){
//业务逻辑
}
}
//子系统3
public class ClassC {
public void doSomethingC(){
//业务逻辑
}
}
门面对象
public class Facade {
//被委托的对象
private ClassA a = new ClassA();
private ClassB b = new ClassB();
private ClassC c = new ClassC();
//提供给外部访问的方法
public void methodA(){
this.a.doSomethingA();
}
public void methodB(){
this.b.doSomethingB();
}
public void methodC(){
this.c.doSomethingC();
}
//其实一般是多个子系统的联调
public void methodOther(){
this.a.doSomething();
this.b.doSomething();
//doSomething
this.c.doSomething();
}
}
-
当要扩展的子系统的时候,只要新增子系统类,然后与门面对象关联,即成为它的属性即可。
7.17.5 具体示例
略。原书挺好,值得一看。
7.17.6 优缺点
7.17.6.1 优点
-
减少系统的相互依赖
想想看,如果我们不使用门面模式,外界访问直接深入到子系统内部,相互之间是一种强耦合关系,你死我就死,你活我才能活,这样的强依赖是系统设计所不能接受的,门面模式的出现就很好地解决了该问题,所有的依赖都是对门面对象的依赖,与子系统无关。
-
提高了灵活性
依赖减少了,灵活性自然提高了。不管子系统内部如何变化,只要不影响到门面对象,任你自由活动。
-
提高安全性
想让你访问子系统的哪些业务就开通哪些逻辑,不在门面上开通的方法,你休想访问到。
7.17.6.2 缺点
-
不符合开闭原则
门面模式是对修改关闭,对扩展开放,完全更开闭原则反过来。
看看我们那个门面对象吧,它可是重中之重,一旦在系统投产后发现有一个小错误,你怎么解决?完全遵从开闭原则,根本没办法解决。继承?覆写?都顶不上用,唯一能做的一件事就是修改门面角色的代码,这个风险相当大,这就需要大家在设计的时候慎之又慎,多思考几遍才会有好收获
7.17.7 使用场景
-
为一个复杂的模块或子系统提供一个供外界访问的接口
-
子系统相对独立——外界对子系统的访问只要黑箱操作即可
比如利息的计算问题,没有深厚的业务知识和扎实的技术水平是不可能开发出该子系统的,但是对于使用该系统的开发人员来说,他需要做的就是输入金额以及存期,其他的都不用关心,返回的结果就是利息,这时候,门面模式是非使用不可了。只需要调用门面对象的方法,提供简单参数,不用管底层api的调用和i其他复杂参数的提供。
-
预防低水平人员带来的风险扩散
比如一个低水平的技术人员参与项目开发,为降低个人代码质量对整体项目的影响风险,一般的做法是“画地为牢”,只能在指定的子系统中开发,然后再提供门面接口进行访问操作。
7.18 备忘录模式
7.18.1 定义
Without violating encapsulation,capture and externalize an object's internal state so that theobject can be restored to this state later.
在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。
简单说,就是将一个对象的你想 事先存储/备份 的一些状态抽取出来,存储到一个如bean类中,然后为了符合迪米特法则,再创一个管理类来管理这些备份的状态bean。
7.18.2 通用类图
7.18.3 角色
-
Originator发起人角色
记录当前时刻的内部状态,负责定义哪些属于备份范围的状态,负责创建和恢复备忘录数据。
-
Memento备忘录角色
负责存储Originator发起人对象的内部状态,在需要的时候提供发起人需要的内部状态。
-
Caretaker备忘录管理员角色
对备忘录进行管理、保存和提供备忘录。
7.18.4 通用代码
发起人角色
public class Originator {
//要备份的内部状态
private String state = "";
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
//创建一个备忘录,其实就是创建一个专门存储该状态的类对象
public Memento createMemento(){
return new Memento(this.state);
}
//恢复一个备忘录,其实就是从 存储状态的类对象中取出存储的状态值
public void restoreMemento(Memento _memento){
this.setState(_memento.getState());
}
}
备忘录角色
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 getMemento() {
return memento;
}
public void setMemento(Memento memento) {
this.memento = memento;
}
}
场景类
public class Client {
public static void main(String[] args) {
//定义出发起人
Originator originator = new Originator();
//定义出备忘录管理员
Caretaker caretaker = new Caretaker();
//创建一个备忘录
caretaker.setMemento(originator.createMemento());
//恢复一个备忘录
originator.restoreMemento(caretaker.getMemento());
}
}
7.18.5 扩展
上面那种最基本的备忘录模式,在实际生产中基本不会用到。
7.18.5.1clone方式的备忘录
后期补,略。
7.18.5.2多状态的备忘录模式
比较麻烦,后期补,略。
7.18.5.3多备份的备忘录模式
把容纳备忘录的容器修改为Map类型就可以了,场景类也稍做改动。以数据备份实例为例。
备忘录管理员
public class Caretaker {
//容纳备忘录的容器
private HashMap<String,Memento> memMap = new HashMap<String,Memento>();
public Memento getMemento(String idx) {
return memMap.get(idx);
}
public void setMemento(String idx,Memento memento) {
this.memMap.put(idx, memento);
}
}
场景类
public class Client {
public static void main(String[] args) {
//定义出发起人
Originator originator = new Originator();
//定义出备忘录管理员
Caretaker caretaker = new Caretaker();
//创建两个备忘录
caretaker.setMemento("001",originator.createMemento());
caretaker.setMemento("002",originator.createMemento());
//恢复一个指定标记的备忘录
originator.restoreMemento(caretaker.getMemento("001"));
}
}
7.18.5.4 多状态多备份的备忘录模式
没空,后期补上笔记,略。
7.19 访问者模式
7.19.1 定义
Represent anoperation to be performed on the elements of an object structure. Visitor lets you define a newoperation without changing the classes of the elements on which it operates.
封装一些作用于某种数据结构中的各元素的操作,它可以在不改变数据结构的前提下定义作用于这些元素的新的操作(定义不同的服务者,对应不同的需求;不改变数据结构,即不在数据实体类中定义具体操作/检索 数据的方法,而是引入访问者依赖(作为方法参数),通过访问者去处理)
7.19.2 通用类图
7.19.3 角色
-
Visitor——抽象访问者
抽象类或者接口,声明访问者可以访问哪些元素,具体到程序中就是visit方法的参数定义哪些对象是可以被访问的。
-
ConcreteVisitor——具体访问者
它影响访问者访问到一个类后该怎么干,要做什么事情。
-
Element——抽象元素
接口或者抽象类,声明接受哪一类访问者访问,程序上是通过accept方法中的参数来定义的。
-
ConcreteElement——具体元素
实现accept方法,通常是visitor.visit(this),基本上都形成了一种模式了。
-
ObjectStruture——结构对象
元素产生者,一般容纳在多个不同类、不同接口的容器,如List、Set、Map等,在项目中,一般很少抽象出这个角色。
7.19.4 通用代码
略
7.19.5 具体示例!
针对公司的员工,有普通员工、管理层人员等,制作报表。不同人需要报表的格式是不一样的。
7.19.5.1 示例1 固定报表
固定报表,即仅1个服务者
类图
访问者接口
public interface IVisitor {
//首先,定义我可以访问普通员工
public void visit(CommonEmployee commonEmployee);
//其次,定义我还可以访问部门经理
public void visit(Manager manager);
}
访问者实现
public class Visitor implements IVisitor {
//访问普通员工,打印出报表
public void visit(CommonEmployee commonEmployee) {
System.out.println(this.getCommonEmployee(commonEmployee));
}
//访问部门经理,打印出报表
public void visit(Manager manager) {
System.out.println(this.getManagerInfo(manager));
}
//组装出基本信息
private String getBasicInfo(Employee employee){
String info = "姓名:" + employee.getName() + "\t";
info = info + "性别:" + (employee.getSex() == Employee.FEMALE?"女":"男") + "\t";
info = info + "薪水:" + employee.getSalary() + "\t";
return info;
}
//组装出部门经理的信息
private String getManagerInfo(Manager manager){
String basicInfo = this.getBasicInfo(manager);
String otherInfo = "业绩:"+manager.getPerformance() + "\t";
return basicInfo + otherInfo;
}
//组装出普通员工信息
private String getCommonEmployee(CommonEmployee commonEmployee){
String basicInfo = this.getBasicInfo(commonEmployee);
String otherInfo = "工作:"+commonEmployee.getJob()+"\t";
return basicInfo + otherInfo;
}
}
抽象员工类
public abstract class Employee {
public final static int MALE = 0; //0代表是男性
public final static int FEMALE = 1; //1代表是女性
//甭管是谁,都有工资
private String name;
//只要是员工那就有薪水
private int salary;
//性别很重要
private int sex;
//以下是简单的getter/setter
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getSalary() {
return salary;
}
public void setSalary(int salary) {
this.salary = salary;
}
public int getSex() {
return sex;
}
public void setSex(int sex) {
this.sex = sex;
}
//我允许一个访问者访问
public abstract void accept(IVisitor visitor);
}
具体员工类
普通员工类
public class CommonEmployee extends Employee {
//工作内容,这非常重要,以后的职业规划就是靠它了
private String job;
public String getJob() {
return job;
}
public void setJob(String job) {
this.job = job;
}
//我允许访问者访问!!!
@Override
public void accept(IVisitor visitor){
visitor.visit(this);//!!!执行特定访问者(对应某种需求),传入自己作为参数(作为数据结构给访问者处理)
}
}
管理层员工
public class Manager extends Employee {
//这类人物的职责非常明确:业绩
private String performance;
public String getPerformance() {
return performance;
}
public void setPerformance(String performance) {
this.performance = performance;
}
//部门经理允许访问者访问
@Override
public void accept(IVisitor visitor){
visitor.visit(this);
}
}
场景类
public class Client {
public static void main(String[] args) {
for(Employee emp:mockEmployee()){
emp.accept(new Visitor());//传入一种访问者需求
}
}
//模拟出公司的人员情况,我们可以想象这个数据是通过持久层传递过来的
public static List<Employee> mockEmployee(){
List<Employee> empList = new ArrayList<Employee>();
//组装员工数据,即数据结构集
//产生张三这个员工
CommonEmployee zhangSan = new CommonEmployee();
zhangSan.setJob("编写Java程序,绝对的蓝领、苦工加搬运工");
zhangSan.setName("张三");
zhangSan.setSalary(1800);
zhangSan.setSex(Employee.MALE);
empList.add(zhangSan);
//产生李四这个员工
CommonEmployee liSi = new CommonEmployee();
liSi.setJob("页面美工,审美素质太不流行了!");
liSi.setName("李四");
liSi.setSalary(1900);
liSi.setSex(Employee.FEMALE);
empList.add(liSi);
//再产生一个经理
Manager wangWu = new Manager();
wangWu.setName("王五");
wangWu.setPerformance("基本上是负值,但是我会拍马屁呀");
wangWu.setSalary(18750);
wangWu.setSex(Employee.MALE);
empList.add(wangWu);
return empList;
}
}
7.19.5.2 示例2 多种报表
即多个服务者,对应多种报表格式的需求。上面的示例的访问者只有一个,现在有多个,同时这访问者需求和示例1的不同,分别是在示例1的基础上进行统计和加上薪资展示。
类图
访问者接口
最上层访问者接口
public interface IVisitor {
//首先,定义我可以访问普通员工
public void visit(CommonEmployee commonEmployee);
//其次,定义我还可以访问部门经理
public void visit(Manager manager);
}
访问者-展示表接口
public interface IShowVisitor extends IVisitor {
//展示报表
public void report();
}
访问者-汇总表接口
public interface ITotalVisitor extends IVisitor {
//统计所有员工工资总和
public void totalSalary();
}
访问者实现
打印的输出就没在visit中了,在单独的方法中。
访问者-具体展示表
public class ShowVisitor implements IShowVisitor {
private String info = "";
//打印出报表
public void report() {
System.out.println(this.info);
}
//访问普通员工,组装信息
public void visit(CommonEmployee commonEmployee) {
this.info = this.info + this.getBasicInfo(commonEmployee)
+ "工作:"+commonEmployee.getJob()+"\t\n";
}
//访问经理,然后组装信息
public void visit(Manager manager) {
this.info = this.info + this.getBasicInfo(manager) + "业绩:
"+manager.getPerformance() + "\t\n";
}
//组装出基本信息
private String getBasicInfo(Employee employee){
String info = "姓名:" + employee.getName() + "\t";
info = info + "性别:" + (employee.getSex() == Employee.FEMALE?"女":
"男") + "\t";
info = info + "薪水:" + employee.getSalary() + "\t";
return info;
}
}
访问者-具体汇总表
public class TotalVisitor implements ITotalVisitor {
//部门经理的工资系数是5
private final static int MANAGER_COEFFICIENT = 5;
//员工的工资系数是2
private final static int COMMONEMPLOYEE_COEFFICIENT = 2;
//普通员工的工资总和
private int commonTotalSalary = 0;
//部门经理的工资总和
private int managerTotalSalary =0;
public void totalSalary() {
System.out.println("本公司的月工资总额是" + (this.commonTotalSalary +
this.managerTotalSalary));
}
//访问普通员工,同时计算工资总额,属性赋上值
public void visit(CommonEmployee commonEmployee) {
this.commonTotalSalary = this.commonTotalSalary + commonEmployee.getSalary() *COMMONEMPLOYEE_COEFFICIENT;
}
//访问部门经理,同时计算工资总额,属性赋上值
public void visit(Manager manager) {
this.managerTotalSalary = this.managerTotalSalary + manager.getSalary() *MANAGER_COEFFICIENT ;
}
}
场景类
public class Client {
public static void main(String[] args) {
//展示报表访问者
IShowVisitor showVisitor = new ShowVisitor();
//汇总报表的访问者
ITotalVisitor totalVisitor = new TotalVisitor();
for(Employee emp : mockEmployee()){
emp.accept(showVisitor); //接受展示报表访问者,作为参数,调用访问者的visit方法,为对应组装信息的属性赋上值
emp.accept(totalVisitor);//接受汇总表访问者
}
//展示报表
showVisitor.report();
//汇总报表
totalVisitor.totalSalary();
}
}
7.20 状态模式
7.20.1 定义
Allow an object to alter its behavior when its internal state changes.The object will appear tochange its class.
当一个对象内在状态改变时允许其改变行为(不同状态对应不同行为),这个对象看起来像改变了其类。
简单说,其实就是封装,将先前一个类中可能出现的多种状态swich(其对应着特定的行为),将这些状态抽取出来成为一个一个类,类中封装着对应该状态下能执行的行为,即方法,然后就需要定义出一个环境类,来统筹这些状态对象的调用。环境类中需要有各个状态类,以便调用,同时还有一个当前状态的属性。而各个状态类中也要有环境类,因为状态的改变是在对应状态类的方法中,此时需要调用在方法体中调用环境类去切换环境。达到一种,环境类执行不同方法,底层是通过在当前状态对象的方法的方法体中去切换环境类中的当前状态对象,然后调用对应要切换的状态对象的方法
7.20.2 通用类图
7.20.3 角色
-
State——抽象状态角色
接口或抽象类,负责对象状态定义,并且封装环境角色以实现状态切换。
-
ConcreteState——具体状态角色
每一个具体状态必须完成两个职责:本状态的行为管理以及趋向状态处理,通俗地说,就是本状态下要做的事情,以及本状态如何过渡到其他状态。
-
Context——环境角色
定义客户端需要的接口,并且负责具体状态的切换。
7.20.4 通用代码
抽象状态角色
public abstract class State {
//定义一个环境角色,提供子类访问!!!
protected Context context;
//设置环境角色
public void setContext(Context _context){
this.context = _context;
}
//行为1
public abstract void handle1();
//行为2
public abstract void handle2();
}
具体状态角色
public class ConcreteState1 extends State {
@Override
public void handle1() {
//本状态下必须处理的逻辑
}
@Override
public void handle2() {
//设置环境类中当前状态属性为stat2
super.context.setCurrentState(Context.STATE2);
//过渡到state2状态,由Context实现
super.context.handle2();
}
}
public class ConcreteState2 extends State {
@Override
public void handle1() {
//设置当前状态为state1
super.context.setCurrentState(Context.STATE1);
//过渡到state1状态,由Context实现
super.context.handle1();
}
@Override
public void handle2() {
//本状态下必须处理的逻辑
}
}
环境角色
public class Context {
//定义状态
public final static State STATE1 = new ConcreteState1();
public final static State STATE2 = new ConcreteState2();
//当前状态
private State CurrentState;
//获得当前状态
public State getCurrentState() {
return CurrentState;
}
//设置当前状态
public void setCurrentState(State currentState) {
this.CurrentState = currentState;
//切换状态
this.CurrentState.setContext(this);
}
//行为委托,委托给当前状态对象
public void handle1(){
this.CurrentState.handle1();
}
public void handle2(){
this.CurrentState.handle2();
}
}
环境角色有两个不成文的约束:
● 把状态对象声明为静态常量,有几个状态对象就声明几个静态常量。
● 环境角色具有状态抽象角色定义的所有行为,具体执行使用委托方式。
场景类
public class Client {
public static void main(String[] args) {
//定义环境角色
Context context = new Context();
//初始化状态
context.setCurrentState(new ConcreteState1());
//行为执行
context.handle1();
context.handle2();
}
}
我们已经隐藏了状态的变化过程,它的切换引起了行为的变化。对外来说,我们只看到行为的发生改变,而不用知道是状态变化引起的。
7.20.5 具体示例
电梯
7.20.5.1不使用状态模式
类图
电梯接口
public interface ILift {
//电梯的4个状态
public final static int OPENING_STATE = 1; //敞门状态
public final static int CLOSING_STATE = 2; //闭门状态
public final static int RUNNING_STATE = 3; //运行状态
public final static int STOPPING_STATE = 4; //停止状态
//设置电梯的状态
public void setState(int state);
//首先电梯门开启动作
public void open();
//电梯门可以开启,那当然也就有关闭了
public void close();
//电梯要能上能下,运行起来
public void run();
//电梯还要能停下来
public void stop();
}
电梯实现类
public class Lift implements ILift {
private int state;
public void setState(int state) {
this.state = state;
}
//电梯门关闭
public void close() {
//电梯在什么状态下才能关闭
switch(this.state){
case OPENING_STATE: //可以关门,同时修改电梯状态
this.closeWithoutLogic();
this.setState(CLOSING_STATE);
break;
case CLOSING_STATE: //电梯是关门状态,则什么都不做
//do nothing;
break;
case RUNNING_STATE: //正在运行,门本来就是关闭的,也什么都不做
//do nothing;
break;
case STOPPING_STATE: //停止状态,门也是关闭的,什么也不做
//do nothing;
break;
}
}
//电梯门开启
public void open() {
//电梯在什么状态才能开启
switch(this.state){
case OPENING_STATE: //闭门状态,什么都不做
//do nothing;
break;
case CLOSING_STATE: //闭门状态,则可以开启
this.openWithoutLogic();
this.setState(OPENING_STATE);
break;
case RUNNING_STATE: //运行状态,则不能开门,什么都不做
//do nothing;
break;
case STOPPING_STATE: //停止状态,当然要开门了
this.openWithoutLogic();
this.setState(OPENING_STATE);
break;
}
}
//电梯开始运行起来
public void run() {
switch(this.state){
case OPENING_STATE: //敞门状态,什么都不做
//do nothing;
break;
case CLOSING_STATE: //闭门状态,则可以运行
this.runWithoutLogic();
this.setState(RUNNING_STATE);
break;
case RUNNING_STATE: //运行状态,则什么都不做
//do nothing;
break;
case STOPPING_STATE: //停止状态,可以运行
this.runWithoutLogic();
this.setState(RUNNING_STATE);
}
}
//电梯停止
public void stop() {
switch(this.state){
case OPENING_STATE: //敞门状态,要先停下来的,什么都不做
//do nothing;
break;
case CLOSING_STATE: //闭门状态,则当然可以停止了
this.stopWithoutLogic();
this.setState(CLOSING_STATE);
break;
case RUNNING_STATE: //运行状态,有运行当然那也就有停止了
this.stopWithoutLogic();
this.setState(CLOSING_STATE);
break;
case STOPPING_STATE: //停止状态,什么都不做
//do nothing;
break;
}
}
//纯粹的电梯关门,不考虑实际的逻辑
private void closeWithoutLogic(){
System.out.println("电梯门关闭...");
}
//纯粹的电梯开门,不考虑任何条件
private void openWithoutLogic(){
System.out.println("电梯门开启...");
}
//纯粹的运行,不考虑其他条件
private void runWithoutLogic(){
System.out.println("电梯上下运行起来...");
}
//单纯的停止,不考虑其他条件
private void stopWithoutLogic(){
System.out.println("电梯停止了...");
}
}
场景类
public class Client {
public static void main(String[] args) {
ILift lift = new Lift();
//电梯的初始条件应该是停止状态
lift.setState(ILift.STOPPING_STATE);
//首先是电梯门开启,人进去
lift.open();
//然后电梯门关闭
lift.close();
//再然后,电梯运行起来,向上或者向下
lift.run();
//最后到达目的地,电梯停下来
lift.stop();
}
}
问题:
-
电梯实现类Lift有点长
使用了大量的switch...case这样的判断(if...else也是一样)!!!
-
扩展性非常差劲
-
比如电梯还有两个状态没有加,是什么?通电状态和断电状态,你要是在程序增加这两个方法,你看看Open()、Close()、Run()、Stop()这4个方法都要增加判断条件,也就是说switch判断体中还要增加case项,这与开闭原则相违背。
-
电梯在门敞开状态下就不能上下运行了吗?电梯有没有发生过只有运行没有停止状态呢(从40层直接坠到1层嘛)?电梯故障嘛,还有电梯在检修的时候,可以在stop状态下不开门,这也是正常的业务需求呀,你想想看,如果加上这些判断条件,上面的程序有多少需要修改?虽然这些都是电梯的业务逻辑,但是一个类有且仅有一个原因引起类的变化,单一职责原则,看看我们的类,业务任务上一个小小的增加或改动都使得我们这个电梯类产生了修改,这在项目开发上是有很大风险的。
-
7.20.5.2使用状态模式
类图
抽象电梯状态
public abstract class LiftState{
//定义一个环境角色,也就是封装状态的变化引起的功能变化
protected Context context;
public void setContext(Context _context){
this.context = _context;
}
//首先电梯门开启动作
public abstract void open();
//电梯门有开启,那当然也就有关闭了
public abstract void close();
//电梯要能上能下,运行起来
public abstract void run();
//电梯还要能停下来
public abstract void stop();
}
具体状态类
//敞门状态
public class OpenningState extends LiftState {
//开启当然可以关闭了,我就想测试一下电梯门开关功能
@Override
public void close() {
//关闭方法由关闭状态执行,则要状态修改,修改为关闭状态
super.context.setLiftState(Context.closeingState);
//动作委托为CloseState来执行,底层就是将环境类中的当前状态对象转换为关闭状态类,然后修改关闭状态类的环境为最新环境,调用关闭状态类的关闭方法
super.context.getLiftState().close();
}
//打开电梯门
@Override
public void open() {
System.out.println("电梯门开启...");
}
//门开着时电梯就运行跑,这电梯,吓死你!
@Override
public void run() {
//do nothing;
}
//开门还不停止?
public void stop() {
//do nothing;
}
}
//敞门状态
public class OpenningState extends LiftState {
//开启当然可以关闭了,我就想测试一下电梯门开关功能
@Override
public void close() {
//状态修改
super.context.setLiftState(Context.closeingState);
//动作委托为CloseState来执行
super.context.getLiftState().close();
}
//打开电梯门
@Override
public void open() {
System.out.println("电梯门开启...");
}
//门开着时电梯就运行跑,这电梯,吓死你!
@Override
public void run() {
//do nothing;
}
//开门还不停止?
public void stop() {
//do nothing;
}
}
上下文类/环境类
public class Context {
//定义出所有的电梯状态
public final static OpenningState openningState = new OpenningState();
public final static ClosingState closeingState = new ClosingState();
public final static RunningState runningState = new RunningState();
public final static StoppingState stoppingState = new StoppingState();
//定义一个当前电梯状态
private LiftState liftState;
public LiftState getLiftState() {
return liftState;
}
public void setLiftState(LiftState liftState) {
this.liftState = liftState;
//把当前的环境通知到各个实现类中
this.liftState.setContext(this);//!!!
}
public void open(){
this.liftState.open();
}
public void close(){
this.liftState.close();
}
public void run(){
this.liftState.run();
}
public void stop(){
this.liftState.stop();
}
}
7.20.6 优缺点
优点
-
结构清晰
避免了过多的switch...case或者if...else语句的使用,避免了程序的复杂性,提高系统的可维护性。
-
遵循设计原则
很好地体现了开闭原则和单一职责原则,每个状态都是一个子类,你要增加状态就要增加子类,你要修改状态,你只修改一个子类就可以了。
-
封装性非常好
这也是状态模式的基本要求,状态变换放置到类的内部来实现,外部的调用不用知道类内部如何实现状态和行为的变换。
缺点
只有一个缺点,即子类会太多,也就是类膨胀/类爆炸。如果一个事物有很多个状态也不稀奇,如果完全使用状态模式就会有太多的子类,不好管理,这个需要大家在项目中自己衡量。其实有很多方式可以解决这个状态问题,如在数据库中建立一个状态表,然后根据状态执行相应的操作,这个也不复杂,看大家的习惯和嗜好了。
7.20.7 状态模式的使用场景
-
行为随状态改变而改变的场景
这也是状态模式的根本出发点,例如权限设计,人员的状态不同即使执行相同的行为结果也会不同,在这种情况下需要考虑使用状态模式。
-
条件、分支判断语句的替代者
在程序中大量使用switch语句或者if判断语句会导致程序结构不清晰,逻辑混乱,使用状态模式可以很好地避免这一问题,它通过扩展子类实现了条件的判断处理。
7.20.8 扩展
结合建造者模式,详见书籍。后期补,略。
7.22 享元模式
(没细致看,后期要再补补细节)
7.22.1 定义
享元模式(Flyweight Pattern)是池技术的重要实现方式,其定义如下:
Use sharing tosupport large numbers of fine-grained objects efficiently.
使用共享对象可有效地支持大量的细粒度的对象。
7.22.2 通用类图
7.22.3 角色
-
Flyweight——抽象享元角色
它简单地说就是一个产品的抽象类,同时定义出对象的外部状态和内部状态的接口或实现。
-
ConcreteFlyweight——具体享元角色
具体的一个产品类,实现抽象角色定义的业务。该角色中需要注意的是内部状态处理应该与环境无关,不应该出现一个操作改变了内部状态,同时修改了外部状态,这是绝对不允许的。
-
unsharedConcreteFlyweight——不可共享的享元角色
不存在外部状态或者安全要求(如线程安全)不能够使用共享技术的对象,该对象一般不会出现在享元工厂中。
-
FlyweightFactory——享元工厂
职责非常简单,就是构造一个池容器,同时提供从池中获得对象的方法。
7.23.4 通用代码
抽象享元角色
public abstract class Flyweight {
//内部状态
private String intrinsic;
//外部状态
protected final String Extrinsic;
//要求享元角色必须接受外部状态
public Flyweight(String _Extrinsic){
this.Extrinsic = _Extrinsic;
}
//定义业务操作
public abstract void operate();
//内部状态的getter/setter
public String getIntrinsic() {
return intrinsic;
}
public void setIntrinsic(String intrinsic) {
this.intrinsic = intrinsic;
}
}
-
抽象享元角色一般为抽象类,在实际项目中,一般是一个实现类,它是描述一类事物的方法。在抽象角色中,一般需要把外部状态和内部状态(当然了,可以没有内部状态,只有行为也是可以的)定义出来,避免子类的随意扩展。
具体享元角色
public class ConcreteFlyweight1 extends Flyweight{
//接受外部状态
public ConcreteFlyweight1(String _Extrinsic){
super(_Extrinsic);
}
//根据外部状态进行逻辑处理
public void operate(){
//业务逻辑
}
}
public class ConcreteFlyweight2 extends Flyweight{
//接受外部状态
public ConcreteFlyweight2(String _Extrinsic){
super(_Extrinsic);
}
//根据外部状态进行逻辑处理
public void operate(){
//业务逻辑
}
}
-
这很简单,实现自己的业务逻辑,然后接收外部状态,以便内部业务逻辑对外部状态的依赖。注意,我们在抽象享元中对外部状态加上了final关键字,防止意外产生,什么意外?获得了一个外部状态,然后无意修改了一下,池就混乱了!
-
在程序开发中,确认只需要一次赋值的属性则设置为final类型,避免无意修改导致逻辑混乱,特别是Session级的常量或变量。
享元工厂
public class FlyweightFactory {
//定义一个池容器
private static HashMap<String,Flyweight> pool= new HashMap<String,Flyweight>();
//享元工厂
public static Flyweight getFlyweight(String Extrinsic){
//需要返回的对象
Flyweight flyweight = null;
//在池中没有该对象
if(pool.containsKey(Extrinsic)){
flyweight = pool.get(Extrinsic);
}else{
//根据外部状态创建享元对象
flyweight = new ConcreteFlyweight1(Extrinsic);
//放置到池中
pool.put(Extrinsic, flyweight);
}
return flyweight;
}
}
7.21 解释器模式
7.21.1 定义
解释器模式(Interpreter Pattern)是一种按照规定语法进行解析的方案,在现在项目中使用较少,其定义如下:
Given a language, define a representation for its grammar along with aninterpreter that uses the representation to interpret sentences in the language.
给定一门语言,定义它的文法的一种表示,并定义一个解释器,该解释器使用该表示来解释语言中的句子。
如,有一堆本质上相同的算法,如四则运算,每个业务中都设计复杂的四则运算,如果单独为每个业务设计算法,那代码的复用率和效率是极低的,那么抽取出四则运算的通用公式,通过该通用公式解析类,一劳永逸,就能方便的运算各个业务中的四则运算。这就是解析器模式。
7.23 桥梁模式
7.23.1 定义
桥梁模式(Bridge Pattern)也叫做桥接模式,是一个比较简单的模式,其定义如下:
Decouple an abstraction from its implementation so that the two can vary independently.
将抽象和实现解耦,使得两者可以独立地变化。
7.23.2 通用类图
7.23.3 角色
-
Abstraction——抽象化角色
它的主要职责是定义出该角色的行为,同时保存一个对实现化角色的引用,该角色一般是抽象类。
-
Implementor——实现化角色
它是接口或者抽象类,定义角色必需的行为和属性。
-
RefinedAbstraction——修正抽象化角色
它引用实现化角色对抽象化角色进行修正。
-
ConcreteImplementor——具体实现化角色
它实现接口或抽象类定义的方法和属性。
大家只要记住一句话就成:抽象角色引用实现角色,或者说抽象角色的部分实现是由实现角色完成的。
7.23.4 通用代码
实现化角色
public interface Implementor {
//基本方法
public void doSomething();
public void doAnything();
}
具体实现化角色
public class ConcreteImplementor1 implements Implementor{
public void doSomething(){
//业务逻辑处理
}
public void doAnything(){
//业务逻辑处理
}
}
public class ConcreteImplementor2 implements Implementor{
public void doSomething(){
//业务逻辑处理
}
public void doAnything(){
//业务逻辑处理
}
}
抽象化角色
public abstract class Abstraction {
//定义对实现化角色的引用
private Implementor imp;
//约束子类必须实现该构造函数
public Abstraction(Implementor _imp){
this.imp = _imp;
}
//自身的行为和属性
public void request(){
this.imp.doSomething();//!!!
}
//获得实现化角色
public Implementor getImp(){
return imp;
}
}
具体抽象化角色
public class RefinedAbstraction extends Abstraction {
//覆写构造函数
public RefinedAbstraction(Implementor _imp){
super(_imp);
}
//修正父类的行为
@Override
public void request(){
/*
* 业务处理...
*/
super.request();
super.getImp().doAnything();
}
}
场景类
public class Client {
public static void main(String[] args) {
//定义一个实现化角色
Implementor imp = new ConcreteImplementor1();
//定义一个抽象化角色
Abstraction abs = new RefinedAbstraction(imp);
//执行行文
abs.request();
}
}
情景:山寨公司生产山寨产品,每个阶段的山寨产品是不同的,如果不用桥梁模式,那么每个阶段的产品对应的都要创建一个新的对应的山寨公司类,去实现接口抽象公司类。用了桥梁模式,则将山寨产品独立出来,抽象为一个类,然后山寨公司类中关联产品类(成员变量为某个山寨产品类),这样换山寨产品时,对应的山寨公司还能继续使用,不用切换。这样抽象和实现进行了解耦。就是原先是某个公司调用某个接口直接去卖东西,现在是某个公司调用某个接口,该接口中是调用产品的业务方法卖东西,然后还可以添加一些增强/修正代码。产品被抽象出来成一个类,作为其属性,即产品实现不在和某个公司耦合,独立出来了,修改抽象公司的代码不会影响具体的实现,即不影响产品类的实现,其作为属性关联进行去了。
类图
实现类
抽象产品类
public abstract class Product {
//甭管是什么产品它总要能被生产出来
public abstract void beProducted();
//生产出来的东西,一定要销售出去,否则亏本
public abstract void beSelled();
}
具体产品类
房子
public class House extends Product {
//豆腐渣就豆腐渣呗,好歹也是房子
public void beProducted() {
System.out.println("生产出的房子是这样的...");
}
//虽然是豆腐渣,也是能够销售出去的
public void beSelled() {
System.out.println("生产出的房子卖出去了...");
}
}
iPod产品
public class IPod extends Product {
public void beProducted() {
System.out.println("生产出的iPod是这样的...");
}
public void beSelled() {
System.out.println("生产出的iPod卖出去了...");
}
}
抽象化角色
抽象公司类
public abstract class Corp {
//定义一个抽象的产品对象,不知道具体是什么产品
private Product product; //!!!!!!
//构造函数,由子类定义传递具体的产品进来
public Corp(Product product){
this.product = product;
}
//公司是干什么的?赚钱的!
public void makeMoney(){
//每家公司都是一样,先生产
this.product.beProducted();
//然后销售
this.product.beSelled();
}
}
实现对象是在抽象类中的
房地产公司
public class HouseCorp extends Corp {
//定义传递一个House产品进来
public HouseCorp(House house){
super(house);
}
//房地产公司很High了,赚钱,计算利润
public void makeMoney(){
super.makeMoney();
System.out.println("房地产公司赚大钱了...");//同时,还能进行修正!!!(代码增强)
}
}
山寨公司
public class ShanZhaiCorp extends Corp {
//产什么产品,不知道,等被调用的才知道
public ShanZhaiCorp(Product product){
super(product);
}
//狂赚钱
public void makeMoney(){
super.makeMoney();
System.out.println("我赚钱呀...");同时,还能进行修正!!!(代码增强)
}
}
场景类
public class Client {
public static void main(String[] args) {
House house = new House();
System.out.println("-------房地产公司是这样运行的-------");
//先找到房地产公司
HouseCorp houseCorp =new HouseCorp(house);
//看我怎么挣钱
houseCorp.makeMoney();
System.out.println("\n");
//山寨公司生产的产品很多,不过我只要指定产品就成了
System.out.println("-------山寨公司是这样运行的-------");
ShanZhaiCorp shanZhaiCorp = new ShanZhaiCorp(new IPod());
shanZhaiCorp.makeMoney();
}
}
7.23.6 优缺点
优点
-
抽象和实现分离
这也是桥梁模式的主要特点,它完全是为了解决继承的缺点而提出的设计模式。在该模式下,实现可以不受抽象的约束,不用再绑定在一个固定的抽象层次上。对于抽象部分的修改不会影响到实现部分,反之亦然。这种解耦性可以提高系统的灵活性和可维护性。
-
优秀的扩充能力
看看我们的例子,想增加实现?没问题!想增加抽象,也没有问题!只要对外暴露的接口层允许这样的变化,我们已经把变化的可能性减到最小。
-
实现细节对客户透明
客户不用关心细节的实现,它已经由抽象层通过聚合关系完成了封装。
缺点
-
增加了系统的复杂性:桥梁模式需要额外的抽象和实现类,并引入了额外的接口和关联关系,这可能增加系统的复杂性。
-
增加了代码量:相比直接继承实现类的方式,桥梁模式需要编写更多的代码,包括抽象部分和实现部分的类以及它们之间的关系