再读《重构》和《架构整洁之道》算法
目录
开放封闭原则 --- OCP
依赖倒置原则 --- DIP
单一职责原则 --- SRP
Liskov替换原则 --- LSP
接口隔离原则 -- ISP
迪米特法则(最小知识原则) -- LOD编程
软件中的基础结构(函数、类或模块)对于功能扩展是开放的,可是对于修改是封闭的。设计模式
可实施的具体行为数据结构
面向接口编程,不要面向实现编程
依赖倒置原则
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 访问者模式测试
意图:定义一些列算法,把它们一个个封装起来,而且使它们能够互相替换。本模式使得算法可独立于使用它的客户而变化this
适用性:设计
意图:定义一个操做中的算法的骨架,而将一些步骤延迟到子类中。本模式使得子类能够不改变一个算法的结构便可重定义该算法的某些特定步骤。3d
适用性:
意图:表示一个做用于某对象结构中的各个元素的操做。本模式使得你能够在不改变各元素的类的前提下定义做用于这些元素的新操做。
适用性:
class Client { Element *element; void SomeOperation() { 生成一个ContreteVisitor1的实例 v element->Accept(&v); v.ShowSomething(); //用ContreteVisitor1定义的方式输出结果 } void AnotherOperation() { 生成一个ContreteVisitor2的实例 v element->Accept(&v); v.ShowSomething(); //用ContreteVisitor2定义的方式输出结果 } };
传统的层次化设计模型,上层和下层业务分离,上层依赖下层提供的功能,下层不能反向依赖上层。
依赖倒置不是简单的依赖方向翻转,它的核心仍然是抽象接口。
一、高层模块不该该依赖于底层模块(两者都应该依赖于抽象)
二、抽象不该该依赖于实现,实现应该依赖于抽象
关键是抽象,面向接口编程
Liskov替换原则
取款业务逻辑流程是稳定的
提款机的实现是变化的
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(...)) { ...... }
意图:
保证一个类仅有一个实例,并提供一个访问它的全局访问点。
适用性:
当类只有一个实例,而且客户能够从一个众所周知的访问点访问它时
重要:
不要把单实例模式当全局变量用
描述:对一个类而言,应该只有一个引发它变化缘由。
如今的描述:任何一个软件模块都应该只对某一类行为者负责
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 模式
意图:为子系统中的一组接口提供一个一致的界面。本模式定义了一个高层接口,这个接口使得这一子系统更加容易使用
意图:为其余对象提供一种代理,以控制对这个对象的访问。本模式定义了一个代理对象,经过代理对象屏蔽原对象的一些接口
意图:将一个类的接口转换成客户但愿的另外一个接口。本模式使得本来因为接口不兼容而不能在一块儿工做的那些类能够在一块儿工做
AdapterA::OperationA(...) { ... opl->Function1(...); } AdapterB::OperationB(...) { ... opl->Function3(...); }
描述:子类型(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的行为发生不可控的变化。
描述:不该该强迫客户依赖于它们不用的方法。换句话说,一个类对另外一个类的依赖应该是创建在最小的接口范围上的。
强迫客户依赖它们不使用的方法,那么客户就要面临着这些未使用的方法的改变所带来的变动,无形中增长了没必要要的耦合关系,潜在地违反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原则的。
描述:迪米特法则(Law of Demeter)又叫做最少知识原则,一个对象应当对其余对象有尽可能少的了解。
“不要和陌生人说话”,一个软件实体应该尽可能少的与其余软件实体发生相互做用,换句话说,对其余软件实体有尽可能少的知识(了解)。
迪米特法则的初衷是下降类之间的耦合,减小对其余类的依赖。
可实施的设计模式:
Facade 外观模式
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); ... }
适用性: