接口隔离类设计模式
在组件构建过程中,某些接口之间的直接的一来往往会带来很多问题,甚至根本无法实现。采用添加一层间接接口,来隔离相互紧密耦合的接口是一种常见的解决方案。
经典模式如下:
Facade
Proxy
Adapter
Mediator
Facade(门面模式)
动机,如下图所示,就是为了简化外部和系统内部交互接口的方式,可以复用
举个例子,大家买股票,每支股票可以看成是SubSystem,我们可以同时买多只股票,那么你需要知道多个SubSystem。有些人比较懒,不想关注股票,但是又想赚钱,那就只能买基金了,基金是结合了多只股票,客户只关注这些基金,而不关注股票,所以需要一个基金类facade把所有的股票的方法都包进来,进行结合,以备客户调用。
代码
#include <iostream>
using namespace std;
class SubSystemOne {
public:
void MethodOne() { cout << "SubSystemOne" << endl; }
};
class SubSystemTwo {
public:
void MethodTwo() { cout << "SubSystemTwo" << endl; }
};
class SubSystemThree {
public:
void MethodThree() { cout << "SubSystemThree" << endl; }
};
class SubSystemFour {
public:
void MethodFour() { cout << "SubSystemFour" << endl; }
};
\\\facade类把所有subSystem都包含进来,从而组合以备客户调用
class Facade {
private:
SubSystemOne s1;
SubSystemTwo s2;
SubSystemThree s3;
SubSystemFour s4;
public:
void MethodA() { // 方法组合
s1.MethodOne();
s3.MethodThree();
}
void MethodB() { // 方法组合
s2.MethodTwo();
s3.MethodThree();
s4.MethodFour();
}
};
\\\客户的调用
int main() {
Facade f;
f.MethodA(); // SubSystemOne
// SubSystemThree
cout << endl;
f.MethodB(); // SubSystemTwo
// SubSystemThree
// SubSystemFour
return 0;
}
案例参照以下链接
原文链接:https://blog.csdn.net/shu_chang1993/article/details/92733590
当我们有更改的时候,只需要更改subsystem里面的定义就行,而不需要更改facade类。这样就实现了变化隔离,这种案例其实数不胜数。
再举个例子,比如电脑,外部接口是统一的,键盘,鼠标,蓝牙等等都是不变的,但是内部的cpu,内存条可以经常换代,而针对用户的接口是不变的。这更像是一种设计的框架和修养。
Proxy (代理模式)
动机:在面向对象系统中,有些对象由于某种原因(比如创建开销过大,安全因素或者进程外的访问等)会给访问者或者系统接口带来很多麻烦,因此i我们就需要增加一层代理曾来管理/控制这些对象特有的复杂性。
class ISubject
{
public:
virtual void process()=0;
virtual ~ISubject(){};
};
class RealSubject :public ISubject
{
public:
void process()override{};
};
class clinetApp
{
ISubject* sub;
public:
clinetApp()
{
sub = new RealSubject();//本来我们希望这里生成Readsubject
//但是由于某些原因我们拿不到这个东西
}
void Dotask()
{
sub->process();
};
};
如何解决上面问题呢?,我们新定义过一个类,通过对实现间接访问的方式实现对类实现的访问,具体如下:
class SubjectProxy :public ISubject
{
public:
void process()override{};
//对realsubject 的间接访问
//很复杂,假设我们实现了,一般是用自动工具生成
};
class clinetApp
{
ISubject* sub;
public:
clinetApp()
{
sub = new SubjectProxy();//这样一来我们就可以调用realsubject内的实现了
}
void Dotask()
{
sub->process();
};
};
说到底就是增加间接层实现对类的间接的控制,具体的实现都是接触一些工具完成,基本没人用手写,因此大概意思懂了就行。
Adapter (适配器模式)
动机:在软件系统中,由于应用环境的变化,杭长要将会一些现存的对象放在新的环境中使用,但是新环境要求的接口在现催对象中是不满足的。那么我们应该如何利用现有对象的良好实现满足新环境的要求?
class ITarget//新接口
{
public:
virtual void process() = 0;
virtual ~ITarget(){};
};
class IAdaptee//老接口
{
public:
virtual void foo(int i = 100) = 0;
virtual int bar() = 0;
};
class oldclass :public IAdaptee
{
public:
//对老接口的实现foo和bar怎么杨怎么样
};
针对上面两种不同的接口,我们可以用适配器的方式更改老接口使之能利用新接口实现老接口的功能,比如说我们老接口希望实现这样的功能:
IAdaptee* pAdaptee;
int num = pAdaptee->bar();
pAdaptee->foo(num);
那么更新后的adapter就可以这样写:
class adapter :public ITarget//继承
{
protected:
IAdaptee* pAdaptee;//组合
public:
adapter(IAdaptee* input) :pAdaptee(input){};
void process()override
{//可能是这样转换的,这里只是一种非常简单的例子
int num = pAdaptee->bar();
pAdaptee->foo(num);
}
};
那么我们在使用的时候就可以这样写
int main()
{
IAdaptee *adap = new oldclass();
ITarget* target = new adapter(adap);//拿旧的类塞到新的类里面,就可以用了
target->process();
}
其实各位如果看过STL源码就能知道,其实queue和stack内部适配器是dequeue,map/set等内部也有共同的适配器这种用法其实在stl库内部非常广泛;在软件类库迁移的时候,适配器也十分常见;
适配器更是作为STL的6大组件之一,建议各位好好学习STL源码并掌握其精妙所在。
STL不在本文讨论范围之内
实际上适配器可以分为两种,对象适配器和类适配器,我们观察上面的适配器就能发现有组合和继承,这种一般就是对象适配器;类适配器举一个小例子:
class adapter :public ITarget ,protected IAdaptee
{
public:
void process()override
{//可能是这样转换的,这里只是一种非常简单的例子
int num = pAdaptee->bar();
pAdaptee->foo(num);
}
};
这种多继承的时候也可以,因为这样就能继承它的实现而不是接口;但是其实本身Iadaptee并没有实现,因此其实什么都没有集成到;那么有的人就想到继承Oldclass;
但是其实这样也不对,这种继承就不具有灵活性,因此不建议用多继承的类适配器;
Mediator (中介者模式)
动机:在软件构建过程中,经常会出现多个对象相互关联交互的情况,对象间常常会维持一种复杂的引用关系,如果遇到一些需要修改的需求,这种引用将不得不变化以应对需求的变化;
因此我们需要一种中间者来管理对象间的关联关系,避免相互交互对象之间的紧耦合引用关系,从而更好地抵御变化。
#include <iostream>
#include <string>
using namespace std;
/*定义抽象人类接口*/
class Person
{
public:
virtual void SetMediator(Mediator *mediator)=0;
virtual void SendMessage(string &message)=0;
virtual void GetMessage(string &message)=0;
protected:
Mediator *p_mediator; //中介
};
/*定义抽象中介类实现*/
class Mediator
{
public:
virtual void Send(string &message, Person *person){}
virtual void SetRenter(Person *renter){}
virtual void SetLandlord(Person *landlord){}
};
/*定义租客类实现*/
class Renter:public Person
{
public:
void SetMediator(Mediator *mediator){p_mediator = mediator;}
void SendMessage(string &message){ p_mediator->Send(message,this);}
void GetMessage(string &message){cout<<"租房者收到房东发来的消息:"<<message;}
};
/*定义房东类实现*/
class Landlord:public Person
{
public:
void SetMediator(Mediator *mediator){p_mediator = mediator;}
void SendMessage(string &message){ p_mediator->Send(message,this);}
void GetMessage(string &message){cout<<"房东收到租客发来的消息:"<<message;}
};
/*定义具体中介类*/
class HouseMediator:public Mediator
{
public:
HouseMediator():p_renter(NULL),p_landlord(NULL){}
void SetRenter(Person *renter){p_renter = renter;}
void SetLandlord(Person *landlord){p_landlord = landlord;}
void Send(string &message, Person *person)
{//接收消息的对象为该对象的对应对象
if(person == p_renter)
p_landlord->GetMessage(message);
else
p_renter->GetMessage(message);
}
private:
Person *p_renter;
Person *p_landlord;
};
int main(int argc, char *argv[])
{
Mediator *mediator = new HouseMediator();
Person *person1 = new Renter();
Person *person2 = new Landlord();
mediator->SetRenter(person1);
mediator->SetLandlord(person2);
person1->SetMediator(mediator);
person2->SetMediator(mediator);
person1->SendMessage(string("我想在深圳北站附近租套房子,一室一厅\n"));
person2->SendMessage(string("我出租一条房子,一室一厅,深圳北站附近\n"));
delete mediator;
delete person1 ;
delete person2 ;
return 0;
}
由于涉及多个类不同的交互,那么我们需要对中介者进行大量的定义,当让我们可以把绑定两端写道虚构函数中,但是互相关联交互都需要在mediator的实现类中实现,因此代码的量非常庞大;
总结:
将多个对象的交互进行集种地管理,形成了接口隔离,简化了系统的维护,抵御了可能的变化。
小结
facade解决了系统内外的复杂交互
proxy解决了无法直接访问类的问题
adapter解决了新老接口的搭配问题
mediator解决了多个不同复杂类之间的交互