</pre>本例取自《敏捷软件开发》第25章,使用C++实现<p></p><p></p><p>1.背景</p><p> <img src="" alt="" /></p><p> Modem类有四个接口,Dial、HandUp、Send、Receive,客户使用Modem,有三种实际的Modem与之对应</p><p></p><p>代码</p><p></p><pre code_snippet_id="475013" snippet_file_name="blog_20140925_2_2898509" name="code" class="cpp">#ifndef MODEM_H_
#define MODEM_H_
#include <string>
#include <iostream>
using namespace std;
class Modem
{
public:
virtual ~Modem()
{}
virtual void dial()=0;
virtual void hangUp()=0;
virtual void send()=0;
virtual void receive()=0;
};
class HayesModem: public Modem
{
public:
void dial()
{
cout<<"HayesModem dial!"<<endl;
}
void hangUp()
{
cout<<"HayesModem hangUp!"<<endl;
}
void send()
{
cout<<"HayesModem send!"<<endl;
}
void receive()
{
cout<<"HayesModem receive!"<<endl;
}
};
class USRoboticsModem:public Modem
{
public:
void dial()
{
cout<<"USRoboticsModem dial!"<<endl;
}
void hangUp()
{
cout<<"USRoboticsModem hangUp!"<<endl;
}
void send()
{
cout<<"USRoboticsModem send!"<<endl;
}
void receive()
{
cout<<"USRoboticsModem receive!"<<endl;
}
};
#endif /* MODEM_H_ */
#include "Modem.h"
void modem()
{
Modem& m1 = *(new HayesModem());
m1.dial();
m1.send();
m1.receive();
m1.hangUp();
}
运行结果
HayesModem dial!
HayesModem send!
HayesModem receive!
HayesModem hangUp!
2.变化一
有一种新的Modem加入,不需要拨号(没有Dial与HandUp的接口),对于该需求,理想的实现方式,是使用接口隔离原则(ISP)的思想,将四个接口分离为两个接口类(Dialer和Modem),使用DedicatedModem的客户不需要看到dial和hangUp接口
也即,用户代码,如果给的是一个DedicatedModem的对象,他们的代码也是可以执行的。(原有dialModem用户的代码,如果给的是一个DedicatedModem的引用,而不是DialModem的引用,他们的代码是不用修改的,可能是为了升级吧)
class Dialler
{
public:
virtual ~Dialler()
{}
virtual void dial()=0;
virtual void hangUp()=0;
};
class Modem
{
public:
virtual ~Modem()
{}
virtual void send()=0;
virtual void receive()=0;
};
class HayesModem: public Modem, public Dialler
{
public:
void dial()
{
cout<<"HayesModem dial!"<<endl;
}
void hangUp()
{
cout<<"HayesModem hangUp!"<<endl;
}
void send()
{
cout<<"HayesModem send!"<<endl;
}
void receive()
{
cout<<"HayesModem receive!"<<endl;
}
};
class USRoboticsModem:public Modem, Dialler
{
public:
void dial()
{
cout<<"USRoboticsModem dial!"<<endl;
}
void hangUp()
{
cout<<"USRoboticsModem hangUp!"<<endl;
}
void send()
{
cout<<"USRoboticsModem send!"<<endl;
}
void receive()
{
cout<<"USRoboticsModem receive!"<<endl;
}
};
class DedicatedModem:public Modem
{
public:
void send()
{
cout<<"DedicatedModem send!"<<endl;
}
void receive()
{
cout<<"DedicatedModem receive!"<<endl;
}
};
void modem()
{
HayesModem modem;
DedicatedModem dModem;
/*---------模拟客户代码------------*/
Modem& m1 = modem;
Dialler& d1 = modem;
d1.dial();
m1.send();
m1.receive();
d1.hangUp();
Modem& m2 = dModem;
m2.send();
m2.receive();
}
运行结果:
HayesModem dial!
HayesModem send!
HayesModem receive!
HayesModem hangUp!
DedicatedModem send!
DedicatedModem receive!
上述代码的问题在于,需要修改原有客户的代码(原有客户只是用Modem&,现在需要使用Modem&和Dialler&,当然,这个是在不让客户看到HayesModem和DedicatedModem类型的前提下的)
3.使用Adapt来解决
引入一个Adapt来匹配DedicatedModem(两个接口)和Modem(四个接口),Adapt继承Modem(目标接口),并持有一个DedicatedModem的引用(还可以使用继承)
由其进行接口翻译,如果是send和receive,则委托给DedicatedModem,如果是dial和hangUp,则实现一个Fake的。
这样,原有代码就不需要进行修改(使用拨号Modem的用户还是使用Modem的引用,新的使用DedicatedModem的用户,只是用send和receive接口)
class Modem
{
public:
virtual ~Modem()
{}
virtual void dial()=0;
virtual void hangUp()=0;
virtual void send()=0;
virtual void receive()=0;
};
class HayesModem: public Modem
{
public:
void dial()
{
cout<<"HayesModem dial!"<<endl;
}
void hangUp()
{
cout<<"HayesModem hangUp!"<<endl;
}
void send()
{
cout<<"HayesModem send!"<<endl;
}
void receive()
{
cout<<"HayesModem receive!"<<endl;
}
};
class USRoboticsModem:public Modem
{
public:
void dial()
{
cout<<"USRoboticsModem dial!"<<endl;
}
void hangUp()
{
cout<<"USRoboticsModem hangUp!"<<endl;
}
void send()
{
cout<<"USRoboticsModem send!"<<endl;
}
void receive()
{
cout<<"USRoboticsModem receive!"<<endl;
}
};
class DedicatedModem
{
public:
void send()
{
cout<<"DedicatedModem send!"<<endl;
}
void receive()
{
cout<<"DedicatedModem receive!"<<endl;
}
};
class DedicatedModemAdapter:public Modem
{
public:
DedicatedModemAdapter(DedicatedModem& arg):modem(arg)
{}
void dial()
{
cout<<"Fake dial!"<<endl;
}
void hangUp()
{
cout<<"Fake hangUp!"<<endl;
}
void send()
{
modem.send();
}
void receive()
{
modem.receive();
}
private:
DedicatedModem& modem;
};
void modem()
{
HayesModem modem;
DedicatedModem dModem;
DedicatedModemAdapter adapter(dModem);
/*---------模拟客户代码------------*/
Modem& m1 = modem;
m1.dial();
m1.send();
m1.receive();
m1.hangUp();
DedicatedModem& m2 = dModem;
m2.send();
m2.receive();
}
运行结果:
HayesModem dial!
HayesModem send!
HayesModem receive!
HayesModem hangUp!
DedicatedModem send!
DedicatedModem receive!
【为什么要引入Adapt?本来,如果直接定义一个DedicatedModem,其与Modem之间没有关联,对于原有使用dialmodem的用户和使用DedicatedModem的用户都没有影响】
原因在于,在引入DedicatedModem需求的时候的一个特殊的约定,就是“用户代码,如果给的是一个DedicatedModem的对象,他们的代码也是可以执行的”
比如将用户代码,修改为以下的样子,原有用户使用的引用m1已经从一个dialModem的对象变成了一个Adapter的对象,代码依旧要可以能够执行,所以说Adapte的引入,在这个例子中,更多的是为了兼容原有代码的需要。
void modem()
{
HayesModem modem;
DedicatedModem dModem;
DedicatedModemAdapter adapter(dModem);
/*---------模拟客户代码------------*/
Modem& m1 = adapter;
m1.dial();
m1.send();
m1.receive();
m1.hangUp();
DedicatedModem& m2 = dModem;
m2.send();
m2.receive();
}
执行结果:
Fake dial!
DedicatedModem send!
DedicatedModem receive!
Fake hangUp!
DedicatedModem send!
DedicatedModem receive!
4.使用Bridge模式来解决
在建模的时候,可以将Modem作为一个抽象的类,然后派生出DialModem和DedicatedModem,然后这两个类分别可以有多个品牌的Modem来实现,但是这样的话,对于这种自由度不是很稳定(会扩展、会变化)的场景,用不了多久,就会派生出大量的类。
Bridge就是将这些自由度(也可以做为变化方向)先进行打散,分为dial、hangup、send、receive四个自由度,每个自由度单独由一个Impler实现,Impler又通过接口委托给真实的Modem厂家来实现。然后ModemConnectionController又将这些变化方向“桥接”起来,组合成一个整体
代码
class Modem
{
public:
virtual ~Modem()
{}
virtual void dial()=0;
virtual void hangUp()=0;
virtual void send()=0;
virtual void receive()=0;
};
class DedicatedModem
{
public:
virtual ~DedicatedModem()
{}
virtual void send()=0;
virtual void receive()=0;
};
class ModemImplementation
{
public:
virtual ~ModemImplementation()
{}
virtual void dial()=0;
virtual void hangUp()=0;
virtual void send()=0;
virtual void receive()=0;
};
class HayesModem: public ModemImplementation
{
public:
void dial()
{
cout<<"HayesModem dial!"<<endl;
}
void hangUp()
{
cout<<"HayesModem hangUp!"<<endl;
}
void send()
{
cout<<"HayesModem send!"<<endl;
}
void receive()
{
cout<<"HayesModem receive!"<<endl;
}
};
class USRoboticsModem:public ModemImplementation
{
public:
void dial()
{
cout<<"USRoboticsModem dial!"<<endl;
}
void hangUp()
{
cout<<"USRoboticsModem hangUp!"<<endl;
}
void send()
{
cout<<"USRoboticsModem send!"<<endl;
}
void receive()
{
cout<<"USRoboticsModem receive!"<<endl;
}
};
class ModemConnectionController:public Modem, public DedicatedModem
{
public:
ModemConnectionController(ModemImplementation& arg):impl(arg)
{}
protected:
void dialImp()
{
impl.dial();
}
void hangUpImp()
{
impl.hangUp();
}
void sendImp()
{
impl.send();
}
void receiveImp()
{
impl.receive();
}
private:
ModemImplementation& impl;
};
class DedModemController:public ModemConnectionController
{
public:
DedModemController(ModemImplementation& arg):ModemConnectionController(arg)
{}
void dial()
{
cout<<"Fake dial!"<<endl;
}
void hangUp()
{
cout<<"Fake hangUp!"<<endl;
}
void send()
{
ModemConnectionController::sendImp();
}
void receive()
{
ModemConnectionController::receiveImp();
}
};
class DialModemController:public ModemConnectionController
{
public:
DialModemController(ModemImplementation& arg):ModemConnectionController(arg)
{}
void dial()
{
ModemConnectionController::dialImp();
}
void hangUp()
{
ModemConnectionController::hangUpImp();
}
void send()
{
ModemConnectionController::sendImp();
}
void receive()
{
ModemConnectionController::receiveImp();
}
};
void modem()
{
HayesModem modem;
DialModemController dialController(modem);
DedModemController dedController(modem);
/*---------模拟客户代码------------*/
Modem& m1 = dialController;
m1.dial();
m1.send();
m1.receive();
m1.hangUp();
DedicatedModem& m2 = dedController;
m2.send();
m2.receive();
}
执行结果:
HayesModem dial!
HayesModem send!
HayesModem receive!
HayesModem hangUp!
HayesModem send!
HayesModem receive!
这种实现,就会有很多的好处,首次它分离了连接策略和硬件实现。ModemConnectController的每个派生类代表了一个新的连接策略。在这个策略的实现中可以使用sendImp、receiveImp、dialImp和hangImp中的一个或者多个。新imp方法的增加不会影响到使用者
可以直接替换Modem的实现,不用考虑Modem是Dedi还是Dial这个变化方向
void modem()
{
HayesModem modem;
USRoboticsModem modem2;
DialModemController dialController(modem);
DedModemController dedController(modem2);
/*---------模拟客户代码------------*/
Modem& m1 = dialController;
m1.dial();
m1.send();
m1.receive();
m1.hangUp();
DedicatedModem& m2 = dedController;
m2.send();
m2.receive();
}
实现结果:
HayesModem dial!
HayesModem send!
HayesModem receive!
HayesModem hangUp!
USRoboticsModem send!
USRoboticsModem receive!
5.总结
使用Adapter模式的解决方案是简单和直接的。它让所有的依赖关系都指向正确的方向,并且实现起来非常简单。Bridge模式稍稍有些复杂,建议开始时不要使用bridge模式,知道你明显可以看出需要完全分离出连接策略和通信策略并且需要增加新的连接策略时,才使用这种方法。