UML软件工程组织

Interface(接口)模式
选择自 www.c-view.org
梗概

用一个类使用其他类的实例提供的数据和服务。由于“使用服务的类”通过一个接口来访问这些“提供服务的实例”,所以它可以与这些实例所属的类保持互不依赖。

场景

假设你正在为一家公司编写采购管理软件。在你的程序中需要下列实体的信息:生产厂商、货运公司、收货地址、交款地点……这些实体的一个共同就是:它们都有一条“街道地址”信息。这条信息将在用户界面的不同部分出现,所以你希望用一个类来显示、编辑街道地址。这样,每当用户界面中出现街道地址时,你可以复用这个类。我们把这个类称为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"
    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;
    };
    
    然后是本模式的核心:提供间接性的“接口”。我们用一个纯虚类AddressIF来实现它:
    #include 
    
    using std::string;
    class AddressIF  
    {
    public:
    	virtual void setAddress(const string & strAddress) =0;
    	virtual string getAddress() =0;
    
    };
    
    AddressIF只是提供需要的方法,由服务者类来实现它。

    最后是服务者──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月。

     
 

版权所有:UML软件工程组织