设计原则

两大主题

系统复用:如何使用已有的组件或类来构建新系统

系统扩展:如何通过扩展现有的系统,同时保持系统的可维护性和灵活性。

设计模式主要关注如何以一种灵活的方式设计和组织对象和类,以满足系统复用和扩展的需求。

七大原则

1 迪米特法则

1.1 定义

如果两个类不必彼此直接通信,那么这两个类就不应当发生直接的相互作用。

如果其中一个类需要调用另一个类的某一个方法的话,可以通过第三者转发这个调用。

1.2 说明

前提共识:在类的结构设计上,每一个类都应当尽量降低成员的访问权限。

根本思想:强调了类之间的松耦合。

生活中的思想体现的举例:

你第一天去公司,怎么会认识 IT部的人呢?

如果公司有很好的管理,那么应该是人事的小杨打个电话到IT部,告诉主管安排人给小菜你装电脑,

就算开始是小菜负责,他临时有急事,主管也可以再安排小李来处理。

由于IT 部是抽象的,哪怕里面的人都离职换了新人,我的电脑出问题也还是可以找IT部解决,而不需要认识其中的同事。

类之间的耦合越弱,越有利于复用,一个处在弱耦合的类被修改,不会对有关系的类造成波及。

信息的隐藏促进了软件的复用。

2 开放-封闭原则

2.1 定义

软件实体(类、模块、函数等等)应该可以被扩展,但是不可被修改。

2.2 本原则的两个特征

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

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

2.3 说明

做任何系统的时候,都不要指望系统一开始时需求确定,就再也不会变化。

怎样的设计才能面对需求的改变却可以保持相对稳定、安全,从而使得系统可以在第一个版本以后不断推出新的版本呢?

开放-封闭给我们答案。(就是多扩展,少修改)

1.

设计的时候,时刻要考虑,尽量让这个类是足够好。

写好了就不要去修改了,如果新需求来,我们增加一些类就完事了,原来的代码能不动则不动。

2.

绝对的对修改关闭是不可能的。

无论模块是多么的'封闭',都会存在一些无法对之封闭的变化。

既然不可能完全封闭,设计人员必须对于他设计的模块应该对哪种变化封闭做出选择。

他必须先猜测出最有可能发生的变化种类,然后构造抽象来隔离那些变化。

3.

猜测程序可能会发生的变化是很难做到的。

但我们却可以在发生小变化时,就及早去想办法应对发生更大变化的可能。

也就是说,等到变化发生时立即采取行动。(即当你发现现有代码结构不灵活、不适用未来的持续开发、不适用未来潜在需要的可扩展的需要时)

在我们最初编写代码时,假设变化不会发生。

当变化发生时,我们就创建抽象来隔离以后发生的同类变化。(即不同程度的重构!)

具体行动的做法:

在尽量减少已有代码的修改,然后重构。

然后面对新需求,对程序的改动是通过增加新代码进行的,而不是更改现有的代码。

不立即行动的后果:

我们希望的是在开发工作展开不久就知道可能发生的变化。

查明可能发生的变化所等待的时间越长,要创建正确的抽象就越困难。

如果不灵活的代码都在很多地方应用了,再考虑抽象、考虑分离,就很困难。

2.4 小结

开放-封闭原则是面向对象设计的核心所在。

开发人员应该仅对程序中呈现出频繁变化的那些部分做出抽象。

对于应用程序中的每个部分都刻意地进行抽象同样不是一个好主意。

拒绝不成熟的抽象和抽象本身一样重要。切记,切记。

3 依赖倒转原则

3.1 定义

A.高层模块不应该依赖低层模块。两个都应该依赖抽象。
B.抽象不应该依赖细节。细节应该依赖抽象。

3.2 说明

说白了,就是要根据接口(即是计算机世界中的某种标准和协议)来编程,不要对根据具体实现来编程。

面向过程的开发时,为了使得常用代码可以复用,一般都会把这些常用代码写成许许多多函数的程序库。

这样我们在做新项目时,去调用这些低层的函数就可以了。

这也就叫做高层模块依赖低层模块

问题也就出在这里。

我们要做新项目时,发现业务逻辑的高层模块都是一样的,

但客户却希望使用不同的数据库或存储信息方式,这时就出现麻烦了。

高层模块都是与低层的访问数据库绑定在一起的,没办法复用这些高层模块,这就非常糟糕了。

就像刚才说的,PC 里如果 CPU、内存、硬盘都需要依赖具体的主板,主板一坏,所有的部件就都没用了。

这显然不合理。

4 里氏替换原则

4.1 定义

子类必须能够替换掉它们的父类。

4.2 举例

问:

如果在面向对象设计时,一个是鸟类,一个是企鹅类,如果鸟是可以飞的,企鹅不会飞。

那么,企鹅是鸟吗? 企鹅可以继承鸟这个类吗?

答:

类拥有父类所有非 private 的行为和属性。

鸟会飞,而企鹅不会飞。

尽管在生物学分类上,企鹅是一种鸟,但在编程世界里,企鹅不能以父类——鸟的身份出现。

因为前提说所有鸟都能飞,而企鹅飞不了。

所以,企鹅不能继承鸟类。

4.3 说明

只有当子类可以替换掉父类,软件单位的功能不受到影响时,父类才能真正被复用。

而子类也能够在父类的基础上增加新的行为。

说白了,本原则是,在贯彻开闭原则且涉及类的继承时的一条注意事项。

扩展的时候(开闭原则),注意要保证扩展的子类,兼容已有的代码、支持已有的功能。

4.4 小结

正是由于子类型的可替换性才使得使用父类类型的模块在无需修改的情况下就可以扩展。

再回过头来看依赖倒转原则,高层模块不应该依赖低层模块,两个都应该依赖抽象,对这句话你会有更深入的理解。

5 单一职责原则

就一个类而言,应该仅有一个引起它变化的原因。

如果一个类承担的职责过多,就等于把这些职责耦合在一起,其中一个职责的变化可能会削弱或者抑制这个类完成其他职责的能力。

这种耦合会导致脆弱的设计,当变化发生时,设计会遭受到意想不到的破坏。

当然,软件设计真正要做的许多内容,就是发现职责并把那些职责相互分离【ASD】。

其实要去判断是否应该分离出类来,也不难,那就是如果你能够想到多于一个的动机去改变一个类,

那么这个类就具有多于一个的职责【ASD】,就应该考虑类的职责分离。

6 接口隔离原则

接口隔离原则,其 "隔离" 并不是准确的翻译,真正的意图是 “分离” 接口(的功能)

接口隔离原则强调:客户端不应该依赖它不需要的接口;一个类对另一个类的依赖应该建立在最小的接口上。

小结:

  1. 接口隔离原则的思想在于建立单一接口,尽可能地去细化接口,接口中的方法尽可能少
  2. 但是凡事都要有个度,如果接口设计过小,则会造成接口数量过多,使设计复杂化。所以一定要适度。

7 组合/聚合复用原则

在一个新的对象里面使用一些已有的对象,使之成为新对象的一部分。

新的对象通过向这些对象的委派达到复用已有功能的目的。

在面向对象的设计中,如果直接继承基类,会破坏封装,因为继承将基类的实现细节暴露给子类;

如果基类的实现发生了改变,则子类的实现也不得不改变;

从基类继承而来的实现是静态的,不可能在运行时发生改变,没有足够的灵活性。

于是就提出了组合/聚合复用原则,也就是在实际开发设计中,尽量使用组合/聚合,不要使用类继承。

小结:

  1. 总体说来,组合/聚合复用原则告诉我们:组合或者聚合好过于继承。
  2. 聚合组合是一种 “黑箱” 复用,因为细节对象的内容对客户端来说是不可见的。
posted @ 2024-03-29 14:59  AbendEssen29  阅读(3)  评论(0编辑  收藏  举报