Geekband C++第八周

课程学习目标

● 理解松耦合设计思想
    ○ 由紧耦合(比如继承)重构到松耦合(比如包含)是代码优化的方向
● 掌握面向对象设计原则
    ○ 充分理解面向对象的八大设计原则是课程的核心。观察违法八大设计原则是使用设计模式(重构)的开始。
● 掌握重构技法改善设计
    ○ 通过重构(坏模式->好模式),提升代码的复用性
● 掌握GOF核心设计模式

设计模式

  1. 概念理解
    ○ “每一个模式描述了一个在我们周围不断重复发生的问题,以及该问题的解决方案的核心。这样,你就能一次又一次地使用方案而不必做重复劳动。” -Christopher Alexander
    ○ 个人理解:设计模式是处理问题的方法,是由特殊到一般,由个性到共性的问题抽象方法,是解决软件设计复杂性的重要手段
  2. 面向对象的重新理解和设计模式
    ○ 面向对象隐含着两种思维模型,底层思维和抽象思维。
    ○ 底层思维是指:理解代码构造,理解编译原理,理解内存模型
    ■ 语言构造、编译转换,内存模型,运行时机制
    ■ 三大面向对象机制:封装,继承,多态
    ○ 抽象思维是指:如何抽象真实世界的问题,将问题抽象成代码程序
    ■ 面向对象,组件封装,设计模式,架构模式
    ■ 理解如何用面向对象的三大机制表达现实世界

软件设计的复杂性

● 来源:复杂来自于变换
  ○ 客户需求变化
  ○ 技术平台变化
  ○ 开发团队变化
  ○ 市场环境变化。。。。
● 解决复杂性的方法
  ○ 分解:分治。将大问题分解成小问题
  ○ 抽象:层次化。将问题划分为多个层次,从高到低,从简到繁,从抽象到具体。
● 使用设计模式的原因
  ○ 设计模式就是用来处理变化(复杂)与稳定(简单)之间的矛盾的。
  ○ 使用设计模式进行优化的目标:复用,最大程度的复用。

重新理解面向对象设计:最大程度的抵制变换

● 理解隔离变换:
  ○ 通过接口的定义,将变换隔离在不同层次。只要接口不变,底层的变换不影响上层软件。
● 各司其职:
  ○ 对象间松耦合,接口稳定,每个对象处理自身内部的变换,不将变换传播至其他对象。
● 不同层次的对象
  ○ 语言层:封装了代码和数据
  ○ 规格层:一系列公共接口
  ○ 概念层:某种责任的抽象

面向对象设计的八大原则:

  1. 依赖倒置原则(Dependecy-Inversion Principle)
    ○ 高层模块(稳定)不依赖于底层模块(变换),二者都同依赖于抽象(稳定)
    ○ 抽象不依赖于具体,具体依赖于抽象。
    ○ 依赖一定会存在于类与类、模块与模块之间。当两个模块之间存在紧密的耦合关系时,最好的方法就是分离接口和实现:在依赖之间定义一个抽象的接口使得高层模块调用接口,而底层模块实现接口的定义,以此来有效控制耦合关系,达到依赖于抽象的设计目标。(来自网络)
    ○ 抽象的稳定性决定了系统的稳定性,因为抽象是不变的,依赖于抽象是面向对象设计的精髓,也是依赖倒置原则的核心。依赖于抽象是一个通用的原则,而某些时候依赖于细节则是在所难免的,必须权衡在抽象和具体之间的取舍,方法不是一层不变的。依赖于抽象,就是对接口编程,不要对实现编程。(来自网络)
  2. 开放封闭原则(Open-Closed principle)
    ○ 优秀的软件设计应该是考虑扩展的,通过扩展适应变化,而不是通过修改适应变化。即对扩展开放,对修改封闭的。
    ○ 对扩展开放,意味着有新的需求或变化时,可以对现有代码进行扩展,以适应新的情况。对修改封闭,意味着类一旦设计完成,就可以独立完成其工作,而不要对其进行任何尝试的修改。
    ○ 实现开开放封闭原则的核心思想就是对抽象编程,而不对具体编程,因为抽象相对稳定。让类依赖于固定的抽象,所以修改就是封闭的;而通过面向对象的继承和多态机制,又可以实现对抽象类的继承,通过覆写其方法来改变固有行为,实现新的拓展方法,所以就是开放的。
  3. 单一职责原则(Single-Resposibility Principle)
    ○ 每个类只做一件事。只处理某一种变化(需求)
    ○ 单一职责原则可以看做是低耦合、高内聚在面向对象原则上的引申,将职责定义为引起变化的原因,以提高内聚性来减少引起变化的原因。职责过多,可能引起它变化的原因就越多,这将导致职责依赖,相互之间就产生影响,从而大大损伤其内聚性和耦合度。
    ○ 一般而言单一职责,就是指只有一种单一功能,不要为类实现过多的功能点,以保证实体只有一个引起它变化的原因。
  4. Liskov替换原则(Liskov-Substituion Principle)
    ○ 子类必须能够替换其基类。在父类和子类的具体行为中,必须严格把握继承层次中的关系和特征,将基类替换为子类,程序的行为不会发生任何变化。这一约束反过来则是不成立的。
    ○ Liskov原则体现为对继承机制的约束规范,只有子类能够替换基类时,才能保证系统在运行期内识别子类,这是保证继承复用的基础。
    ○ Liskov替换原则,主要着眼于对抽象和多态建立在继承的基础上,因此只有遵循了Liskov替换原则,才能保证继承复用是可靠地。实现的方法是面向接口编程:将公共部分抽象为基类接口或抽象类,通过Extract Abstract Class,在子类中通过覆写父类的方法实现新的方式支持同样的职责。Liskov替换原则是关于继承机制的设计原则,违反了Liskov替换原则就必然导致违反开放封闭原则。(来自网络)
  5. 接口隔离原则(Interface-Segregation Principle)
    ○ 使用多个小的专门的接口,而不要使用一个大的总接口。具体而言,接口隔离原则体现在:接口应该是内聚的,应该避免“胖”接口。一个类对另外一个类的依赖应该建立在最小的接口上,不要强迫依赖不用的方法,这是一种接口污染。
    ○ 接口有效地将细节和抽象隔离,接口隔离强调接口的单一性。而胖接口存在明显的弊端,会导致实现的类型必须完全实现接口的所有方法、属性等;而某些时候,实现类型并非需要所有的接口定义,在设计上这是“浪费”,而且在实施上这会带来潜在的问题,对胖接口的修改将导致一连串的客户端程序需要修改,有时候这是一种灾难。在这种情况下,将胖接口分解为多个特点的定制化方法,使得客户端仅仅依赖于它们的实际调用的方法,从而解除了客户端不会依赖于它们不用的方法。
    ○ 分离的手段主要有以下两种:1、委托分离,通过增加一个新的类型来委托客户的请求,隔离客户和接口的直接依赖,但是会增加系统的开销。2、多重继承分离,通过接口多继承来实现客户的需求,这种方式是较好的。
  6. 优先使用对象组合,而不是类继承
    ○ 继承和组合都能达到一个代码复用的效果,但是类的继承通常是白箱复用,对象组合通常为黑箱复用。我们在使用继承的时候同时也就拥有了父对象中的保护成员,增加了耦合度。而对象组合就只需要在使用的时候接口稳定,耦合度低。
    ○ 只有在对象之间关系具有很强的is a关系的时候才使用继承。
    ○ 继承具有如下优点:实现新的类非常容易,因为基类的大部分功能都可以通过继承关系自动赋予派生类;修改或者扩展继承来的实现非常容易;只要修改父类,派生的类的行为就同时被修改了。初学面向对象编程的人会认为继承真是一个好东西,是实现复用的最好手段。
    ○ 继承的缺点:继承破坏封装性。基类的很多内部细节都是对派生类可见的,因此这种复用是“白箱复用”;如果基类的实现发生改变,那么派生类的实现也将随之改变。这样就导致了子类行为的不可预知性;从基类继承来的实现是无法在运行期动态改变的,因此降低了应用的灵活性。
    ○ 继承关系有很多缺点,如果合理使用组合则可以有效的避免这些缺点,使用组合关系将系统对变化的适应力从静态提升到动态,而且由于组合将已有对象组合到了新对象中,因此新对象可以调用已有对象的功能。由于组合关系中各个对象的内部实现是隐藏的,我们只能通过接口调用,因此我们完全可以在运行期用实现了同样接口的另外一个对象来代替原对象,从而灵活实现运行期的行为控制。而且使用合成关系有助于保持每个类的职责的单一性,这样类的层次体系以及类的规模都不太可能增长为不可控制的庞然大物。因此我们优先使用组合而不是继承。
  7. 封装变化点
    ○ 设计模式是“封装变化”方法的最佳阐释,归根结底都是寻找软件中可能存在的“变化”,然后利用抽象的方式对这些变化进行封装。由于抽象没有具体的实现,就代表了一种无限的可能性,使得其扩展成为了可能。所以,我们在设计之初,除了要实现需求所设定的用例之外,还需要标定可能或已经存在的“变化”之处。封装变化,最重要的一点就是发现变化,或者说是寻找变化。
    ○ 隔离变化点的好处在于,将系统中经常变化的部分和稳定的部分隔离,有助于增加复用性,并降低系统耦合度。
  8. 针对接口编程,而不是针对实现编程
    ○ 优点:
    ■ 客户端不知道他们所使用对象的具体类型
    ■ 一个对象可以被另一个对象轻易地替换
    ■ 对象不需要硬连接到一个特殊类的对象,因此增加了灵活性
    ■ 松耦合
    ■ 增加了重用的机会
    ■ 增加了组合的机会,因为被包含的对象可以被实现了特定接口的其他对象替换
    ● 缺点:
    ■ 某种程度上增加了设计的复杂性

GOF设计模式分类

  1. 创建模式
    ○ 创建模式(Creational Pattern)是对类的实例化过程的抽象化。一些系统在创建对象时,需要动态地决定怎样创建对象,创建哪些对象,以及如何组合和表示这些对象。创建模式描述了怎样构造和封装这些动态的决定。将对象的创建和对象的使用分开。创建模式分为类的创建模式和对象的创建模式两种。
    ○ 类的创建模式 类的创建模式使用继承关系,把类的创建延迟到子类,从而封装了客户端将得到哪些具体类的信息,并且隐藏了这些类的实例是如何创建和放在一起的。
    ○ 对象的创建模式 对象的创建模式则把对象的创建过程动态的委派给另一个对象,从而动态地决定客户端将得到哪些具体类的实例,以及这些类的实例是如何被创建和组合在一起的。
  2. 结构模式
    ○ 结构模式(Structural Pattern)描述如何将类或者对象结合在一起形成更大的结构。结构模式描述两种不同的东西:类与类实例。根据这一不同,结构模式可以分为类的结构模式和对象的结构模式两种。
    ○ 类的结构模式 类的结构模式使用集成来把类、接口等组合在一起,以形成更大的结构。当一个类从父类继承并实现某接口时,这个新的类就 把父类的结构和接口的结构结合起来。类的结构模式是静态的。一个类的结构模式的典型例子,就是类形式的适配器模式。
    ○ 对象的结构模式 对象的结构模式描述怎样把各种不同类型的对象组合在一起,以实现新的功能的方法。对象的结构模式是动态的。
  3. 行为模式
    ○ 行为模式(Behavioral Pattern)是对在不同的对象之间划分责任和算法的抽象化。行为模式不仅仅是关于类和对象的,而且是关于它们之间的互相作用的。行为模式分为类的行为模式和对象的行为模式两种。
    ○ 类的行为模式 类的行为使用集成关系在几个类之间分配行为。对象的行为模式 对象的行为模式则使用对象的聚合来分配行为。

从封装变化角度对模式分类

Geekband C++第八周_第1张图片
1

重构

● 重构和模式
  ○ 寻找变换点,在变换点处优化设计,应用设计模式
  ○ 不宜一步到位,提倡refractoring to patterns
● 重构的关键技法
  ○ 静态绑定->动态绑定
  ○ 早绑定->晚绑定
  ○ 继承->组合
  ○ 编译时依赖->运行时依赖
  ○ 紧耦合->松耦合

Template method

● 定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。 模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
● 模板方法模式是基于继承的代码复用基本技术。在模板方法模式中,可以将相同的代码放在父类中,而将不同的方法实现放在不同的子类中。
● 类图
Geekband C++第八周_第2张图片
2

Strategy模式

● 定义一系列算法,并逐一封装起来,使它们可以互相替换,使得算法可以独立于使用它的客户程序而变化
● 将子类各自的行为和公共的逻辑分离开来,将子类的行为抽象为算法插入到公共的逻辑中,这样替换子类的行为也不会对公共逻辑产生影响,也不会影响到调用类的逻辑。
● 类结构
Geekband C++第八周_第3张图片
3

观察者模式

● 第一对象间的一对多(变化)的依赖关系,以便当一个对象的状态发生变化时,所有依赖于它的对象都得到通知并自动更新。
● 类图
Geekband C++第八周_第4张图片
4

装饰模式

● 动态地给一个对象添加一些额外的职责。就增加功能来说,Decorator模式相比生成子类更为灵活。
●  装饰器模式提供了改变子类的灵活方案。装饰器模式在不必改变原类文件和使用继承的情况下,动态的扩展一个对象的功能。它是通过创建一个包装对象,也就是装饰来包裹真实的对象。当用于一组子类时,装饰器模式更加有用。如果你拥有一族子类(从一个父类派生而来),你需要在与子类独立使用情况下添加额外的特性,你可以使用装饰器模式,以避免代码重复和具体子类数量的增加。
● 类图
Geekband C++第八周_第5张图片
5

Bridge模式

● 桥模式解决的问题:在软件系统中,某些类型由于自身的逻辑,它具有两个或多个维度的变化,那么如何应对这种“多维度的变化”
● 将抽象部分(业务功能)和实现部分(平台实现)分离,使他们都能独立变化
● 类图
Geekband C++第八周_第6张图片
6

你可能感兴趣的:(Geekband C++第八周)