C++ 设计模式 序章
Posted 鱼竿钓鱼干
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++ 设计模式 序章相关的知识,希望对你有一定的参考价值。
C++ 设计模式 序章
在本小节,我将通过自己的语言讲所学到的知识展现出来
同时,在此感谢李建忠老师讲解的C++设计模式相关课程,这篇文章主要参考李老师的讲课内容
从介绍一本书开始
GOF四人帮 写了一本书来介绍设计模式其标题为《设计模式-可复用面向对象软件的基础》
那么我们就从书名来介绍设计模式吧
何为设计模式
谈到设计,我们会想到什么?
对于我这样一个没接受过啥艺术熏陶的理工科直男而言,我所想到的词是:优美、高端、富有思想
那么,模式又是什么呢?
在此引用一位建筑大师的话
”每一个模式描述了一个在我们周围不断重复发生的问题,以及该问题的解决方案的核心。这样,你就能一次又一次地使用该方案而不必重复劳动“。—— Christopher Alexander
看到了吗,一位建筑大师也有着对模式的独到见解。这说明模式不光存在于面向对象编程中,生活当中处处有模式
拿我们最熟悉的做题而言,模式就是老师给你总结好的解题方法,你写题时可以套用一些解题方法来帮助你更快更好的完成题目
要我来说,模式就是经验总结
既然如此,设计模式便可以理解为对设计这一行为的经验总结
面向对象软件的设计模式
似乎感觉缺了些什么?没错,是我们要设计的对象
回到书名《设计模式-可复用面向对象软件的基础》,我们已经完成了对主标题的解读。接下来副标题告知我们要对什么进行设计
“可复用面向对象软件的基础“,这隐含地告诉我们这本书讲的是面向对象软件的设计模式
来谈谈面向对象
什么是面向对象
对象是什么?
- 从语言实现层面,对象封装了代码和数据
- 从规格层面将,对象是一系列可被使用的公共接口
- 从概念层面将,对象是某种拥有责任的抽象
面向对象就是使用诸如class
之类的关键字来编写程序吗?
不全是,我觉得使用诸如class
之类的关键字来编写程序应当属于 OOP 面向对象编程,属于面向对象思维的向下延伸。
众所周知,面向对象的三大机制(底层思维的理解)
- 封装,隐藏内部实现
- 继承,复用现有代码
- 多态,改写对象行为
我对这三大机制的初步理解源自《C++ Primer 5th》的面向对象部分,说的这么高端不就是用了些关键字嘛
但是实际上,我们还应该把握面向对象机制所带来的抽象意义,理解如何使用这些机制来表达现实世界,掌握什么是好的面向对象设计
我们为什么需要面向对象?
要了解面向对象的目的,我们首先需要回顾一下软件工程里复杂性相关的问题
软件设计复杂性的根本原因是变化,那么如何解决复杂性呢?
我们处理一个复杂问题一般有两种方案
- 分解:分而治之,大问题分为多个小问题;复杂问题分为多个简单问题
- 抽象:由于不能掌握全部的复杂对象,我们选择忽视它的非本质细节,而去处理泛化和理想化的对象模型
两个方案有优劣高低之分吗?我认为是没有的,黑猫白猫能抓到老鼠的就是好猫。我认为能够有效解决问题的技术都是好技术
我的另一观点是:当我们谈论某一技术方法时应当尽可能全面,包括但不限于适用情况、技术目的、技术的成本、技术带来的好处等等
作为一名大二学生,我了解的并不多、也不深入,在此用我浅薄的知识谈谈我对这两种方法的感受
分解:
我觉得比较贴近的是C语言的面向过程编程,当时老师教C语言的时候要我们尝试画一些流程图,这大概就是一种分解。第一步做什么,第二步做什么……这样一来一个问题就被分解为多个步骤,我们只需要逐个做好每一步,问题就被解决了
顺便,在编写这篇文章的时候,我也在尝试用分解的思维。大问题分解为多个小问题,逐个解决。
就目前而言,分解的思维可以帮助我解决大部分编程课程中遇到的问题了,甚至可以说是几乎所有问题
优势:心智负担看起来比较低,关系比较明确直观
劣势:对于那些长久的、变化频繁的、工程量巨大的项目可能显得乏力
如果采用分解问题解决问题的代价没有让你感到痛苦,那我认为没必要为了抽象而抽象,转换也是有代价的
抽象:
抽象即抽离具象,提炼本质,隐藏复杂细节。
通过两年的学习,抽象给我带来的首先是层次感。计算机网络、操作系统里的抽象往往伴随着分层的模型,每一层各司其职责任独立在隐藏下层细节的同时为上层提供服务接口。
其次是便捷性,C 语言的出现对汇编语言做了抽象,C++、JAVA 等高级编程语言的出现又对 C 语言做了进一步抽象,随着一次次的抽象,编程的复杂性有了显著的下降。很多时候我们作为库的使用者,不必关系其底层实现细节,只需熟悉调用接口就可以了,这极大提高了编程和学习的效率
此刻不仅联想到一句话“所谓岁月静好,只因有人替我们负重前行“,那么使用者的便利是谁负重前行带来的呢?我想或许是设计者们
为了避免偏离题目太远,关于设计者所需要背负的责任这一话题我想放在后面的小节“面向对象需要设计”当中谈论
回到问题“我们为什么需要面向对象这种技术“
在我个人看来,是各种变化造成了软件固有的复杂性,软件的复杂性带来的代价超出了我们可承受的范围。为了降低复杂性,我们动用分解这一思维武器,遗憾的是分解在某些情况下太过局限,不能很好的限制变化带来的复杂性。此时,我们发现诸如面向对象、设计模式、架构模式等等这些抽象的思维武器能够相对有效的应对软件变化带来的复杂性。既然好用,那就让行业新人们学学吧!(可惜的是,大部分学校的学生不会在学校体会到那种从痛苦到发现良药的喜悦。因为没有承担什么失败的成本:时间、金钱、挫败感,自然也就体会不到这么做的好处)
现在我们知道了面向对象是为了应对变化带来的复杂性,那么它具体是怎么做的?
软件设计的金科玉律是复用,而变化是复用的天敌,面向对象设计的最大优势在于:抵御变化!
- 隔离变化:从宏观层面来看,面向对象的构建方式更能适应软件的变化,能将变化所带来的影响减为最小
- 各司其职:
- 从微观层面来看,面向对象的方式更强调各个类的责任
- 由于需求变化导致的新增类型不应该影响原来类的实现是所谓的各负其责
体现在机制上便是封装、继承、多态这三大机制
设计模式
面向对象需要设计
在这一标题下,我想谈论的是“设计者们需要背负什么样的责任”这一话题
或许谈责任显得有些太过严肃了,回到设计这一词给我带来的直观感受:优美、高端、富有思想
那么,如何让我们的面向对象设计显得:优美、高端、富有思想呢?
这不是一件容易的事情,幸运的是已经有很多前辈为我们总结好了一些面向对象的设计原则
面向对象设计原则
- 依赖倒置(DIP)
- 高层模块(稳定)不应该依赖于低层模块(变化),二者都应该依赖于抽象(稳定)
- 抽象(稳定)不应该依赖于实现细节(变化),实现细节应该依赖于抽象(稳定)
- 开放封闭原则(OCP):用扩展来适应需求的变化,用增加代替修改
- 对扩展开放,对更改封闭
- 类模块应该是可扩展的,但是不可修改
- 单一职责原则(SRP)
- 一个类应该仅有一个引起它变化的原因
- 变化的方向隐含着类的责任
- Liskov 替换原则(LSP)
- 子类必须能够替换它们的基类(IS-A)
- 继承表达类型抽象
- 接口隔离原则(ISP):不要把不必要的方法把它
public
出去,滥用public
容易产生依赖- 不应该强迫客户程序依赖它们不用的方法
- 接口应该小而完备
- 优先使用对象组合,而不是类继承
- 类继承通常为白箱复用,对象组合通常为黑箱复用
- 继承在某种程度破坏了封装性,子类父类耦合度较高
- 而对象组合则只要求被组合的对象具有良好定义的接口,耦合度较低
- 封装变化点
- 使用封装来创建对象之前的分解层,让设计者可以在分解层的一侧进行修改,而不会对另一侧产生不良的影响,从而实现层次间的松耦合
- 针对接口编程,而不是针对实现编程
- 不将变量类型声明为某个特定的具体类,而是声明为某个接口(不要绝对化了,这里主要指业务类)
- 客户程序无需获知对象的具体类型,需要知道对象所具有的接口
- 减少系统中各部分的依赖关系,从而实现“高内聚、松耦合”的类型设计方案
- 产业强盛的标志是接口标准化,接口标准化的本质是分工协作
我们应该如何去学习使用设计模式
重构获得模式 Refactoring to Patterns 是我们学习和使用设计模式的主要方法
重构的关键技法
- 静态->动态
- 早绑定->晚绑定
- 继承->组合
- 编译时依赖->运行时依赖
- 紧耦合->松耦合
以上是关于C++ 设计模式 序章的主要内容,如果未能解决你的问题,请参考以下文章