0%

设计模式

cbf4life <<设计模式>>

六大设计原则

单一职责原则(Single Responsibility Principle,SRP)

应该有且仅有一个原因引起类的变更

用户管理的案例

这是最简单的类图,但有很明显的问题,用户的属性(Properties)和行为(Behavior)没有区分开,应该分别抽取成两个接口,一个业务对象BO(Bussiness Object)存储用户的属性,一个保存用户的行为,如下:

代码如下:

1
2
3
4
5
6
7
IUserBiz userInfo = new UserInfo();
//我要赋值了,我就认为它是一个纯粹的BO
IUserBO userBO = (IUserBO)userInfo;$$
userBO.setPassword("abc");
//我要执行动作了,我就认为是一个业务逻辑类
IUserBiz userBiz = (IUserBiz)userInfo;
userBiz.deleteUser();

实际代码编写中,直接使用IUserInfO也可以,然后这样用户的属性和行为又混在了一起,因此我们更倾向于使用两个完全不同的类或接口,如下:

电话案例

电话通话有四个过程:拨号,通话,回应,挂机,类图和代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* 电话接口
*/
public interface IPhone {
//拨通电话
public void dial(String phoneNumber);
//通话
public void chat(Object o);
//回应,只有自己说话而没有回应,那算啥?!
public void answer(Object o);
//通话完毕,挂电话
public void huangup();
}

这个设计看似”接近于”单一职责,但实际这个IPhone接口可不只有一个职责,有两个职责:一个是协议管理,一个是数据传送.其中dial()和hangup()的方法实现的是协议管理,chat()和answer()的方法是实现数据的传送.协议和数据传送都会引起该类的变化,且协议和数据传送无关.因此拆分成两个接口:

这个设计完全满足了类和接口的单一职责要求,但增加了类的复杂性,一个电话类把两个类ConnectionManager和DataTransfer组合在一起,组合是一种强耦合关系,不利于拓展和维护.修改如下:

虽然Phone类不满足单一职责原则,但是对外仅公开接口,而不是实现类,还是可以接受的.单一职责实际很难在项目中提现,需要考虑项目环境,工作量,人员技术水平,硬件的资源情况等.

单一职责适用于类和接口,同时也适用方法.一个方法尽可能只做一件事,比如:修改用户密码,别把这个方法放到”修改用户信息”方法中(根据输入参数不同,修改不同信息).

建议:接口一定要做到单一职责,类设计尽量只有一个原因引起变化.

里氏替换原则(Liskov Substitution Principle,LSP)

所有引用基类的地方必须能透明地使用其子类的对象

通俗地讲,只要父类能出现的地方子类都可以出现,而且不会产生任何错误或异常

  • 子类必须完全地实现父类的方法
  • 子类可以有自己的个性
  • 重写不可改变输入参数,不论放大或缩小
  • 重载父类的方法时输入参数可以被放大,但绝不能缩小(如果是缩小,旧程序中方法调用逻辑存在会调用新重载方法的可能而实际应该调用被重载方法,发生逻辑错误且不易发现)
  • 重写父类的方法时输出结果和异常可以被缩小,访问权限可以放大
  • 在类中调用其他类务必使用父类或接口,否则说明已经违背LSP原则

里氏替换法则诞生的目的就是加强程序的健壮性,同时版本升级也可以做到非常好的兼容性,增加子类,原有的子类还可以继续运行.在我们项目实施中就是每个子类对应了不同的业务含义,使用父类作为参数,传递不同的子类完成不同的业务逻辑,非常完美.

依赖倒置原则(Dependence Inversion Principle)

高层模块不应该依赖于底层模块,它们(高层和底层)都应该依赖于抽象

抽象不应该依赖于细节,细节应该依赖于抽象

中心思想:面向接口编程.相对于细节的多变性,抽象的东西要稳定的多.以抽象为基础搭建的架构比以细节为基础搭建的架构要稳定的多.

接口隔离原则(Interface Segregation Principle)

两种定义:

客户端不应该依赖它不需要的接口

类间的依赖关系应该建立在最小的接口上

换句话说,要实现客户端不应该依赖它不需要的接口,那接口首先需要进行细化,保证其纯洁性;类间依赖建立在最小接口也是要求接口细化.

建立单一接口,不要建立臃肿庞大的接口

接口隔离原则包含以下四种含义:

  1. 接口尽量要小.这是接口隔离原则的核心定义,但是小是有限度的,首先不能违反单一职责原则.比如:

    1
    2
    3
    4
    IConnectionManager{
    void dial();
    void hangup();
    }

    这里挂电话有两种方式,一种正常挂掉,一种手机没电.前者是发送了中断信号,后者是信号丢失,中继器查到停止计费.那是不是要将IConnectionManager进行拆分,但这样不满足单一职责原则,从业务上通讯建立和关闭已经是最小的业务单元,再细分,一个电话还要考虑中继器等,过于复杂.因此根据接口隔离原则拆分时,必须满足单一职责原则.

  2. 接口要高内聚.高内聚就是提高接口,类,模块的处理能力,减少对外交互.具体到接口隔离原则就是减少public方法,接口是对外的承诺,承诺越少对系统开发越有利.

  3. 定制服务.一个系统或系统内的模块之间必然会有耦合,有耦合就要相互访问接口(并不一定就是Interface,也可能是类或数据交换),我们设计时就要给各个访问者定制服务,而不暴露访问者不需要的方法.

  4. 接口设计是有限度的.接口设计粒度越小系统越灵活,这是不争的事实.但因此结构复杂化,开发难度增加.实践中可以根据以下几个规则衡量:

    1. 一个接口只服务于一个子模块或业务逻辑
    2. 通过业务逻辑压缩接口中的public方法
    3. 已经污染的接口,尽量去修改,若变更风险大,采用适配器进行转换.
    4. 了解环境,拒绝盲从.环境不同,拆分的标准就不同.深入了解业务逻辑.

迪米特原则(Least Knowledge Principle,LKP)

一个对象应该对其它对象有最少的了解

迪米特原则包含以下四层逻辑:

  1. 只和朋友交流. 只和直接的朋友通信(组合,聚合,依赖),尽量减少于其它类的耦合.
  2. 朋友间也是有距离的(少公布public方法,多使用private等访问权限).对于直接的朋友了解的越少越好.
  3. 是自己的就是自己的.在项目中有一些方法,放在本类中也可以,放在其他类中也没有错误,那怎么去衡量呢?你可以坚持这样一个原则:如果一个方法放在本类中,即不增加类间关系,也对本类不产生负面影响,就放置在本类中.
  4. 谨慎使用Serializable.这个问题会很少出现的,即使出现也会马上发现问题.如果你使用RMI的方式传递一个对象VO(Value Object),这个对象就必须使用Serializable接口,也就是把你的这个对象进行序列化,然后进行网络传输.突然有一天,客户端的VO对象修改了一个属性的访问权限,从private变更为public了,如果服务器上没有做出响应的变更的话,就会报序列化失败.

迪米特原则核心观念就是类间解耦,弱耦合.

开闭原则(Open Close Principle)

软件对象(模块,类,方法等)应该对于扩展是开放的,对修改是关闭的.

当软件需要变化时,尽量通过扩展软件实体的行为来实现变化,而不是修改已有的代码来实现.

如何使用开闭原则?

  1. 抽象约束.抽象对一组事物的描述,没有具体的实现,也就表示可以有非常多的可能性,可以随需求变化而变化.
  2. 元数据控制模块行为.元数据:配置参数.通过扩展一个子类,修改配置文件来完成业务的变化.
  3. 制定项目章程.
  4. 封装变化.将相同的变化封装到一个接口或抽象类中;将不同的变化封装到不同的接口或抽象类中.不能有两个不同的变化出现在同一个接口或抽象类中.封装变化,找出预计的变化或不稳定的点,为这些变化点创建稳定的接口,准确的讲是封装可能发生的变化.

创建型模式

一共五种:单例模式/多例模式,工厂方法模式,抽象工厂模式,建造者模式,原型模式

单例模式

饿汉式:

1
2
3
4
5
6
7
8
9
public class SingletonPattern {
private static SingletonPattern singletonPattern= new SingletonPattern();
// 限制住不能直接产生一个实例
private SingletonPattern(){
}
public static SingletonPattern getInstance(){
return singletonPattern;
}
}

懒汉式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class SingletonPattern {
private static SingletonPattern singletonPattern;
// 限制住不能直接产生一个实例
private SingletonPattern(){
}
public static synchronized SingletonPattern getInstance(){
if (singletonPattern == null) {
singletonPattern = new SingletonPattern();
}
return singletonPattern;
}
}

public class SingletonPattern {
private static SingletonPattern singletonPattern;
// 限制住不能直接产生一个实例
private SingletonPattern(){
}
public static SingletonPattern getInstance(){
if (singletonPattern == null) {
synchronized (SingletonPattern.class) {
if (singletonPattern == null) {
singletonPattern = new SingletonPattern();
}
}
}
return singletonPattern;
}
}

多例模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Emperor {
private static final int maxNumberOfEmperor = 2;
private static ArrayList emperorList = new ArrayList(maxNumberOfEmperor);
private static int countNumofEmperor = 0;

static{
for(int i = 0; i < maxNumberOfEmperor ; i++){
emperorList.add(new Emperor());
}
}

private Emperor(){

}

public static Emperor getInstance(){
Random random = new Random();
countNumOfEmperor = random.nextInt(maxNumOfEmperor); //随机拉出一个皇帝,只要是个精神领袖就成
return (Emperor)emperorList.get(countNumOfEmperor);
}
}

工厂方法模式

工厂方法模式还有一个重要的应用,就是延迟初始化(双重检查/锁)后不释放,等到再次用到就不用再次初始化,直接从内存中获取.

抽象工厂模式

抽象工厂模式符合开闭原则,如果需要在产品族中增加一类产品,同时再增加一个工厂就可以解决问题.抽象工厂模式还有一个非常大的优点:高内聚,低耦合.对外仅需要提供一个Human和FemaleHumanFactory/MaleHumanFactory接口.

建造者模式

Client代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Client {
public static void main(String[] args) {
Director director = new Director();
//1W辆A类型的奔驰车
for(int i=0;i<10000;i++){
director.getABenzModel().run();
}
//100W辆B类型的奔驰车
for(int i=0;i<1000000;i++){
director.getBBenzModel().run();
}
//1000W量C类型的宝马车
for(int i=0;i<10000000;i++){
director.getCBMWModel().run();
}
}
}

建造者模式和抽象工厂模式非常相似,但建造者模式主要功能是基本方法的调用顺序安排,也就是这些基本方法已经实现了;而抽象工厂模式在于创建,你需要什么就创建什么,组装顺序并不关心.

原型模式

通过对象拷贝来实现的模式就叫原型模式.

Java提供Cloneable接口来表示某个类的对象是可能被拷贝的,通过实现/重写clone()方法来实现这个类的对象是可以被拷贝的(虽然Object已有clone()方法,但权限是protected).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Thing implements Cloneable{
public Thing(){
System.out.println("构造函数被执行了...");
}
@Override
public Thing clone(){
Thing thing=null;
try {
thing = (Thing)super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return thing;
}
}

注意事项:

  1. 拷贝时,对象的构造函数不会被执行(拷贝是从内存中以二进制流的方式进行拷贝到另一块内存)
  2. 浅拷贝和深拷贝.

浅拷贝: Object类提供的clone()方法只是拷贝本对象,其对象内的数组,引用对象都不拷贝,还是指向原生对象的内部元素地址.

深拷贝: 同时拷贝类中的成员引用对象和数组.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Thing implements Cloneable{

private ArrayList<String> arrayList = new ArrayList<String>();

@Override
public Thing clone(){
Thing thing=null;
try {
thing = (Thing)super.clone();
thing.arrayList = (ArrayList<String>)this.arrayList.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return thing;
}
}

但要注意如果成员属性附带final修复,则无法进行深拷贝.

原型模式适合场景:

  1. 类初始化需要耗费很多资源(数据,硬件等)
  2. new 对象需要非常繁琐的数据准备或访问权限
  3. 一个对象需要提供给其它对象使用,并且都可能会修改其值

在实际项目中,原型模式很少独自出现,常常和工厂模式一起出现.

结构型模式

一共七种:适配器模式,装饰模式,代理模式,门面模式(外观模式),桥梁模式,组合模式,享元模式.其中适配器模式和装饰者模式都属于包装模式.

适配器模式

类适配器:

对象适配器:

类适配器和对象适配器区别就是一个是继承,一个是关联.

适配器模式不适合在系统设计阶段采用,没有一个系统分析师会在做详设的时候考虑适配器模式,这个模式主要用于扩展应用中.

装饰模式

  • Componet:被装饰者(接口/抽象类)
  • ConcreteComponet:被装饰者实现类
  • Decorator:装饰者(接口/抽象类)
  • ConcreteDecoratorA/ConcreteDecoratorA:装饰者实现类

装饰模式一般可以多次装饰如下:

1
2
3
4
5
Component component = new ConcreteComponet();
Decorator dr = new ConcreteDecoratorA(component);
dr = new ConcreteDecoratorB(dr);
dr = new ConcreteDecoratorC(dr);
....

代理模式

代理模式主要用了Java的多态,代理类和被代理类实现同一个接口,代理类对外公开,实际调用被代理类.

门面模式

如上类图,可通过公开门面类来实现高内聚,低耦合.同时后续部分需求增加时,改动也较小,比如对写信内容进行检查时,仅需门面类增加对相关类的依赖并调用即可.

桥梁模式

  • Abstraction:业务抽象对象,接口/抽象类
  • RefinedAbstraction:业务抽象对象名义上的实现类
  • Implementor:业务实现对象,接口
  • ConcreteImplementorA/ConcreteImplementorB:业务实现对象的具体实现类

业务抽象对象引用业务实现对象.

将业务抽象对象和实现对象分开,从继承/实现关系降为聚合关系,实现类间解耦(RefinedAbstraction和ConcreteImplementor可进行更多扩展而不互相影响).如果是继承/实现关系,一方面Father-Son-GrandSon的继承关系,导致后续Son无法重写Father的方法;另一方面如果扩展需要不停增加子类,而桥梁模式很可能只增加ConcreteImplementor即可,RefinedAbstraction引用即可,Client调用更简单.

1
2
3
//ShanZhaiCorp shanZhaiCorp = new ShanZhaiCorp(new Clothes());
ShanZhaiCorp shanZhaiCorp = new ShanZhaiCorp(new IPod());
shanZhaiCorp.makeMoney();

组合模式

通用类图:

  • Component:抽象构建角色.定义共有方法和属性
  • Leaf:叶子对象,无其它分支
  • Composite:树枝对象,它的作用是组合树枝或叶子节点

组合模式有两种模式,一种是透明模式,一种是安全模式.

  1. 透明模式

  2. 安全模式

透明模式将组合使用的方法放到抽象类中,通过判断getChildre()方法返回值区分叶子节点还是分支节点.如果处理不当程序会出现问题.

优点:一般的,只要是树形结构,就要考虑使用组合模式;只要是体现局部和整体的关心的时候,而且这种关系比较深,考虑一下组合模式.

缺点:组合模式中直接使用了实现类,如Leaf leaf = new ....,这个在面向接口编程是很不恰当的.

扩展:如果需要从下往上寻找父节点或相邻节点排序,在抽象构建角色中增加相应方法即可.

享元模式

主要用于减少创建对象的数量,以减少内存占用和提高性能.大多运用在池技术,比如String常量池,数据库连接池等都是享元模式应用.

注意事项:

  1. 注意划分外部状态和内部状态,否则可能会引起线程安全问题.
  2. 这些类必须有一个工厂对象加以控制.

行为型模式

策略模式,模板方法模式,观察者模式,迭代器模式,责任链模式,命令模式,备忘录模式,状态模式,访问者模式,中介者模式,解释器模式.

策略模式

策略模式优点是:符合开闭原则,且高内聚低耦合,缺点是具体何种策略需要Client自行决定,且Client需要明确了解所有的策略类,另外就是策略类的数量问题增加维护难度.

模板方法模式

通用类图:

  • TemplateMethod:模版方法,,通过汇总或排序基本方法产生的结果集.
  • operation1/operation2:基本方法

如果需要扩展功能,模版方法模式就很好用,通过继承这个抽象类,重写protected方法,再调用类似execute方法,完成扩展开发.

观察者模式

通用类图:

观察者模式在实际项目很常见,比如ATM取钱,多次输错密码,卡就会被吞掉,卡被吞会触发哪些事件?

  1. 摄像头连拍
  2. 通知监控系统,吞卡发生
  3. 初始化ATM屏幕,回到最初状态

一般前两个动作都是通过观察者模式实现.使用观察者模式同时有两个重要问题需要解决:

  1. 广播链的问题.B观察A,C观察B,存在观察链条,逻辑复杂,如果这时A观察B,导致B既是观察者,又是被观察者,逻辑更加无法复杂,可维护性差.一般最多出现一个双重身份.
  2. 异步处理问题.如果观察者比较多,处理时间长,需要使用异步,异步就需要考虑线程安全和队列的问题.
发布订阅模式(观察者模式的变种)

发布订阅模式与观察者模式的不同:

  1. 在观察者模式中,Observer知道Subject,同时Subject还保留了Observers的记录.在发布订阅模式中,发布者和订阅者不需要彼此了解.他们只是在消息队列或代理的帮助下进行通信.
  2. 在发布订阅模式中,组件是松散耦合的.
  3. 观察者模式主要以同步方式实现,即当某些事件发生时,Subject调用其所有观察者的适当方法.发布订阅模式在大多情况下是异步方式(使用消息队列).
  4. 观察者模式一般在单个应用程序中实现.发布订阅者模式一般用于跨应用程序中.

迭代器模式

通用类图:

现在Java项目中很少使用迭代器模式,一般直接使用List或Map就可以解决问题.

迭代器的意义:将遍历序列的操作和序列底层相分离.不需要知道被迭代容器中底层序列原理,就实现遍历.

责任链模式

通用类图如下:

  • Handler:接口或抽象类,HandleRequest方法是处理请求,setNext是设置下一个Handler.

责任链模式+模版方法模式,类图如下:

通过融合模版方法模式,各个实现类只需要关心自己的业务实现,具体哪些请求需要自己处理,由父类决定,也就是父类实现请求传递,子类实现请求处理,符合单一职责.

责任链模式屏蔽了请求处理过程,你发起了一个请求,请求是谁处理并不关心,这是责任链模式的核心;同时责任链模式也可以作为一种补救模式来使用,比如一开始只能处理人民币,,但后续业务需要增加美元等,就可以使用责任链模式,增加处理者.

责任链的缺点就是调试很不方便,特别是链条比较长的,调试时逻辑复杂.

责任链模式和观察者模式的不同:

  1. 受众数量不同.观察者模式可以1:N的方式广播,而责任链要求1:!传递,必然有且只有一个类完成请求处理.
  2. 请求内容不同.观察者模式可以更改请求内容,而责任链模式不可以.
  3. 处理逻辑不同.观察者模式主要用于触发联动动作,而责任链模式是对一个类型请求按照既定规则处理.

命令模式

命令模式比较简单,但在项目中非常使用,将请求者和执行者区分开,扩展性也有很好的保障.但是也有缺点,命令模式中执行者的子类会很多.

备忘录模式

备忘录模式是指在不破坏封装性的前提下,捕获这个对象的状态并在这个对象之外保存该状态,以便还原此对象.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class MementoPatternDemo {
public static void main(String[] args) {
Originator originator = new Originator();
CareTaker careTaker = new CareTaker();
originator.setState("State #1");
careTaker.add(originator.saveStateToMemento());
originator.setState("State #2");
//还原
System.out.println("Current State: " + originator.getState());
originator.getStateFromMemento(careTaker.get(0));
System.out.println("Saved State: " + originator.getState());

}
}

备忘录模式优点:

  1. 提供了一种可以恢复状态的机制
  2. 实现了信息的封装,使得用户不需要关心状态保存细节

备忘录模式缺点:

  1. 消耗资源,如果类的成员变量较多,势必会占用比较大的资源

备忘录场景:

  1. 需要保存/恢复数据的相关场景
  2. 提供可会滚操作

备忘录注意事项:

  1. 为了符合迪米特原则,还需要增加一个管理备忘录的类
  2. 为了节约内存,可使用原型+备忘录模式

状态模式

1
2
3
4
5
6
7
8
9
10
public class Client {
public static void main(String[] args) {
Context context = new Context();
context.setLiftState(new ClosingState());
context.open();
context.close();
context.run();
context.stop();
}
}

状态模式:状态的变更引起行为的变更,从外部看起来好像这个对象对应的类发生了改变

通用类图:

状态模式优点:

  1. 避免过多switch-case和if-else语句,减小程序复杂性
  2. 满足开闭原则和单一职责原则,每个状态是一个子类,增加状态增加子类即可,修改也是一样
  3. 封装性较好,外部调用不用知道类内部如何实现状态和行为的变换

状态模式缺点:

  1. 子类过多,类膨胀

状态模式注意事项:

  1. 实际使用过程,状态的变化是复杂的,甚至存在从A直接变化到C,导致状态变化会有很多种,可使用建造者模式,建造者模式+状态模式能起到非常好的封装作用.

建造者模式+状态模式 UML图:

访问者模式

  • Visitor:接口/抽象类.声明访问者可以访问哪些元素.
  • ConcretVisitor:具体访问到一个类后的执行实现
  • Element:接口/抽象类.声明接收哪一种类型访问者访问,即accept()的方法参数
  • ConcreteElement:具体实现accept方法,通常是visitor.visit(this)
  • Object Structure:容器.一般是List等

使用场景:

  1. 遍历多个不同的对象,尤其统计功能
  2. 充当拦截器

拦截器实现类图:

访问者模式优点:

  1. 符合单一职责.Element只需要描述本身,而Visitor负责报表等的呈现
  2. 对数据操作快捷,如果遍历过程针对不同Element增加不同操作,修改Visitor即可

访问者模式缺点:

  1. 访问者访问一个类就要求这个类必须关注一些方法,违背迪米特原则
  2. Element更改属性比较困难,会影响每一个Visitor

中介者模式

其中Mediator负责将原有对象依多个对象的情况转移到自己负责,部分代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class 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+ "台");
}
}
...
}

实际项目中,一般的做法是中介者按照职责划分,每个中介者负责处理一个或多个类似关联请求.

通用类图:

  • Mediator:抽象中介者角色定义统一的接口用于各同事角色之间的通信
  • ConcreteMediator:具体中介者实现类,协调各同事角色实现协作行为,必须依赖各同事角色
  • Colleague:同事角色,每一个同事角色都知道中介者角色,而且与其他同事通信时一定要通过中介者角色协作

中介者模式优点: 减少类间依赖,把原有一对多的依赖转变成一对一的依赖

中介者模式缺点: 同事类越多,中介者越臃肿,逻辑也会很复杂

中介者模式使用场景:适用于多个对象之间紧密耦合,耦合的标准可以这样衡量:在类图中出现了蜘蛛网结构,在这种情况下一定要考虑使用中介者模式,有利于将拓扑结构梳理为星型结构.

中介者模式很少用到接口/抽象类,这与依赖倒置原则冲突,为什么?

首先说同事类,既然是同事类而不是兄弟类,说明这些类是协作关系,完成不同业务,所以不能抽取出共性的方法,当然也有部分共性的方法,比如吃饭等,但中介者并不需要关心这些.如果两个对象不能提炼出共性,就不要刻意去追求二者的抽象,抽象只需要定义出模式需要的角色即可.

其次中介者的原因,在一个项目中中介者可能被多个模块采用,每个中介者围绕的同事类各不相同,无法抽象出一个具有共性的中介者.一个中介者抽象类一般只有一个实现者,除非中介者逻辑非常大,这时才会出现多个中介者.

解释器模式

解释器模式主要用于对一些固定文法构建一个解释句子的解释器,常用语SQL解析,符号处理引擎等.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public interface Expression {
public boolean interpret(String context);
}

public class TerminalExpression implements Expression {

private String data;

public TerminalExpression(String data){
this.data = data;
}

@Override
public boolean interpret(String context) {
if(context.contains(data)){
return true;
}
return false;
}
}

public class OrExpression implements Expression {

private Expression expr1 = null;
private Expression expr2 = null;

public OrExpression(Expression expr1, Expression expr2) {
this.expr1 = expr1;
this.expr2 = expr2;
}

@Override
public boolean interpret(String context) {
return expr1.interpret(context) || expr2.interpret(context);
}
}

...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class InterpreterPatternDemo {

//规则:Robert 和 John 是男性
public static Expression getMaleExpression(){
Expression robert = new TerminalExpression("Robert");
Expression john = new TerminalExpression("John");
return new OrExpression(robert, john);
}

//规则:Julie 是一个已婚的女性
public static Expression getMarriedWomanExpression(){
Expression julie = new TerminalExpression("Julie");
Expression married = new TerminalExpression("Married");
return new AndExpression(julie, married);
}

public static void main(String[] args) {
Expression isMale = getMaleExpression();
Expression isMarriedWoman = getMarriedWomanExpression();

System.out.println("John is male? " + isMale.interpret("John"));
System.out.println("Julie is a married women? "
+ isMarriedWoman.interpret("Married Julie"));
}
}

解释器模式可利用场景比较少,JAVA中如果碰到可以用expression4J代替

设计模式汇总

模式大PK

创建型模式PK

工厂方法模式VS建造者模式

工厂方法模式注重的是整体对象的创建方法,而建造者模式注重的是部件构建的过程,旨在通过一步一步地精确构造创建出一个复杂的对象。就拿造车来说吧,工厂方法模式,直接生产出来的就是奔驰、宝马和大众;而建造者模式则不同了,则需要创建车胎、引擎、车门、座椅、车灯等等,然后组装成一辆奔驰或者宝马车。

抽象工厂模式VS建造者模式

抽象工厂模式实现对产品家族的创建,一个产品家族是一系列不同分类维度的产品组合,采用抽象工厂模式则是不需要关心构建过程,只关心什么产品由什么工厂生产即可。

结构型模式PK

装饰模式VS代理模式

首先要说明的是装饰模式是代理模式的特殊应用,两者的共同点是有相同的接口,不同点事代理模式着重对代理过程的控制,而装饰模式则是对类的功能进行加强或减弱,着重类功能的变化。

装饰模式VS适配器模式

装饰模式和适配器模式在类图的设计上差别比较大,但是他们的功能有很多相似的地方,都能够实现包装的作用,通过委托方式实现功能。不同点是:装饰模式包装的是自己的兄弟类,隶属于同一个家族(相同接口或父类),适配器模式则修饰非血缘关系的类,把一个非本家族对象伪装成一个本家族的对象,而对象的本质还是非相同接口的对象。

行为型模式PK

命令模式VS策略模式

命令模式和策略模式的相似点和区别:

  • 关注点不同:命令模式关注的是解耦问题,如何让请求者和执行者解耦是他需要首先解决的问题,解耦的要求就是将请求的命令封装为一个个命令,由接收者执行。由于封装成了命令,就同时可以对命令进行多种处理,例如撤销、记录等。策略模式关注的是算法替换为题,一种新的算法投产,旧算法退休,或者提供多种算法由调用者自己选择使用,算法的自由更替是它实现的要点。换句话说,策略模式关注的是算法的完整性、封装性,只有具备了这两个条件才能保证其自由切换。

  • 角色功能不同:命令模式关注功能实现,命令模式中接收者可以按照不同的原则进行设计(压缩文件格式或者职责),接口设计的改变只影响命令簇的变更,对请求者没有任何影响,接收者对命令负责,而与请求者无关。命令模式中的接收者只要符合六大设计原则,完全不同关系是否完成了一个具体逻辑,它的影响范围也仅仅是抽象命令和具体命令,修改不会扩散到模式外的模块。而策略模式中的具体算法是一个完整的逻辑,是不可拆分的原子业务单元,一旦变更就是对算法整体的变更。

  • 使用场景不同:命令模式适用于解耦两个有紧耦合关系的对象场合或者多命令撤销的场景,而策略模式适用于算法要求变换的场景。

状态模式VS策略模式

状态模式和策略模式的相似点和区别:

  • 环境角色的职责不同:两者都有一个叫Context环境角色的类,但是两者的区别很大,策略模式的环境角色只是一个委托作用,负责算法的替换;而状态模式的环境角色不仅仅是委托行为,它还具有登记状态变化的功能,与具体的状态类协作,共同完成状态切换行为随之切换的任务。

  • 解决问题的重点不同:策略模式旨在解决内部算法如何改变的问题,也就是将内部算法的改变对外界的影响降低到最小,保证算法之间的自由切换;而状态模式旨在改变内部状态的改变而引起的行为的改变的问题,出发点是事务的状态,状态封装而暴露行为,一个对象状态的改变,从外界来看就好像是行为改变。

  • 解决问题的方法不同:策略模式解决算法自由切换,但是什么时候用什么算法它决定不了,而装填模式对外暴露行为,状态的变换一般是由环境角色和具体状态共同完成的,也就是说装填模式封装了状态变化而暴露了不同的行为或行为结果。

  • 应用场景不同:策略模式是算法的封装,可以是一个有意义的对象,也可以是一个无意义的逻辑片段,如MD5加密算法,算法必须是平行的;状态模式则要求有一系列状态发生变化的场景,他要求的是有状态且有行为的场景,也就是一个对象必须有二维(状态和行为)描述才能采用状态模式。

  • 复杂度不同:策略模式结构比较简单,容易扩展,而状态模式通常比较复杂,因为他要从两个角色看到一个对象状态和行为的改变,也就是说封装的是变化。

观察者模式VS责任链模式

观察者模式和责任链模式的相似点和区别:

  • 链中的消息对象不同.从首节点开始到最终的尾节点,两个链中传递的消息对象是不同的。责任链模式基本上不改变消息对象的结构,虽然每个节点都可以参与消费,但是它的结构不会改变。但是在观察者模式中是允许的,链中传递的对象可以自由变化,只要上下级节点对传递对象了解即可,它不要求链中的消息对象不可变化 ,它只要求链中相邻两个节点的消息对象固定。

  • 上下节点的关系不同.在责任链模式中,上下节点没有关系,都是接收同样的对象,所有传递的对象都是链首传递过来,上一节点是什么没有关系,只要按照自己的逻辑处理就成,而观察者模式就不同了,它的上下级关系很亲密,下级对上级顶礼膜拜,上级对下级绝对信任,链中的任意两个相邻节点都是一个牢固的独立团体。

  • 消息的分销渠道不同.在责任链模式中,一个消息从链首传递进来后,就开始沿着链条向链尾运动,方向是单一的、固定的;而观察者模式则不同,有非常大的灵活性,一个消息传递到链首后,具体怎么传递是不固定的,可以以广播方式传递,也可以以跳跃方式传递,这取决于处理消息的逻辑。

您的支持是对我最大的动力 (●'◡'●)