写点什么

面向对象架构设计

用户头像
Jeff
关注
发布于: 2020 年 06 月 22 日

面向对象架构设计



面向对象定义



  • 万物皆为对象

  • 程序是对象的集合,它们通过发送消息来告知彼此所要做的。

  • 每个对象都有自己的由其他对象所构成的存储。

  • 每个对象都拥有其类型。

  • 某一特定类型的所有对象都可以接受同样的消息。



对象的描述



  1. 状态:表明每个对象可以有自己的数据。

  2. 行为:表明每个对象可以产生行为。

  3. 标识:表明每个对象都区别于其它的对象。(唯一的地址)



面向对象编程三要素(特征)



  1. 封装性(Encapsulation)

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

2. 定义接口

  1. 继承性(Inheritance)

1. IS-A关系

2. HAS-A关系(组合)

  1. 多态性(Polymorphism)

1. 后期绑定(虚函数)

2. 向上转形(Up Casting)



面向对象编程与面向对象分析



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



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



  • 充血模式与贫血模式

  • 领域驱动设计DDD



面向对象设计的目的



强内聚、低耦合,从而使系统



  1. 易扩展:易于增加新的功能

  2. 更强壮:不容易被粗心的程序员破坏

  3. 可移植:能够在多样环境下运行

  4. 更简单:容易理解、容易维护



与之相反,一个不好的软件,会有如下的bad smell



  1. 僵硬:不易改变,很难对系统进行改动,因为每个改动都会迫使许多对系统其他部分的改动。

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



  1. 脆弱:只想改A,结果B被意外破坏。对系统的改动会导致系统中和改动的地方无关的许多地方出现问题

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



  1. 不可移植(牢固性):不能适应坏境的变化。很难揭开系统的纠结,使之成为一些可在其他系统中重用的组件。

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



  1. 导致误用的陷阱(粘滞性):做错误的事比做正确的事更容易,引诱程序员破坏原有的设计

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



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

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



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

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



  1. 晦涩性:很难阅读、理解,没有很好的表现出意图。

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



设计模式



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

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

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



三大类的设计模式



  1. 创建模式

  2. 行为模式

  3. 结构模式



框架



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



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

  • 简化应用开发者的工作

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



框架VS工具



  • 框架调用应用程序代码

  • 应用程序代码调用工具

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

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



面向对象设计的原则



  1. 开闭原则(OCP - Open/Close Principle)

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

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

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



例子:

这样的设计在后续扩展功能的时候会不停加入if else,而且在修改的时候还会影响到之前的代码



通过使用观察者模式(加适配器模式)后,后续增加功能,只需要增加新的adepter就可以,在实现层面上,我们在组件初始化的时候直接指定该组件需要的adepter就可以。



  1. 依赖倒置原则(DIP - Dependency Inversion Principle)

- 高层模块不能依赖底层模块,而是大家都依赖于抽象

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



DIP倒置了



- 模块或包的依赖关系

- 开发顺序和职责



软件的层次化



- 高层决定底层

- 高层被重用



理解:



  • 在软件架构设计上,框架层面出接口,需要使用框架的人去实现接口

  • 框架是一个稳定的对象,使用框架的业务是一个不稳定的对象,当业务不停变化的时候,不会影响到框架的稳定。

  • 这也符合Hollywood原则:“Don't call us, we'll call you."。在该原则下,框架本身应该不被底层业务系统所调用,而是让业务系统运行在框架自身基础上,在运行时被框架所调用。

  • 具体到Spring框架,也是通过暴露一些接口,让业务可以感知框架本身的一些变化(比如event listener)或者参与框架某个层面的逻辑(比如实现filter)。

  • 对于运行期而言,业务代码受限于框架代码的限制,在框架代码的框架下进行执行,对于实现业务来说,可以快速实现自己业务需求。

  • 编写业务的开发人员,也可以在业务压力下,快速开发出伸缩弹性良好的代码。

  • 在业务开发,就三层架构来说:controller、service、dao

  • 从高到低的顺序应该是:controller → service → dao

  • controller更贴近于业务,所以应该是更高层的对象。

  • service应该去实现controller的接口,但是service的方法本身也要维持单一功能,那么就需要有一个实现controller接口抽象方法的方法,在该方法中组合调用单一功能的service方法。所以从职责分离的角度来看,这里是不是应该还有一层facade,通过facade去实现controller的方法,在实现上,去调用对应service的方法,让facade和service层方法尽可能去复用(facade方法的复用,是否只可以在controller里边的调用?还是也可以出现在不同facade里边?)。

  1. Liskov替换原则(LSP)



继承符合的就是该原则,简言之:子类型必须能够替换掉它们的基类型。因为子类型继承自基类型,所有基类型中所有的方法都可以在子类型中找到。



违反该设计原则的案例:



1. 不符合IS-A关系的继承,一定不符合LSP

Java中的Properties继承自Hashtable,就是一个不符合LSP的案例。



2. IS-A关系是关于行为的。从对象的属性来证明这一论点,对于同一个类,所创建的不同对象

- 标识:是不同的。

- 状态:是不同的。

- 行为:是相同的。



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



从Java语法来说:



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

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

- 例如,Object类中:protected Object clone(),子类可以覆盖(override)之并放松其访问控制:public Objects clone(),但反过来就不行。

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

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

- 例如:Properties的契约比Hashtable更严格



解决LSP问题的思路



1. 最简单的,提取共性到基类。

2. 改成组合

继承的优点

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

继承的缺点

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

- 当基类发送改变时,可能会层层影响其下的子类。

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

- 类数量的爆炸。

优先使用组合



  1. 单一职责原则(SRP)

- 又称为“内聚性原则”,意为“一个模块的组成元素之间的功能相关性”

- 将它与引起一个模块改变的作用力相联。一个类,只能有一个引起它的变化的原因。

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



违反的后果



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

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

  1. 接口分离原则(ISP)



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



ISP和SRP的关系:



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

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

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



用户头像

Jeff

关注

还未添加个人签名 2017.11.30 加入

还未添加个人简介

评论

发布
暂无评论
面向对象架构设计