梗概
用一个类使用其他类的实例提供的数据和服务。由于“使用服务的类”通过一个接口来访问这些“提供服务的实例”,所以它可以与这些实例所属的类保持互不依赖。
场景
假 设你正在为一家公司编写采购管理软件。在你的程序中需要下列实体的信息:生产厂商、货运公司、收货地址、交款地点……这些实体的一个共同就是:它们都有一 条“街道地址”信息。这条信息将在用户界面的不同部分出现,所以你希望用一个类来显示、编辑街道地址。这样,每当用户界面中出现街道地址时,你可以复用这 个类。我们把这个类称为AddressPanel类。
你希望AddressPanel对象可以从一个独立的数据对象中接收、设置地址信息。但是,对于这些数据对 象,AddressPanel对象应该把它们当作什么类的实例呢?很明显你应该用不同的类来表示生产厂商、货运公司……等等的实体。如果你用C++这样支 持多重继承的语言编程,你可以让“AddressPanel使用的数据对象所属的类”在继承其他类的同时还继承一个地址类。但是如果你使用的编程语言象 JAVA这样只支持单继承,你就应该再想想其他的解决方案。
在JAVA中你可以用接口(interface)来代替C++中的多重继承。这样AddressPanel类 就只要求数据对象实现地址接口。你也可以通过这个接口访问或设置数据对象中的地址信息。通过这个接口,AddressPanel对象可以在不知道对象确切 类型的前提下调用数据对象的方法。图1的类图表示出了这种关系。
图1 通过地址接口进行间接访问
约束
- 如果一个类的实例必须使用另一个对象、而这个对象又属于一个特定的类,那么复用性会受到损害。
- 如果“使用”类只需要“被使用”类的某些方法、而不是要求“被使用”类与“使用”类有“is-a”的关系,就可以考虑让“被使用”类实现一个接口、“使用”类通过这个接口来使用需要的方法,从而限制了类之间的依赖。
解决方案
为了避免类之间因为彼此使用而造成的耦合,让它们通过接口间接使用。图2表示出这种组织结构。
图2中的角色如下:
- Client(客户)——使用实现了IndirectionIF的其他类。
- InterdirectionIF(间接接口)——提供间接性,保证Client与Service之间的独立性。
- Service(服务者)——实现IndirectionIF,为Client提供服务。
图2 类之间通过接口解耦效果
让调用者通过接口间接使用服务者”,这是面向对象设计的基础——多态性正是从这样的设计中产生的。
使用Interface模式,可以保证需要服务的类不与任何提供服务的类发生耦合。
和其他任何间接性一样,Interface模式会让程序变得更加难以理解。
使 用Interface模式同样有可能造成对继承的滥用。但是,通过Interface模式让你可以从接口的角度考虑设计思想。这是面向对象设计的三大原则 之一:“针对接口编程,而不是针对实现编程”[GoF95,P12]。(另外两条原则是:“优先使用对象组合,而不是类继承”[GoF95,P13]和 “发现并封装变化点”[GoF95,P20])
实现
实现Interface非常直接:定义提供服务的接口,让客户类通过该接口访问服务者对象,并让服务者类实现该接口。
Interface 模式在JAVA中得到了直接的支持:interface关键字。超越语言本身,实际上在C++和JAVA中,你都可以用同样直接的方式实现 Interface模式——C++尽管不提供interface关键字,但我们也通常把纯虚类称为“接口”,并用多重继承来“实现”接口。在用C++实现 COM规范时,我们也是用纯虚类来作为接口的。
同时,Interface模式还有不那么直接的实现方式。图2中的Client和 IndirectionIF之间的“使用”关系可能不那么直接;Client和Service都可能不是一个类而是一组类甚至一个应用程序; IndirectionIF也可能不是一个接口(或抽象类)而是一个具体类;IndirectionIF和Service之间也可能不是实现(继承)的关 系而是使用的关系……重要的是,应该随时想到“针对接口编程”的原则。
C++应用
正如前面所说的,C++(当然也包括其他所有的面向对象语言)是依靠Interface模式来实现多态性的。这种比较接近细节的应用,不提也罢,实在太多了!
当我们实现COM规范时,我们就使用了Interface模式:客户程序只知道接口信息,并用CoCreateInstance函数来获取适当的服务者对象。但是,三个角色之间的关系更加复杂。(如果读者对此有兴趣,请参阅[PAN99])
代码示例
我将就“场景”一节中讨论过的问题给出一个简略的示例代码。单是代码本身不能完整的表示Interface模式的精妙,但愿能起到抛砖引玉之效,让读者能理解“针对接口编程”的思想。
首先是代替AddressPanel出现的Client类:
#include "AddressIF.h"
然后是本模式的核心:提供间接性的“接口”。我们用一个纯虚类AddressIF来实现它:
class Client
{
public:
Client(){
m_pAddress = NULL;
};
string GetAddress(){
if(m_pAddress!=NULL)
return m_pAddress->getAddress();
else
return string();
};
bool SetAddress(string & strAddress){
if(m_pAddress!=NULL){
m_pAddress->setAddress(strAddress);
return true;
}
else
return false;
};
void SetAddressIF(AddressIF * pAddress){
m_pAddress = pAddress;
};
private:
AddressIF * m_pAddress;
};#include
AddressIF只是提供需要的方法,由服务者类来实现它。
using std::string;
class AddressIF
{
public:
virtual void setAddress(const string & strAddress) =0;
virtual string getAddress() =0;
};最后是服务者──DataClass类的代码。它通过继承来“实现”AddressIF“接口”。
#include "AddressIF.h"
主控程序可能是这样:
// ……
// 其他头文件。
class DataClass :
// ……继承其他类
public AddressIF
{
public:
// ……其他方法
virtual string getAddress(){
return m_strAddress;
};
virtual void setAddress(const string & strAddress){
m_strAddress = strAddress;
};
private:
// ……其他成员数据。
string m_strAddress;
};#include
#include "DataClass.h"
#include "Client.h"
void main()
{
Client client;
DataClass dc1, dc2;
dc1.setAddress("Beijing");
dc2.setAddress("Shanghai");
client.SetAddressIF(&dc1);
cout << client.GetAddress().c_str() << endl;
client.SetAddressIF(&dc2);
cout << client.GetAddress().c_str() << endl;
}相关模式
Delegation模式[GRAND98]——Delegation模式和Interface模式经常在一起使用。
另外有很多模式使用了Interface模式。
参考书目
[GoF95] Erich Gamma etc., Design Patterns: Elements of Reusable Object-Oriented Software. Addison-Wesley 1995. 中文版:《设计模式:可复用面向对象软件的基础》,李英军等译,机械工业出版社2000年9月。
[GRAND98] Mark Grand, Patterns in Java (volume 1), Wiley computer publishing 1998.
[PAN99] 潘爱民,《COM原理与应用》,清华大学出版社1999年11月。