写点什么

成为架构师 - 架构师训练营第 02 周

发布于: 2020 年 10 月 31 日



面向对象编程三要素:



封装性(Encapsulation)

• 隐藏实现细节(访问控制)

• 定义接口



继承性(Inheritance)

• IS-A关系

• HAS-A关系(组合)

多态性(Polymorphism)

• 后期绑定(虚函数)

• 向上转形(Up Casting)



子类实现父类或者接口的抽象方法,程序使用抽象父类或者接口编程,运行期注入不同的子类,程序就表现出不同的形态,是为多态。



面向对象编程不是使用面向对象的编程语言进行编程,而是利用多态特性进行编程。



面向对象分析是将客观世界,即编程的业务领域进行对象分析。

• 充血模型与贫血模型

• 领域驱动设计DDD



面向对象设计的目的和原则



面向对象设计的目的:强内聚、低耦合,从而使系统

易扩展 - 易于增加新的功能

更强壮 - 不容易被粗心的程序员破坏

可移植 - 能够在多样的环境下运行

更简单 - 容易理解、容易维护



面向对象设计的原则:

  • 为了达到上述设计目标,有人总结出了多种指导原则

• “原则”是独立于编程语言的,甚至也可以用于非面向对象的编程语言中。



设计模式(design patterns)

设计模式是用于解决某一种问题的通用的解决方案。

设计模式也是语言中立的。

设计模式贯彻了设计原则。



Gang of Four(Erich Gamma, Richard Helm, Ralph Johnson and John Vlissides)提出了三大类23种基本的设计模式:

• 创建模式

• 行为模式

• 结构模式



框架(frameworks)

框架是用来实现某一类应用的结构性的程序, 是对某一类架构方案可复用的设计与实现

• 如同框架结构的大厦的框架

• 简化应用开发者的工作

• 实现了多种设计模式,使应用开发者不需要花太大的力气,就能设计出结构良好的程序来



不同领域的框架

  • 微软公司为Windows编程开发了MFC框架。

  • Java为它的GUI(图形用户界面)开发了AWT框架。

  • 还有许多开源的框架:MyBatis,Spring等。

  • Web服务器也是框架:Tomcat



框架 VS 工具



框架调用应用程序代码

应用程序代码调用工具

架构师用框架保证架构的落地

架构师用工具提高开发效率



软件设计的“臭味”

软件设计的最终目的,是使软件达到“强内聚、松耦合”,从而使软件:

  • 易扩展 - 易于增加新的功能

  • 更强壮 - 不容易被粗心的程序员破坏

  • 可移植 - 能够在多样的环境下运行

  • 更简单 - 容易理解、容易维护



与之相反,一个“不好的”软件,会发出如下“臭味”:

  • 僵硬 - 不易改变。

  • 脆弱 - 只想改 A,结果 B 被意外破坏。

  • 不可移植 - 不能适应环境的变化。

  • 导致误用的陷阱 - 做错误的事比做正确的事更容易,引诱程序员破坏原有的设计。

  • 晦涩 - 代码难以理解。

  • 过度设计、copy-paste 代码。



僵化性(Rigidity):很难对系统进行改动,因为每个改动都会迫使许多对系统其他部分的改动。

• 如果单一的改动会导致依赖关系的模块中的连锁改动,那么设计就是僵化的,必须要改动的模块越多,设计就越僵化。



脆弱性(Fragility):对系统的改动会导致系统中和改动的地方无关的许多地方出现问题。

• 出现新问题的地方与改动的地方没有概念上的关联。要修正这些问题又会引出更多的问题,从而使开发团队就像一只不停追逐自己尾巴的狗一样。



牢固性(Immobility):很难解开系统的纠结,使之成为一些可在其他系统中重用的组件。

• 设计中包含了对其他系统有用的部分,而把这些部分从系统中分离出来所需的努力和风险是

巨大的。



粘滞性(Viscosity):做正确的事情比做错误的事情要困难。

• 面临一个改动的时候,开发人员常常会发现会有多种改动的方法。有的方法会保持系统原来的设计,而另外一些则会破坏设计,当那些可以保持系统设计的方法比那些破坏设计的方法跟难应用是,就表明设计具有高的粘滞性,作错误的事情就很容易。



不必要的复杂性(Needless Complexity):设计中包含有不具任何直接好处的基础结构

• 如果设计中包含有当前没有用的组成部分,他就含有不必要的复杂性。当开发人员预测需求的变化,并在软件中放置了处理那些潜在变化的代码时,常常会出现这种情况。



不必要的重复(Needless Repetition):设计中包含有重复的结构,而该重复的结构本可以使用单一的抽象进行统一。

• 当 copy,cut,paste 编程的时候,这种情况就会发生。



晦涩性(Opacity)

• 代码可以用清晰、富有表现力的方式编写,也可以用晦涩、费解的方式编写。一般说来,随 着时间的推移,代码会变得越来越晦涩。



OOD 原则一:开/闭原则(OCP)

OCP - Open/Closed Principle

对于扩展是开放的(Open for extension)

对于更改是封闭的(Closed for modification



• 简言之:不需要修改软件实体(类、模块、函数等),就应该能实现功能的扩展。

传统的扩展模块的方式就是修改模块的源代码。如何实现不修改而扩展呢?

• 关键是抽象!



OOD 原则二:依赖倒置原则(DIP)

DIP - Dependency Inversion Principle

• 高层模块不能依赖低层模块,而是大家都依赖于抽象;

• 抽象不能依赖实现,而是实现依赖抽象。



DIP 倒置了什么?

• 模块或包的依赖关系

• 开发顺序和职责



软件的层次化

• 高层决定低层

• 高层被重用



高层依赖不依赖低层模块,而是去依赖接口



接口要属于高层模块



当接口属于高层模块,由高层去调用,高层可以根据自己的使用场景进行模块接口设计或者抽象的设计



低层模块按照高层的抽象和设计进行实现



而不是自己实现的一个接口,写了一个接口,写了一个实现,然后根据这个实现,抽象一个接口来供高层调用



框架的核心



好莱坞规则:don't call me I will call you



框架设计的基本技巧:



框架去调用我们的应用程序,我们的应用程序不会去调用框架,同时框架也不依赖我们的应用程序



当我们实现了高层模块定义的接口或规范的时候,高层模块就可以调用我们的代码,就可以执行我们的代码,但是却不依赖我们的代码,



框架主导了系统的软件的整个结构、整个流程



OOD 原则三:Liskov替换原则(LSP



在 Java/C++ 这样的静态类型语言中,实现 OCP 的关键在于抽象,而抽象的威力在于多



• 一个正确的继承要符合什么要求呢?

• 答案:Liskov 替换原则



1988年,Barbara Liskov 描述这个原则:

  • 若对每个类型 T1 的对象 o1,都存在一个类型 T2 的对象 o2,使得在所有针对 T2 编写的程

  • 简言之:子类型(subtype)必须能够替换掉它们的基类型(base type)。



为什么正方形 IS-NOT-A 长方形呢?IS-A 关系是关于行为的。

• 从行为方式来看,正方形和长方形是不同的。



从对象的属性来证明这一论点,对于同一个类,所创建的不同对象,它们的:

  • 标识 - 是不同的。

  • 状态 - 是不同的。

  • 行为 - 是相同的。

  • 因此,设计和界定一个类,应该以其行为作为区分。



从“契约”的角度来看 LSP

LSP 要求,凡是使用基类的地方,一定也适用于其子类。

从 Java 语法角度看,意味着:

  • 子类一定得拥有基类的整个接口。

  • 子类的访问控制不能比基类更严格。

  • 例如,Object类中有一个方法:

  • protectedObjectclone();

  • 子类中可以覆盖(override)之并放松其访问控制:

  • publicObjectclone();

  • 但反过来是不行的,例如:

  • 覆盖publicStringtoString()方法,并将其访问控制缩小成private,编译器不可能允许这样的代码通过编译。



从更广泛的意义来看,子类的『契约』不能比基类更『严格』

• 例如,正方形长宽相等,这个契约比长方形要严格,因此正方形不是长方形的子类。

• 例如,Properties 的契约比 Hashtable 更严格。



如何重构代码,以解决 LSP 问题?

方法1. 提取共性到基类:

方法2. 改成组合



继承 vs. 组合



继承和组合是 OOP 的两种扩展手段



继承的优点:

• 比较容易,因为基类的大部分功能可以通过继承直接进入子类。



继承的缺点:

• 继承破坏了封装,因为继承将基类更多的细节暴露给子类。因而继承被称为“白盒复用”。

• 当基类发生改变时,可能会层层影响其下的子类

• 继承是静态的,无法在运行时改变组合。

• 类数量的爆炸。



应该优先使用组合



何时检测 LSP?一个模型,如果孤立地看,并不具有真正意义上的有效性。

• 孤立地看,Rectangle 和 Square 并没有什么问题。



通过它的客户程序才能体现出来

• 从对基类做出合理假设的客户程序的角度来看, Rectangle 和 Square 这个模型就是有问题的。



有谁知道设计的使用者会做出什么合理的假设呢?

  • 大多数这样的假设都很难预测。

  • 避免“过于复杂”或“过度设计”。

  • 只预测明显的违反 LSP 的情况,而推迟其它的预测。



可能违反 LSP 的征兆

派生类中的退化函数

派生类中抛出基类不会产生的异常。



OOD 原则四:单一职责原则(SRP)

SRP - Single Responsibility Principle



  • 又被称为“内聚性原则(Cohesion)”,意为:

  • 一个模块的组成元素之间的功能相关性

  • 将它与引起一个模块改变的作用力相联,就形成了如下描述:

  • 一个类,只能有一个引起它的变化的原因。



什么是职责

• 单纯谈论职责,每个人都会得出不同的结论

• 因此我们下一个定义 :

Ø 一个职责是一个变化的原因。



违反 SRP 原则的后果

后果

  • 脆弱性 - 把绘图和计算功能耦合在一起,当修改其中一个时,另一个功能可能会意外受损。

  • 不可移植性 - 计算几何应用只需要使用“计算面积”的功能,却不得不包含 GUI 的依赖。



OOD 原则五:接口分离原则(ISP)



ISP - Interface Segregation Principle

• 不应该强迫客户程序依赖它们不需要的方法。

ISP 和 SRP 的关系

  • ISP 和 SRP 是相关的,都和“内聚性”有关。

  • SRP 指出应该如何设计一个类 —— 只能有一种原因才能促使类发生改变。

  • ISP 指出应该如何设计一个接口 —— 从客户的需要出发,强调不要让客户看到他们不需要的方法。



用户头像

还未添加个人签名 2018.08.11 加入

还未添加个人简介

评论 (1 条评论)

发布
用户头像
很完整,考虑下思维导图吧~
2020 年 11 月 08 日 23:03
回复
没有更多了
成为架构师 - 架构师训练营第 02 周