c++ 面向对象类设计五项基本原则

来源:互联网 发布:steam淘宝黑卡 编辑:程序博客网 时间:2024/04/29 13:00


原文:http://www.cnblogs.com/skyofbitbit/archive/2012/09/09/2677470.html

类设计五项基本原则

类设计五项基本原则
原则:

单一职责原则
开放封闭原则
Liskov替换原则
依赖倒置原则
接口隔离原则

 

第8章 单一职责原则 ( SRP )

就一个类而言.应该仅有一个引起它变化的原因.

  一个class就其整体应该只提供单一的服务 如果一个class提供多样的服务,那么就应该把它拆分,反之,如果一个在概念上单一的功能却由几个class负责,这几个class应该合并

第9章 开放-封闭原则 ( OCP )

软件实体(类. 模块. 函数等等)应该是可以扩展的. 但是不可修改的.
例如.把一个类的功能抽象出来.形成一个抽象接口.然后对该接口编程.这样当需要扩展时只要从该接口派生一个

新类就可以完成扩展的功能.

看一个例子: 一个保存形状的链表. 打印其中的每个形状. 形状可能是圆.可能是矩形.要求先打印所有的圆形.

第一种方案:
struct 形状{
  bool  是否圆形?
  //其他数据
};
形状 [N] ; //数组;
for(i=0; i<N; ++i){
   if ( 是圆形 ) 绘制圆形.
   else  绘制矩形;
}
这就是一个糟糕的设计. 如果增加第三种图形如三角形. 改动是很麻烦的.

第2种方案:
struct 图形 { 
   virtual void draw() = 0;
};

struct 圆形 : public 图形{
   void draw() { 绘制圆 }
   //...
};

struct 矩形 : public 图形{
   void draw() { 绘制矩形 }
   //...
};

vector<图形*> v;
foreach(v.begin(), v.end(), mem_fun(&图形::draw) );
这种设计 . 如果要增加三角形的种类. 只要从图形类派生就可以了. 绘制部分不用改动.

第三种设计:
前两种设计都暂时没有考虑"先输出圆形"这个要求.
我们为vector<图形*> 排序设计了一个比较图形*的函数:
struct 图形{
   bool bijiao(const 图形& s) const {
       if (dynamic_cast<矩形*>(&s) ) return true;
       else return false;
   }
   //..
};
这个bijiao的函数经过包装就可以用在sort()中对vector<图形*>排序.
但...这个函数不具有封闭性.如果增加一个三角形类. 这个函数还要改动..

另一种设计:
将bijiao函数使用"表格驱动"的方法.获得排序功能的封闭性:

class Shape {
public:
   virtual void draw() const = 0;
   bool bijiao(const Shape&) const;
private:
   static const char* typeOrderTable[];
};
const char* Shape::typeOrderTable[] = {
    typeid(Circle).name(), 
    typeid(Square).name(),
    0
};
然后在bijiao函数中. 根据 typeid(*this).name() 和 typeid(s).name()在表格中的位置.来比较.
这样如果增加了三角形. 想调整输出的顺序为 先三角形. 再圆. 再矩形.  只要添加三角形类. 
并修改类型名表格就可以了.

 一个设计并实现好的class,应该对扩充的动作开放,而对修改的动作封闭 也就是说,这个class应该是允许扩充的,但不允许修改 如果需要功能上的扩充,一般来说应该通过添加新类实现,而不是修改原类的代码 添加新类不单可以通过直接继承,也可以通过组合

 


第10章 Liskov 替换原则  ( LSP )

Barbara Liskov说: 所有针对基类编的程序. 在用派生类替换后. 程序的行为不变.
违反这一规则的例子是 : 让正方形从矩形派生. 
虽然正方形看起来 IsA 矩形. 但它们的行为不是. 例如: 
class Rectangle { //矩形
    void setwidth( double );    //这两个函数对正方形来说. 行为和矩形不同.
    void setheight( double ); 
    //...
};
这就是一个违法LSP规则的设计. 因为把正方形作为矩形的派生类. 但它没有"可替换性".


第11章 依赖倒置原则 ( DIP )

高层模块不应该依赖于低层模块. 二者都应该依赖于抽象.
抽象不应该依赖于细节. 细节应该依赖于抽象.

为什么叫"倒置"呢. 因为在结构化分析和设计的传统开发方法里.经常是高层依赖低层模块.而这在
面向对象设计时是糟糕的. 因为那意味着对低层模块的改动会直接影响到高层模块.从而迫使高层整个
作出改动.

高层模块不能依赖于低层模块. 这是"框架设计"时的核心原则.

"高层模块应该依赖于抽象接口.而不应该依赖于具体类." 根据这一规则:
任何变量都不应该持有一个指向具体类的指针或引用.
任何类都不应该从具体类派生.
任何方法都不应该覆写它的任何基类中已经实现了的方法.

事实上. 对于象Java中的String这样低层的类. 高层的模块可以依赖它. 因为它是稳定的.

例如: 有个按钮类 (Button) 控制 灯类 (Lamp) 的打开(turnOn)和关闭(trunOff).
糟糕的设计(违反了DIP) :
public class Button {
    private Lamp itsLame;    //依赖具体的类 Lamp
    public void poll() {
        if (...) itsLamp.turnOn();
    }

上边的Button类依赖于低层的 Lamp 类. 要解除对Lamp的依赖. 我们抽象出一个抽象接口:
interface ButtonServer; 然后Button只操作ButtonServer接口. 而让Lamp类从该
接口派生.


第12章 接口隔离原则 (ISP)

有些对象.它们的接口不是内聚的. ISP建议将它们分为多个具有内聚接口的抽象基类.

例如: 有个Door对象. 它可以被锁和被解锁. 如:
class Door {
public: 
    virtual void Lock() = 0;
    virtual void Unlock() = 0;
    virtual bool IsDoorOpen() = 0;
};
现在需要一个 TimedDoor 类. 它会在门打开一定时间后发出警报声. 自动提醒关门.
现在有个 Timer 类:
class Timer {
public:
    //向Timer 对象 注册一个 TimerClient对象. 当指定的时间timeout到达时. 它自动
    //向TimerClient对象发送 TimeOut() 消息;
    void Register( int timeout, TimerClient* client);
};
class TimerClient{
public:
    virtual void TimeOut() = 0;
};

现在设计一个TimedDoor 类. 下边是个糟糕的设计:
让 Door 接口 继承自 TimerClient . 这样Door就有了 TimerOut()纯虚函数.
然后让 TimedDoor 类 实现 Door 接口. 就可以将 TimedDoor对象向Timer对象注册.

这个设计有个问题. 让 Door 接口 继承自 TimerClient . 但并不是所有的Door都要有定时功能.
所以在这个设计中. Door的接口变"胖" . 被 TimeOut()函数 污染了 .

解决的办法是分离接口. 下边有两种办法:

1. 使用委托分离接口:
创建一个派生自TimerClient的对象. 并把对该对象的请求委托给TimedDoor. 如:
class TimedDoor : public Door {
public:
    virtual void DoorTimeOut ( );
};

class DoorTimeAdapter : public TimerClient {
public:
    DoorTimerAdapter( TimedDoor& theDoor) : itsTimedDoor(theDoor) {}
    virtual void TimeOut() {
        itsTimedDoor.DoorTimeOut();
    }
private:
    TimedDoor& itsTimedDoor;
}; 

这样. 如果有:
TimedDoor  td; 
并有个Timer 的对象 tm . 就可以用委托类 DoorTimeAdapter 来注册 :
tm.Register( new DoorTimeAdapter(td) );

2. 使用多继承分离接口
使TimedDoor继承自 Door 和 TimerClient :
class TimeDoor : public Door, public TimerClient {
public:
    virtual void DoorTimeOut();
};

0 0
原创粉丝点击