“接口隔离”模式
在组建构建过程中,某些接口之间直接的依赖常常会带来很多问题、甚至根本无法实现。采用添加一层间接接口(稳定的),来隔离本来相互紧密关联的接口是一种常见的解决方案
- 典型模式
- Facade
- Proxy
- Adapter
- Mediator
门面模式(Facade)
- 系统间的耦合的复杂度
对于客户系统和子系统之前存在很多的耦合的情况,如果不考虑设计的情况,那么会形成A方案的情况,系统的依赖严重,维护性大大降低。
如果在客户层和子系统之间添加一层Facade,那么客户系统之和Facade打交道,子系统中也只和Facade打交道,那么在这时候,也就减少了客户和自系统的依赖程度,相对使两个系统更加独立,可维护提高。
- 动机
上述方案A的问题在于组件的客户和组件中的各种复杂的子系统有了过多的耦合,随着外部客户程序和子系统的演化,这种过多的耦合面临很多变化的挑战。
为子系统中的一组接口提供一个一致(稳定)的界面,Facade模式定义了一个高层接口,这个接口使得这一子系统更加容易使用(复用)。
——《设计模式》GoF
- Facade的结构
对于Facade来说其实没有固定的结构可循,很多时候看起来代码差别很大情况,但都是Facade模式。
而Facade更多的表达是一种设计原则和思想。
比如数据库的访问层(持久层的接口)就是一种典型的Facade模式。
- 要点总结
从客户程序角度来看,Facade模式简化了整个组建系统的接口,对于组建内部与外部客户程序来说,达到了一种“解耦”的效果——内部子系统的任何变化不会影响到Facade接口的变化。
Facade设计模式更注重从构架的层次去看整个系统,而不是单个类的层次,Facade很多时候更是一种架构设计的模式
Facade设计模式并非一个集装箱,可以任意的放入任何多个对象。Facade模式中组件的内部应该是“相互耦合关系比较大的一系列组件”,而不是简单的功能集合,以便能够实现松耦合,高内聚的特性。
代理模式(Proxy)
- 动机
在面向对象系统中,有些对象由于某种原因(比如对象创建开销很大或者某些操作需要安全控制或者需要进程外访问等),直接访问会给使用者、或者系统结构带来很多的麻烦。
为其他对象提供一种代理以控制(隔离,使用接口)对这个对象的访问。
——《设计模式》GoF
结构看是很简单,但实际使用的情况可能会很复杂。
由于某种原因,用户程序不能直接访问RealSubject对象,那么他需要使用一个代理类Proxy来代理的访问RealSubject对象。
以下就是简单的一种
class ISubject{
public:
virtual void process();
};
class RealSubject: public ISubject{
public:
virtual void process(){
//....
}
};
class ClientApp{
ISubject* subject;
public:
ClientApp(){
subject=new RealSubject();
}
void DoTask(){
//...
subject->process();
//....
}
};
class ISubject{
public:
virtual void process();
};
//Proxy的设计
class SubjectProxy: public ISubject{
public:
virtual void process(){
//对RealSubject的一种间接访问
//....
}
};
class ClientApp{
ISubject* subject;
public:
ClientApp(){
subject=new SubjectProxy();
}
void DoTask(){
//...
subject->process();
//....
}
};
- 要点总结
“增加一层间接层”是软件系统中对许多复杂问题的一种常见解决方案。在面向对象系统中,直接使用某些对象会带来很多问题,作为间接层的Proxy对象便是解决这一问题的常用手段。
具体Proxy设计模式的实现方法、实现粒度都相差很大,有些可能对单个对象做细粒度的控制,如copy-on-write技术,有些可能对组建模块提供抽象代理层,在架构层次对对象做proxy
proxy并不一定要求保持接口完整的一致性,只要能够实现间接控制,有时候损及一些透明性是可以接受的。
适配器(Adapter)
将一个类的接口转换成客户希望的另一个接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
——《设计模式》GoF
- 动机
在软件系统中,由于应用环境的变化,常常需要将“一些现存的对象”放在新的环境中应用,但是新环境要求的接口是这些现存对象所不满足的。
//目标接口(新接口)
class ITarget{
public:
virtual void process()=0;
};
//遗留接口(老接口)
class IAdaptee{
public:
virtual void foo(int data)=0;
virtual int bar()=0;
};
//遗留类型
class OldClass: public IAdaptee{
//....
};
//对象适配器
class Adapter: public ITarget{ //继承
protected:
IAdaptee* pAdaptee;//组合
public:
Adapter(IAdaptee* pAdaptee){
this->pAdaptee=pAdaptee;
}
virtual void process(){
int data=pAdaptee->bar();
pAdaptee->foo(data);
}
};
//类适配器
class Adapter: public ITarget,
protected OldClass{ //多继承
}
int main(){
IAdaptee* pAdaptee=new OldClass();
ITarget* pTarget=new Adapter(pAdaptee);
pTarget->process();
}
class stack{ //也符合Adapter的设计模式
deqeue container;
};
class queue{ //也符合Adapter的设计模式
deqeue container;
};
要点总结
Adapter模式主要应用于“希望复用一些现存的类,但是接口又与服用环境要求不一致的情况”,在遗留代码复用、类库迁移等方面非常有用。
GoF23定义了两种Adapter模式的结构实现:对象适配器好类适配器。但类适配器采用“多继承”的实现方式,一般不推荐使用。对象适配器采用“对象组合”的方式,更符合松耦合的精神。
Adapter模式可以实现的非常灵活,不必拘泥于GoF23中定义的两种结构。例如,完全可以将Adapter模式中的“现存对象”作为新的接口方法参数,来达到适配的目的。
中介者(Mediator)
用一个中介对象来封装(封装变化)一系列的对象交互。中介者使各个对象不需要显示的相互引用(编译时依赖 -> 运行时依赖),从而使其耦合松散(管理变化),而且可以独立的改变他们之间的交互。
- 动机
在软件构建的过程中,劲吹出现多个多项互相关联交互的情况,对象之间常常会维持一种复杂的引用关系,如果遇到了一些需求的更改,这种直接的饮用关系将面临不断的变化。在这种情况下,我们可以使用“中介对象”来管理对象间的关联关系,避免相互交互的对象之间的紧耦合引用关系,从而更好地抵御变化。
这幅图只是描述了依赖的解耦。通过中间的ConcreteMediator将ConcreteColleague1和ConcreteColleague2进行了依赖。从而使得ConcreteColleague1和ConcreteColleague2之间不在直接进行依赖。以此来进行解耦。
要点总结
将多个对象间复杂的关联关系解耦,Mediator模式将多个对象间的控制逻辑进行集中管理,变“多个对象互相关联”为“多个对象和一个中介者关联”,简化了系统的维护,地狱了可能的变化。
随着控制逻辑的复杂化,Mediator具体对象的实现可能相当复杂。这时候可以对Mediator对象进行分解处理。
Facade模式是解耦系统间(单向)的对象关联关系;Mediator模式是解耦系统内各个对象之间(双向)的关联关系。