软件设计与重构的六个原则

软件设计与重构的六个原则

再读《重构》和《架构整洁之道》算法

目录
开放封闭原则 --- OCP
依赖倒置原则 --- DIP
单一职责原则 --- SRP
Liskov替换原则 --- LSP
接口隔离原则 -- ISP
迪米特法则(最小知识原则) -- LOD编程

1、开放封闭原则 --- OCP

软件中的基础结构(函数、类或模块)对于功能扩展是开放的,可是对于修改是封闭的。设计模式

可实施的具体行为数据结构

面向接口编程,不要面向实现编程
依赖倒置原则
Liskov替换原则架构

eg.函数

//Method One
int DoSomeFunction(code,  param1, param2)
{
    int rtnCode = ...
    switch(code)
    {
    case CODE_A:
    {
        code block A
        break;
    }
    case CODE_B:
    {
         code block B
        break;
    }
    ....
    default:
        rtnCode = not support error code

    return rtnCode;
}

//Method Two
int ProcessForCodeA(param1, param2)
{
    code block A
}

int ProcessForCodeB(param1, param2)
{
    code block B
}

struct 
{
    int code;
    int (*Processor)(param1, param2);
}ITEM;

//Table-Driven Methods
static ITEM processItems[] = 
{
    {CODE_A, ProcessForCodeA},
    {CODE_B, ProcessForCodeB},
     ......
};

int DoSomeFunction(code,  param1, param2)
{
    for_each(item in processItems)
    {
        if(item.code == code)
        {
             return item.Processor(param1, param2);
        }
    }

    return not supoort error code;
}

 

 

 

#define  MAX_PROCESS_ITEMS    32

ITEM processItems[MAX_PROCESS_ITEMS] = { 0 };

int DoSomeFunction(code,  param1, param2)
{
    ......
}

bool RegisterProcessor(int code, Processor func)
{
    add {code, func} to processItems[]
}

void DeregisterProcessor(int code)
{
    remove code process item from processItems[]
}

 

 

可实施的设计模式:

Strategy 策略模式
Template Method 模板方法模式
Visitor 访问者模式测试

开放封闭原则 --- Strategy 模式

意图:定义一些列算法,把它们一个个封装起来,而且使它们能够互相替换。本模式使得算法可独立于使用它的客户而变化this

适用性:设计

  • 许多相关的类仅仅是行为有差别,这个模式提供一种用多个行为中的一个来配置一个类的方法
  • 须要使用一个算法的不一样变体
  • 算法使用客户不该该知道的数据,使用策略模式能够避免暴露复杂的、与算法相关的数据结构
  • 一个类定义了多种行为,而且这些行为在这个类的操做中以多个条件语句的形式出现。将相关的条件分支移入它们各自的Strategy类中以代替这些条件语句。

开放封闭原则 --- Template Method 模式

意图:定义一个操做中的算法的骨架,而将一些步骤延迟到子类中。本模式使得子类能够不改变一个算法的结构便可重定义该算法的某些特定步骤。3d

适用性:

  • 一次性定义一个算法的不变部分,并将可变的行为留给子类来实现
  • 各子类中公共的行为应被提取出来并集中到一个公共父类中以免代码重复 控制子类扩展。
  • 模板方法只在特定的点调用“Hook”操做,遮掩个旧只容许在这些点进行扩展。

开放封闭原则 --- Visitor 模式

意图:表示一个做用于某对象结构中的各个元素的操做。本模式使得你能够在不改变各元素的类的前提下定义做用于这些元素的新操做。

适用性:

  • 一个对象结构包含不少类对象,它们有不一样的接口,而你想对这些对象实施一些依赖与其具体类的操做。
  • 须要对一个对象结构中的对象进行不少不一样的而且不相关的操做,而你想避免让这些操做“污染”这些对象的类。Visitor使得你能够将相关的操做集中起来定义在一个类中。
  • 定义对象结构的类不多改变,但常常须要在此结构上定义新的操做。改变对象结构类须要重定义对全部访问者的接口,这可能须要很大的代价。若是对象结构类常常改变,那么可能仍是在这些类中定义这些操做比较好。
class Client
{
    Element *element;

    void SomeOperation()
    {
        生成一个ContreteVisitor1的实例 v
        element->Accept(&v);
        v.ShowSomething(); //用ContreteVisitor1定义的方式输出结果
    }
    void AnotherOperation()
    {
        生成一个ContreteVisitor2的实例 v
        element->Accept(&v);
        v.ShowSomething(); //用ContreteVisitor2定义的方式输出结果
    }
};

 

2、依赖倒置原则 --- DIP

传统的层次化设计模型,上层和下层业务分离,上层依赖下层提供的功能,下层不能反向依赖上层。
依赖倒置不是简单的依赖方向翻转,它的核心仍然是抽象接口。
一、高层模块不该该依赖于底层模块(两者都应该依赖于抽象)
二、抽象不该该依赖于实现,实现应该依赖于抽象

    

可实施的具体行为:

关键是抽象,面向接口编程
Liskov替换原则

依赖倒置原则 --- ATM提款机

取款业务逻辑流程是稳定的
提款机的实现是变化的

    

A:  bool  Withdraw(帐户验证参数,金额)
B: int GeiQian(其余参数,帐户验证参数,金额)

将取款业务流程中对提款机的操做接口抽象出来,定义一组提款的逻辑接口。
抽象接口的提出,解除了取款业务和提款机的耦合关系,在知足Liskov替换原则的基础上,替换不一样厂家的提款机变的很是容易。

class Teller
{
public:
   ...
    bool IsConnected(环境参数) = 0;
    bool GetBalance(位置信息, 余额信息) = 0;
    bool Pay(上下文信息,支付金额) = 0;
    ...
};

//具体不一样厂家的提款机,就是这个抽象接口的实现者
class IntelTeller : public Teller
{
    IntelTeller(TellerMgmt& tm)
    {    tm.Register(this); }
    ...
    bool IsConnected(环境参数)
    {    Intel 的实现   }
    bool GetBalance(位置信息, 余额信息)
    { Intel 的实现    }
    bool Pay(上下文信息,支付金额)
    { Intel 的实现    }
    ...
};
class MoftTeller : public Teller
{
    ...
    bool IsConnected(环境参数)
    { Moft 的实现 }
    bool GetBalance(位置信息, 余额信息)
    { Moft 的实现 }
    bool Pay(上下文信息,支付金额)
    { Moft 的实现 }
    ...
};

//提供注册接口,由能提供者注册本身提供的功能
class TellerMgmt
{
    void Register(Teller *teller) 
    { ...... }
    Teller *GetTeller(...) 
    { ...... }
protected:
    //管理注册的Teller们;
}
TellerMgmt& GetTellerMgmtObj()
{
    static TellerMgmt tm;

    if(tm 没有初始化)
    {
        对tm初始化,并设置初始化标志
    }

    return tm;  
}

//取款业务模块
Teller *teller = tm.GetTeller();

if(teller->IsConnected(...))
{
    ......
}

依赖倒置原则 --- Singleton单实例模式

意图:
保证一个类仅有一个实例,并提供一个访问它的全局访问点。

适用性:
当类只有一个实例,而且客户能够从一个众所周知的访问点访问它时

重要:
不要把单实例模式当全局变量用

3、单一职责原则 --- SRP

描述:对一个类而言,应该只有一个引发它变化缘由。
如今的描述:任何一个软件模块都应该只对某一类行为者负责

  • 下降代码复杂度,一个类只负责一项职责,逻辑也简单
  • 提升代码可读性
  • 当发生变化的时候,能减小变化影响的范围,而且受影响的类的变化状况更好预知

    

   

ConnectionMgmt::AddModem(Connection *modem)
{
    Add modem to modems list
}

ConnectionMgmt::Connect(...user param...)
{
    for_each(modem in modems list)
    {
        modem->Dial(...);
    }
}

ModemImplemention modem1;
ModemImplemention modem2;

......

ConnectionMgmt cm;
cm.AddModem(&modem1);
cm.AddModem(&modem2);
......
cm.Connect(...);

可实施的具体行为:

高内聚原则
分离变化的部分和不变的部分
TDD 测试驱动开发
最小知识原则 LOD

可实施的设计模式:

Facade 外观模式
Proxy 代理模式
Adapter 适配器模式

单一职责原则 --- Facade 模式

意图:为子系统中的一组接口提供一个一致的界面。本模式定义了一个高层接口,这个接口使得这一子系统更加容易使用

单一职责原则 --- Proxy 模式

意图:为其余对象提供一种代理,以控制对这个对象的访问。本模式定义了一个代理对象,经过代理对象屏蔽原对象的一些接口

单一职责原则 --- Adapter 模式

意图:将一个类的接口转换成客户但愿的另外一个接口。本模式使得本来因为接口不兼容而不能在一块儿工做的那些类能够在一块儿工做

AdapterA::OperationA(...)
{
    ...
    opl->Function1(...);
}

AdapterB::OperationB(...)
{
    ...
    opl->Function3(...);
}

4、Liskov替换原则 --- LSP

描述:子类型(subtype)必需可以替换掉它们的基类型

OO背后的主要机制是抽象和多态,在C++和Java这样的静态语言中,支持抽象和多态的关键机制之一就是继承。若是继承体系中某个类的实现不知足LSP原则,那么这个体系就会变的很脆弱,失去健壮性。

违反LSP原则,将致使对OCP原则的违反。

class A 
{
}

class B : public A
{
    bool Operation();
};

class C : public A
{
    bool Operation();
};

void TestFunc(const A& ta)
{
    if(ta is a B)
    {
        static_cast<const B&>(ta).Operation();
    }
    else if(ta is a C)
    {
        static_cast<const C&>(ta).Operation();
    }
    else if(...)
    {
    ...
    }
    ...
}
//new design
class A 
{
    virtual bool Operation();
};

void TestFunc(const A& ta)
{
    ...
    ta.Operation();
    ...
}
/*
正是子类型的可替换性才使得使用基类类型的模块在无需修改的状况下就能够扩展。
好比增长一个新类D: 
*/
class D : public A 
{     
    bool Operation(); 
}; 
//使用基类类型的函数TestFunc()不须要作任何修改就能够支持D: 
D d; 
TestFunc(d)

 

类的继承体系设计要遵循 IS-A 原则,而且这个 IS-A 是关于行为的,不是关于数据的。  
IS-A 原则只能做为子类型定义的含义过于宽容,应该将子类型的“可替换性”做为子类型定义的必要条件。

潜在的违反LSP的状况:
没有 IS-A 关系的继承
派生类中退化了某个函数
还有一些语言层面上的错误,会致使违反LSP原则,好比某个子类的Operation()内部抛出了异常(而不是按照约定返回错误值),这会使得TestFunc的行为发生不可控的变化。

5、接口隔离原则 --- ISP

描述:不该该强迫客户依赖于它们不用的方法。换句话说,一个类对另外一个类的依赖应该是创建在最小的接口范围上的。

强迫客户依赖它们不使用的方法,那么客户就要面临着这些未使用的方法的改变所带来的变动,无形中增长了没必要要的耦合关系,潜在地违反SRP原则。

可实施的具体行为:
经过拆分职责分离接口
使用委托分离接口
分离变化的部分和不变的部分
使用多继承分离接口

使用委托分离接口

class TimedDoor: public Door
{
    void DoorTimeOut(...)
}

class TimerClient
{
    virtual void TimeOut() = 0;
}

class DoorTimedAdapter: public TimerClient
{
    DoorTimedAdapter(TimedDoor& door) { aTimedDoor = door; }
    virtual void TimeOut() { aTimedDoor.DoorTimeOut(); }
    TimedDoor  aTimedDoor;
}

class Timer
{
    RegisterTimeClient(TimerClient *tc)  
    { 
        //add tc to tcs 列表
    }
    
//事件发生时:
for_each( tc in tcs)
    tc->TimeOut(...);
}



TimedDoor door;

Timer timer;

timer.RegisterTimeClient(new DoorTimedAdapter(door));

door.Open();

class Timer
{
    friend static Timer& GetTimer()
    public:
    RegisterTimeClient(TimerClient *tc)  { add tc to tcs 列表}
    protected:
    Timer() {}
//事件发生时:
for_each( tc in tcs)
    tc->TimeOut(...);
}

Timer& Timer::GetTimer()
{
    static Timer timer;
    if(....)
    {
    }

    return timer;
}


class TimedDoor: public Door
{
    TimedDoor()
    {
        Timer& timer = Timer::GetTimer();
         timer.RegisterTimeClient(new DoorTimedAdapter(*this));
    }
    virtual void DoorTimeOut(...);
}


TimedDoor door;

door.Open();

使用多重继承分离接口

class TimerClient
{
    virtual void TimeOut() = 0;
}

class TimedDoor: public Door, public TimerClient
{
    TimedDoor()
    {
        Timer& timer = Timer::GetTimer();
         timer.RegisterTimeClient(this);
    }
    virtual void TimeOut()
    {
        do alarm report
    }
    // other interface inherit from Door
}

TimedDoor door;

door.Open();

可实施的设计模式:
Facade 外观模式
Proxy 代理模式

ISP原则和SRP原则
SRP原则强调的是“只有一个缘由能形成对象的改变”,这就潜在地对一个类的接口个数和接口内方法的个数提出了要求。通常来讲,接口个数越多,接口内的方法个数越多,越容易违反SRP原则。因此具体实施的时候,都要求一个类只实现一个接口。
ISP原则强调的是“隔离”,对一个类实现的接口数量和各个接口内方法的数量都没有要求,只要求这些接口之间相互隔离,而且没有多余的接口。ISP在具体实施的时候,可使用多继承的方式使用那些相互隔离的接口。
虽然ISP所采用多继承的时候会潜在地形成一个类的接口的增长,可是这两个原则本质上是不矛盾的,由于每个被分离出来的接口都应该是知足SRP原则的。

6、迪米特法则 --- LOD

描述:迪米特法则(Law of Demeter)又叫做最少知识原则,一个对象应当对其余对象有尽可能少的了解。

“不要和陌生人说话”,一个软件实体应该尽可能少的与其余软件实体发生相互做用,换句话说,对其余软件实体有尽可能少的知识(了解)。
迪米特法则的初衷是下降类之间的耦合,减小对其余类的依赖。

可实施的设计模式:
Facade 外观模式
Mediator 中介者模式

迪米特法则 --- Mediator 模式

意图:用一个中介对象封装一些列的对象交互。本模式使得对象不须要显示地相互引用,从而使其耦合松散,并且能够独立地改变它们之间的交互

    

三个对象的协做:TextField,Button 和 StaticText

class WelcomeDialog
{
    TextField aUserNameText;
    Button aNextButton;
    StaticText aStaticWelcome;

    WelcomeDialogMediator mediator;
    ......
};

//WelcomeDialog类的初始化部分:

......
aUserNameText.SetMediator(mediator);
aNextButton.SetMediator(mediator);
aStaticWelcome.SetMediator(mediator);

mediator.btnNext = aNextButton;
mediator.staticWelcome = aStaticWelcome;
    ......
};


void TextField::TextChange(String text)
{
    ......
    mediator.OnTextChange(this->self_ID, text);
    ......
};

void WelcomeDialogMediator::OnTextChange(int id, String text)
{
    //检查 id 是不是 aUserNameText,不一样的控件触发的TextChange能够有不一样的响应处理
    boolean isEnable = text.IsEmpty() ? false : true;
    ...
    btnNext.SetState(isEnable);
    staticWelcome.SetText(text);
    ...
}

适用性:

  • 一组对象以定义良好可是复杂的方式进行通讯,产生的相互依赖关系结构混乱且难以理解。
  • 一个对象引用其余不少对象而且直接与这些对象通讯,致使该对象难以复用。
  • 想定制一个分布在多个类中的行为,而又不想生成太多的子类。