最近学习了李建忠老师设计模式教程,感受有一种豁然开朗的感受。数据库
"每个模式描述了在咱们周围不断重复发生变化的问题,以及该问题的解决方案的核心。这样,你就能一次又一次的使用该方案而没必要重复的劳动"——christopher alexander编程
固然,设计模式的有本很著名的书,常考教程:设计模式
书名:设计模式:可复用面向对象软件的基础学习
固然,我不是推销书的。主要是书名很明确的给出了,软件设计模式中使用的手法,经常使用就是面向对象,确实,在我第一次听到面向对象的时候,个人理解是彻底不懂,在个人二次学习面向对象的时候,个人理解是封装,继承,多态。而到如今,我以为,咱们的最终目的,仍是编写可复用,可扩展,便于维护的软件。固然,如何把面向对象的特性运用到极致,我想还得深刻去理解设计模式,否则,不少时候,即便咱们用了面向对象语言编程,每每出现拔苗助长的效果。spa
这里先给出面向对象五大基本原则:设计
(DIP)依赖倒置原则:3d
高层模块(稳定)不该该依赖于底层模块(变化),两者都应该依赖于抽象(稳定)。code
抽象(稳定)不该该依赖于实现细节(变化),实现细节依赖于抽象(稳定)。对象
个人理解:总结为,稳定的东西不该该依赖于变化的东西。在面向对象设计过程当中,可能有时候,我会把一些具体实现依赖另一些具体实现,可是咱们知道,具体实现是容易变化的,若是依赖的实现发现变化,意味着上层也将发生变化。这里的变化,不是说具体的逻辑变更,而是接口变更,简单的说就是,面向对象应该面向抽象接口来实现,而不是面向具体的逻辑,这个抽象接口应该是十分稳定的,依赖于一个稳定的接口的好处,就是,咱们具体实现和耦合性会比较低。这意味着咱们须要很敏感的发现系统的稳定点和变化点,将稳定点变成抽象接口,把变化点变成扩展实现。blog
我本身想了一个坏栗子,不知道合不合适:
class Waiter //服务生 { } class Cater //计算员 { } class Guard //保安 { }
class Bank { public: vector<Waiter> waiterList; vector<Cater> caterList; vector<Guard> guardList; protect: void doWork(); } void Bank::doWord() { for(auto a in waiterList) { a.doWord(); } for(auto a in caterList) { a.doWord(); } for(auto a in guardList) { a.doWord(); }
做为伪代码,随意看看吧,我想意思很简单,首先有个员工文件,里面我定义了三种员工,而后银行定义了员工的LIst,固然,实际可能还须要添加员工犯法,dowork方法用来让他的员工工做,代码冗余问题虽然还能够解决,可是,最大的问题是,你应该发现,员工种类咱们假设是须要扩展的,这种方式,就是具体依赖于具体。你能够假设若是如今多了一种员工,叫作数据库管理员,那么你对于上面的代码应该作那些修改。我想你应该,写一个类,而后,在银行类里面,添加一个数据库管理员list对象,最后,还要在员工在工做里面在添加方法。是否是很不利。
我想,好的方式是这样:
class Staff { public: void doWork()=0; void ~Staff() {} } class Waiter:public Staff //服务生 { public: void doWork(); } class Cater:public Staff //计算员 { void doWork(); } class Guard:public Staff //保安 { void doWork(); }
class Bank { public: vector<Staff *> stafflList; protect: void doWork(); } void Bank::doWord() { for(auto a in staffList) { a.doWork(); }
你如今能够想象下,若是这种状况下,老板让你添加一种新的员工,叫作数据库,管理员,那么是否是方便,你只须要,在写一个类,而后继承抽象基类staff便可。这也就是面向抽象基础编程。
开放封闭原则(OCP)
对扩展开放,对更改封闭。
类模块应该可扩展的,可是不可更改。
个人理解:开放封闭原则,是最为重要的设计原则。然后面的Liskov替换原则和合成/聚合复用原则为开放封闭原则的实现提供保证。
我这里无耻的引用百度百科的例子:
class BusyBankStaff { private BankProcess bankProc = new BankProcess(); // 定义银行员工的业务操做 public void HandleProcess(Client client) { switch (client.ClientType) { case "存款用户": bankProc.Deposit(); break; case "转帐用户": bankProc.Transfer(); break; case "取款户": bankProc.DrawMoney(); break; } } }
其实这代码跟第一个原则差很少,对扩展开发怎么理解,这段代码问题主要在于,若是有新的业务出现,那么扩展,将在switch中进行修改,实际上违背了对修改封闭的原则,也就是说,若是出现新的业务,咱们须要的是扩展,而不是修改以前的类。那么好的实现方法能够是这样的:
class IBankProcess { virtual void Process()=0; }
<-接口基类
class DepositProcess : IBankProcess { //IBankProcess Members public void : Process() { // 办理存款业务 } } class TransferProcess : IBankProcess { //IBankProcess Members public : void Process() { // 办理转帐业务 } } class DrawMoneyProcess : IBankProcess { //IBankProcess Members public : void Process() { // 办理取款业务 } }
<-不一样业务
class EasyBankStaff { private : IBankProcess *bankProc = NULL; public : void HandleProcess(Client client) { bankProc = client.CreateProcess(); bankProc.Process(); } }
<-用户切换
class BankProcess { public: void Main() { EasyBankStaff bankStaff = new EasyBankStaff(); bankStaff.HandleProcess(new Client("转帐用户")); } }
处理业务,就这个方法而言,对于银行业务扩展,咱们只须要定义新的类,这叫对扩展开放,而不须要修改原先类的代码,这叫对修改封闭。这样一个原则,是否是很像一个衡量标准呢。
单一职责原则(SRP)
一个类应该仅有一个引发它变化的缘由。
变化的方向隐含着类的责任。
这是相对比较好理解的吧,"若是一个类承担的职责过多,就等于把这些职责耦合在一块儿了。一个职责的变化可能会削弱或者抑制这个类完成其余职责的能力。这种耦合会致使脆弱的设计,当发生变化时,设计会遭受到意想不到的破坏。而若是想要避免这种现象的发生,就要尽量的遵照单一职责原则。此原则的核心就是解耦和加强内聚性。"
一个类有且只有一个能够改变的理由。
liskov替换原则(LSP)
子类必须可以替换它们的基类(IS-A)。
继承表达类型抽象。
接口隔离原则(ISP)
不能够强迫客户程序依赖他们不用的方法。
接口应该小而完备。
几个额外的建议:
组合复用原则(carp):
优先使用对象组合,而不是类继承
类继承一般为“白箱复用”,对象组合一般为“黑箱复用”。
继承在某种程度上破坏了封装性,之类父类耦合度高。
而对象组合则只要求被组合的对象具备良好定义的接口,耦合度低
封闭变化点
使用封装来建立对象之间的分界层。让设计者能够在变化一侧进行修改,而不会影响另外一侧产生不良影响,从而实现松耦合。
针对接口编程
针对接口编程,而不是针对实现编程
1.不将变量类型声明为某个特定的具体类,而是声明为某个接口
2.客户程序不须要获知对象的具体类型,只须要知道对象所具备的接口。
3.减小系统中各部分的依赖关系,从而实现“高内聚,松耦合”的设计类型。
对设计模式按照目的分类能够分为如下三种:
1.建立型:与对象建立有关。
2.结构型:处理类或对象的组合。
3.行为型:模式对类或者对象怎么交互和分配职责进行描述。
按照其做用范围又可分为类模式和对象模式 ,前者主要处理类和子类的关系,经过继承创建,具备静态,在编译时刻就稳定下来。后者处理对象之间的关系,这些关系多是运行时刻变化的,具备动态性。
具体模式能够参考书上的表格:
如何使用设计模式:
我想大概是要达到对症下药的层次吧,在此以前,咱们更应该关注原则。