zoukankan      html  css  js  c++  java
  • 类设计五项基本原则

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

    单一职责原则
    开放封闭原则
    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();
    };

  • 相关阅读:
    常用的系统操作需要的响应时间
    几种RAID技术比较
    iptables详解
    mount命令详解
    解决CSocket高数据传输问题
    VC++ ComBox下拉菜单看不到值
    封装MySQL C API 基本操作
    MySQL存储过程和存储函数
    MYSQL 常用命令
    VS2005连接MySQL C API
  • 原文地址:https://www.cnblogs.com/skyofbitbit/p/2677470.html
Copyright © 2011-2022 走看看