Chinaunix首页 | 论坛 | 博客
  • 博客访问: 4242077
  • 博文数量: 176
  • 博客积分: 10059
  • 博客等级: 上将
  • 技术积分: 4681
  • 用 户 组: 普通用户
  • 注册时间: 2006-03-24 12:27
文章分类

全部博文(176)

文章存档

2012年(1)

2011年(4)

2010年(14)

2009年(71)

2008年(103)

分类: 项目管理

2008-06-27 10:40:43

创建高质量的类,第一步,可能也是最重要的一步,就是创建一个好的接口。这也包括了创建一个可以通过接口来展现的合理的抽象,并确保细节仍被隐藏在抽象背后。

Good Abstraction

好的抽象

正如第5.3节“形成一致的抽象”中所述,抽象是一种以简化的形式来看待复杂操作的能力。类的接口为隐藏在其后的具体实现提供了一种抽象。类的接口应能提供一组明显相关的子程序。

你可以有一个实现雇员(Employee)这一实体的类。其中可能包含雇员的姓名、地址、电话号码等数据,以及一些用来初始化并使用雇员的服务子程序。看上去可能是这样的

C++示例: 展现良好抽象的类接口

class Employee {

public:

    // public constructors and destructors

    Employee();

    Employee(

        FullName name,

        String address,

        String workPhone,

        String homePhone,

        TaxId taxIdNumber,

        JobClassification jobClass

    );

    virtual ~Employee();

    // public routines

    FullName GetName() const;

    String GetAddress() const;

    String GetWorkPhone() const;

    String GetHomePhone() const;

    TaxId GetTaxIdNumber() const;

    JobClassification GetJobClassification() const;

    ...

private:

    ...

};

在类的内部还可能会有支持这些服务的其他子程序和数据,但类的使用者并不需要了解它们。类接口的抽象能力非常有价值,因为接口中的每个子程序都在朝着这个一致的目标而工作。

一个没有经过良好抽象的类可能会包含有大量混杂的函数,就像下面这个例子一样:

C++示例:展现不良抽象的类接口

class Program {

public:

    ...

    // public routines

    void InitializeCommandStack();

    void PushCommand( Command command );

    Command PopCommand();

    void ShutdownCommandStack();

    void InitializeReportFormatting();

    void FormatReport( Report report );

    void PrintReport( Report report );

    void InitializeGlobalData();

    void ShutdownGlobalData();

    ...

private:

    ...

};

假设有这么一个类,其中有很多个 子程序,有用来操作命令栈的,有用来格式化报表的,有用来打印报表的,还有用来初始化全局数据的。在命令栈、报表和全局数据之间很难看出什么联系。类的接 口不能展现出一种一致的抽象,因此它的内聚性就很弱。应该把这些子程序重新组织到几个职能更专一的类里去,在这些类的接口中提供更好的抽象。

如果这些子程序是一个叫做Program类的一部分,那么可以这样来修改它,以提供一种一致的抽象: 

C++示例:能更好展现抽象的类接口

class Program {

public:

    ...

    // public routines

    void InitializeUserInterface();

    void ShutDownUserInterface();

    void InitializeReports();

    void ShutDownReports();

    ...

private:

    ...

};

在清理这一接口时,把原有的一些子程序转移到其他更合适的类里面,而把另一些转为InitializeUserInterface()和其他子程序中使用的私用子程序。

这种对类的抽象进行评估的方法是基于类所具有的公用(public)子程序所构成的集合——即类的接口。即使类的整体表现了一种良好的抽象,类内部的子程序也未必就能个个表现出良好的抽象,也同样要把它们设计得可以表现出很好的抽象。你可以在第7.2节“在子程序层次上的设计”里获得相关的指导建议。

为了追求设计优秀,这里给出一些创建类的抽象接口的指导建议:

类的接口应该展现一致的抽象层次  在考虑类的时候有一种很好的方法,就是把类看做一种用来实现抽象数据类型(ADT,见第6.1节)的机制。每一个类应该实现一个ADT,并且仅实现这个ADT。如果你发现某个类实现了不止一个ADT,或者你不能确定究竟它实现了何种ADT,你就应该把这个类重新组织为一个或多个定义更加明确的ADT

在下面这个例子中,类的接口不够协调,因为它的抽象层次不一致:

C++示例:混合了不同层次抽象的类接口

class EmployeeCensus: public ListContainer {

public:

    ...

    // public routines

    void AddEmployee( Employee employee );

    void RemoveEmployee( Employee employee );

    Employee NextItemInList();

    Employee FirstItem();

    Employee LastItem();

    ...

private:

    ...

};

 


 

这个类展现了两个ADTEmployeeListContainer。出现这种混合的抽象,通常是源于程序员使用容器类或其他类库来实现内部逻辑,但却没有把“使用类库”这一事实隐藏起来。请自问一下,是否应该把使用容器类这一事实也归入到抽象之中?这通常都是属于应该对程序其余部分隐藏起来的实现细节,就像下面这样:

C++示例:有着一致抽象层次的类接口

class EmployeeCensus {

public:

    ...

    // public routines

    void AddEmployee( Employee employee );

    void RemoveEmployee( Employee employee );

    Employee NextEmployee();

    Employee FirstEmployee();

    Employee LastEmployee();

    ...

private:

    ListContainer m_EmployeeList;

    ...

};

有的程序员可能会认为从ListContainer继承更方便,因为它支持多态,可以传递给以ListContainer对象为参数的外部查询函数或排序函数来使用。然而这一观点却经不起对“继承”合理性的主要测试:“继承体现了‘是一个……(is a)’关系吗?”如果从ListContainer中继承,就意味着EmployeeCensus“是一个”ListContainer,这显然不对。如果EmployeeCensus对象的抽象是它能够被搜索或排序,这些功能就应该被明确而一致地包含在类的接口之中。

如果把类的公用子程序看做是潜水艇上用来防止进水的气锁阀(air lock), 那么类中不一致的公用子程序就相当于是漏水的仪表盘。这些漏水的仪表盘可能不会让水像打开气锁阀那样迅速进入,但只要有足够的时间,它们还是能让潜艇沉 没。实际上,这就是混杂抽象层次的后果。在修改程序时,混杂的抽象层次会让程序越来越难以理解,整个程序也会逐步堕落直到变得无法维护。

定要理解类所实现的抽象是什么  一些类非常相像,你必须非常仔细地理解类的接口应该捕捉的抽象到底是哪一个。我曾经开发过这样一个程序,用户可以用表格的形式来编辑信息。我们想用一个简单的栅格(grid)控件,但它却不能给数据输入单元格换颜色,因此我们决定用一个能提供这一功能的电子表格(spreadsheet)控件。

阅读(2440) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~