本系列将用C++编程语言来进行设计模式的实践。从实践触发,对设计模式与面向对象进行充分的理解。建议阅读本系列博文的同学,有面向对象的编程基础,对于编程语言并没有要求。在本系列的博文中,如有错误或者有失偏颇的地方,望不吝指点。
1. 软件设计
1.1 复杂性
首先以《大话设计模式》一书中的一个例子来引入:
话说曹操在大胜之后,宴请文武,在酒酣耳热的时候,诗兴大发,不觉吟到:”喝酒唱歌,人生真爽……“。众文武称:”好诗!“,于是一臣子命印刷工匠去印刷,以便流传天下。那个时候活字印刷术还没出现,都是先直接雕刻整体,也就是在一个板子上刻完。工匠刻完后,将样张呈现给曹操一看,曹操感觉不妥,说道”喝与唱此话过俗,应改为“对酒当歌较好。于是此臣就命工匠重新来过。工匠眼看连夜刻版之工,彻底白费,心中叫苦不选,只得照办。样张再次出来请曹操过目,曹操细细一品,觉得还是不好,说:”人生真爽太过直接,不够意境,因此应改为“对酒当歌,人生几何?当臣转告工匠之时,工匠卒。
其实软件设计也当如此,客户或者PM在要求变化时,并不会考虑其中的人力与实现,只觉易如反掌。
因此软件设计的复杂性,究其根本就是——变化。
一个产品在众多版本的迭代后,其复杂性越来越大,可维护性越来越低。
如何解决复杂性:
- 可以借助【快排】【归并】的思想——即分而治之,将大问题分解为多个小问题,将复杂问题分解为多个简单问题。
- 还有就是用面向对象的思想抽象: 由于不能掌握全部的复杂对象,我们选择忽视它的非本质细节, 而去处理泛化和理想化了的对象模型。
1.2 目标
好的软件设计用两个字概况就是——复用。
但是复用的天敌就是变化,因此软件设计的核心就是从变化中抽离稳定的部分,将稳定与变化隔离,从而控制变化,达到复用。那么如何达到这一目的呢?——面向对象。
注意:后面的一系列的设计模式,都会在稳定与变化在进行博弈,因此请记住稳定与变化,后面会经常出现。
1.3 面向对象
说到这儿,可能存在一些疑虑。到底什么是面向对象?
面向对象的三大特点:
- 封装:隐藏内部实现
- 继承:复用现有代码
- 多态:改写对象行为
通过封装,继承,多态降低程序中的耦合。就比如:刻板印刷升级的活字印。
毕升之前的雕版印刷将字刻死在木板或石板上,每次印刷不同文章,要刻不同的版。而毕升发明的活字印刷首先在板上刻好字格,然后再刻单独的字模。印刷时,将活的字模“按需索取”放在字格中,不同的排列方法产生不同的文章,而不必重新刻版。
活的字模就是对象,面向活的字模。字格比喻成一个类就是:模板+刻的行为,活的字模就是继承自这个类,也就是字格。封装了刻的行为,只用使用该字模,就可以印刷出该字每一个字模都是不同的字,这便是多态。
这里面还有很多的面向对象。
2. 设计原则
2.1 说明
通常所说的设计模式隐含地表示“面向对象设计模式”。但不意味着设计模式就是面向对象设计模式
。设计原则,也可以称之为面向对象设计原则。
设计模式一定不要理解为是算法,设计模式都是基于后面所说的设计原则所推到的。设计原则比设计模式更重要,它是衡量设计模式的尺子。因此需要充分理解设计原则,万变不离其宗,即便有千千万万的设计模式,也是从设计原则出发的。
2.2 七大原则
1)依赖倒置原则
前面说到变化与稳定,依赖倒置原则的本质就是变化应该依赖于稳定。也就是说:抽象不应该依赖实现细节,实现细节应该依赖抽象。也就是我们应该面向接口编程,而不是针对实现编程。
2)开闭原则
对扩展开放,对修改关闭。当实现一个类的时候,应该可以让其被扩展,但是对内部的修改是要禁止的。比如一个手机,为了增加保护,可以对其增加保护套和保护膜,但是不能更改手机壳等原来本身的属性。
3)单一职责原则
一个类应该只能有一个引起其变化的原因,也就是其承担的责任应该只能有一个。责任可以理解为作用。举个例子,让你停止一局王者荣耀的游戏的原因只能是这局打完,而不是对象给你打电话,或者手机关机等等诸多原因,尤其在设计类的时候需要避免这种功能情况。
4)里氏替换原则
子类必须能替换父类。这个意思就是说,从父类继承来的属性和方法,应该保持能够使用。比如从父类继承来的方法,重写的时候,里面直接抛异常,这就导致这个方法不能直接使用。比如鸟类是会飞的,那么属于鸟类的动物就要会飞。在编程的世界中,企鹅由于不会飞,因此不是继承自鸟类。
5)接口隔离原则
不应该让用户依赖不需要的接口,应该建立专门的接口,是接口小而完备。接口的设计应该遵循最小接口原则,不要把用户不使用的方法塞进同一个接口里。如果一个接口的方法没有被使用到,则说明该接口过胖,应该将其分割成几个功能专一的接口。
6)组合/聚合复用原则
举个例子来说明聚合与组合。诗句和诗句一起组合成了诗,诗和诗聚合成了诗集。诗集少一首诗并不会有什么影响,但是诗句少了一句诗就不是原来的诗了。
继承在某种程度上破坏了封装性,子类父类耦合度高。而对象组合则只要求被组合的对象具有良好定义的接口,耦合度低。
7)迪米特原则
又被称之为知道最少原则,在类的结构设计时,应该尽量降低类或者成员的访问权限。好的封装就会降低类与类之间的耦合,越提高复用。
3. 总结
最后再说一点,后面会带来设计模式的实现,用的C++语言。但是并不代表不熟悉C++的同学看不懂,语言只是一种表达形式,其思想才是最重要的。
这篇入门的博客是引子,也是核心。设计模式都是从设计模式出发的,违法了设计原则的设计模式就是错误的,就是有坏味道的代码与设计。
最后概括下来就是,封装变化,依赖稳定。注意是封装从而控制变化,不是消灭变化。比如一个房子里有一只兔子,这个变化点就是兔子,蹦来蹦去,而我们需要用一个笼子把它关着控制起来,而不是把它消灭。