关闭

面向对象的设计原则与经验

757人阅读 评论(0) 收藏 举报

         面向对象设计经验:

         (1)所有数据都应该隐藏在所在的类的内部。p13
 
  (2)类的使用者必须依赖类的共有接口,但类不能依赖它的使用者。p15
 
  (3)尽量减少类的协议中的消息。p16
 
  (4)实现所有类都理解的最基本公有接口[例如,拷贝操作(深拷贝和浅拷贝)、相等性判断、正确输出内容、从ASCII描述解析等等]。 p16
 
  (5)不要把实现细节(例如放置共用代码的私有函数)放到类的公有接口中。p17

   如果类的两个方法有一段公共代码,那么就可以创建一个防止这些公共代码的私有函数。
 
  (6)不要以用户无法使用或不感兴趣的东西扰乱类的公有接口。p17
 
  (7)类之间应该零耦合,或者只有导出耦合关系。也即,一个类要么同另一个类毫无关系,要么只使用另一个类的公有接口中的操作。 p18
 
  (8)类应该只表示一个关键抽象。p19
 
  包中的所有类对于同一类性质的变化应该是共同封闭的。一个变化若对一个包影响,则将对包中的所有类产生影响,而对其他的包不造成任何影响 .
 
  (9)把相关的数据和行为集中放置。p19
 
  设计者应当留意那些通过get之类操作从别的对象中获取数据的对象。这种类型的行为暗示着这条经验原则被违反了。
 
  (10)把不相关的信息放在另一个类中(也即:互不沟通的行为)。p19
 
  朝着稳定的方向进行依赖.
 
  (11)确保你为之建模的抽象概念是类,而不只是对象扮演的角色。p23
 
  (12)在水平方向上尽可能统一地分布系统功能,也即:按照设计,顶层类应当统一地共享工作。p30
 
  (13)在你的系统中不要创建全能类/对象。对名字包含Driver、Manager、System、Susystem的类要特别多加小心。p30
 
  规划一个接口而不是实现一个接口。
 
  (14)对公共接口中定义了大量访问方法的类多加小心。大量访问方法意味着相关数据和行为没有集中存放。p30
 
  (15)对包含太多互不沟通的行为的类多加小心。p31
 
  这个问题的另一表现是在你的应用程序中的类的公有接口中创建了很多的get和set函数。
 
  (16)在由同用户界面交互的面向对象模型构成的应用程序中,模型不应该依赖于界面,界面则应当依赖于模型。p33
 
  (17)尽可能地按照现实世界建模(我们常常为了遵守系统功能分布原则、避免全能类原则以及集中放置相关数据和行为的原则而违背这条原则) 。p36
 
  (18)从你的设计中去除不需要的类。p38
 
  一般来说,我们会把这个类降级成一个属性。
 
  (19)去除系统外的类。p39
 
  系统外的类的特点是,抽象地看它们只往系统领域发送消息但并不接受系统领域内其他类发出的消息。 
 
  (20)不要把操作变成类。质疑任何名字是动词或者派生自动词的类,特别是只有一个有意义行为的类。考虑一下那个有意义的行为是否应当迁移到已经存在或者尚未发现的某个类中。p40
 
  (21)我们在创建应用程序的分析模型时常常引入代理类。在设计阶段,我们常会发现很多代理没有用的,应当去除。p43
 
  (22)尽量减少类的协作者的数量。p52
 
  一个类用到的其他类的数目应当尽量少。 
 
  (23)尽量减少类和协作者之间传递的消息的数量。p55 

  (24)尽量减少类和协作者之间的协作量,也即:减少类和协作者之间传递的不同消息的数量。p55
 
  (25)尽量减少类的扇出,也即:减少类定义的消息数和发送的消息数的乘积。p55
 
  (26)如果类包含另一个类的对象,那么包含类应当给被包含的对象发送消息。也即:包含关系总是意味着使用关系。p55
 
  (27)类中定义的大多数方法都应当在大多数时间里使用大多数数据成员。p57
 
  (28)类包含的对象数目不应当超过开发者短期记忆的容量。这个数目常常是6。p57
 
  当类包含多于6个数据成员时,可以把逻辑相关的数据成员划分为一组,然后用一个新的包含类去包含这一组成员。 
 
  (29)让系统功能在窄而深的继承体系中垂直分布。p58
 
  (30)在实现语义约束时,最好根据类定义来实现。这常常会导致类泛滥成灾,在这种情况下,约束应当在类的行为中实现,通常是在构造函数中实现,但不是必须如此。p60
 
  (31)在类的构造函数中实现语义约束时,把约束测试放在构造函数领域所允许的尽量深的包含层次中。p60
 
  (32)约束所依赖的语义信息如果经常改变,那么最好放在一个集中式的第3方对象中。p60
 
  (33)约束所依赖的语义信息如果很少改变,那么最好分布在约束所涉及的各个类中。p60 
 
  (34)类必须知道它包含什么,但是不能知道谁包含它。p61
 
  (35)共享字面范围(也就是被同一个类所包含)的对象相互之间不应当有使用关系。p61
 
  (36)继承只应被用来为特化层次结构建模。p74
 
  (37)派生类必须知道基类,基类不应该知道关于它们的派生类的任何信息。p74
 
  (38)基类中的所有数据都应当是私有的,不要使用保护数据。p75
 
  类的设计者永远都不应该把类的使用者不需要的东西放在公有接口中。 
 
  (39)在理论上,继承层次体系应当深一点,越深越好。p77
 
  (40)在实践中,继承层次体系的深度不应当超出一个普通人的短期记忆能力。一个广为接受的深度值是6。p77
 
  (41)所有的抽象类都应当是基类。p81
 
  (42)所有的基类都应当是抽象类。p82
 
  (43)把数据、行为和/或接口的共性尽可能地放到继承层次体系的高端。p85 
 
  (44)如果两个或更多个类共享公共数据(但没有公共行为),那么应当把公共数据放在一个类中,每个共享这个数据的类都包含这个类。 p88
 
  (45)如果两个或更多个类有共同的数据和行为(就是方法),那么这些类的每一个都应当从一个表示了这些数据和方法的公共基类继承。 p89
 
  (46)如果两个或更多个类共享公共接口(指的是消息,而不是方法),那么只有他们需要被多态地使用时,他们才应当从一个公共基类继承。 p89
 
  (47)对对象类型的显示的分情况分析一般是错误的。在大多数这样的情况下,设计者应当使用多态。p89
 
  (48)对属性值的显示的分情况分析常常是错误的。类应当解耦合成一个继承层次结构,每个属性值都被变换成一个派生类。 p96
 
  (49)不要通过继承关系来为类的动态语义建模。试图用静态语义关系来为动态语义建模会导致在运行时切换类型。p97 
 
  (50)不要把类的对象变成派生类。对任何只有一个实例的派生类都要多加小心。p99
 
  (51)如果你觉得需要在运行时刻创建新的类,那么退后一步以认清你要创建的是对象。现在,把这些对象概括成一个类。 p103
 
  (52)在派生类中用空方法(也就是什么也不做的方法)来覆写基类中的方法应当是非法的。p103
 
  (53)不要把可选包含同对继承的需要相混淆。把可选包含建模成继承会带来泛滥成灾的类。p108
 
  (54)在创建继承层次时,试着创建可复用的框架,而不是可复用的组件。p112
 
  (55)如果你在设计中使用了多重继承,先假设你犯了错误。如果没犯错误,你需要设法证明。p120
 
  (56)只要在面向对象设计中用到了继承,问自己两个问题:(1)派生类是否是它继承的那个东西的一个特殊类型?(2)基类是不是派生类的一部分?p121
 
  (57)如果你在一个面向对象设计中发现了多重继承关系,确保没有哪个基类实际上是另一个基类的派生类。p122
 
  (58)在面向对象设计中如果你需要在包含关系和关联关系间作出选择,请选择包含关系。p135
 
  (59)不要把全局数据或全局函数用于类的对象的薄记工作。应当使用类变量或类方法。p140
 
  (60)面向对象设计者不应当让物理设计准则来破坏他们的逻辑设计。但是,在对逻辑设计作出决策的过程中我们经常用到物理设计准则。 p149
 
  (61)不要绕开公共接口去修改对象的状态。p164

面向对象的设计原则(整理于敏捷开发)

“开-闭”原则(OCP)对可变性封装
 
The OpenThe Open--Closed PrincipleClosed Principle
任何系统在其生命周期中都会发生变化。如果我们希望开发出的系统不会在第一版本后就被抛弃,那么我们就必须牢牢记住这一点。
软件组成实体(类,模块,函数,等等)应该是可扩展的,但是不可修改的。
OCP OCP特征 特征
可扩展(对扩展是开放的)
模块的行为功能可以被扩展,在应用需求改变或需要满足新的应用需求时,我们可以让模块以不同的方式工作
不可更改(对更改是封闭的)
这些模块的源代码是不可改动的。任何人都不许修改模块的源代码。
关键是抽象!
模块可以操作一个抽象体。由于模块依赖于一个固定的抽象体,因此它可以是不允许修改(closed for modification)的;同时,通过从这个抽象体派生,也可扩展此模块的行为功能。
符合OCP原则的程序只通过增加代码来变化而不是通过更改现有代码来变化,因此这样的程序就不会引起象非开放―封闭(open-closed)的程序那样的连锁反应的变化。

对可变性的封装
考虑系统中什么可能会发生变化
一种可变性不应当散落在代码的很多角落里,而应当被封装到一个对象里
正确理解继承
一种可变性不应当与另一个可变性混合在一起
选择性的封闭(Strategic Closure)没有任何一个大的程序能够做到100%的封闭。一般来讲,无论模块是多么的“封闭”,都会存在一些无法对之封闭的变化。既然不可能完全封闭,因此就必须选择性地对待这个问题。也就是说,设计者必须对于他(她)设计的模块应该对何种变化封闭做出选择。

里氏替换原则(LSP)如何进行继承

Liskov替换原则替换原则
LSP
LSP The The Liskov Substitution Principle
OCP原则背后的主要机制是抽象和多态。支持抽象和多态的关键机制是继承。
LSP LSP的定义 的定义
若对于每一个类型S的对象o1,都存在一个类型T的对象o2,使得在所有针对T编写的程序P中,用o1替换o2后,程序P的行为功能不变,则S是T的子类型。
LSP原则清楚地指出,OOD中Is-A关系是就行为功能而言。行为功能(behavior)不是内在的、私有的,而是外在、公开的,是客户程序所依赖的。行为功能(behavior)才是软件所关注的问题!所有派生类的行为功能必须和客户程序对其基类所期望的保持一致。
LSP LSP和DBC DBC
DBC(Design by Contract)定义把类和其客户之间的关系看作是一个正式的协议,明确各方的权利和义务。DBC对类的要求类的方法声明为先决条件(precondition)和后续条件(postcondition)。为了让方法得以执行,先决条件必须为真。完成后,方法保证后续条件为真。DBC对派生类的要求当重新定义派生类中的例行程序时,我们只能用更弱的先决条件和更强的后续条件替换之。
LSP-结论
LSP原则是符合OCP原则应用程序的一项重要特性。仅当派生类能完全替换基类时,我们才能放心地重用那些使用基类的函数和修改派生类型。
 
依赖倒转原则(DIP)针对接口编程

高层模块不应该依赖于低层模块。二者都应该依赖于抽象。
抽象不应该依赖于细节。细节应该依赖于抽象。
实施重点
从问题的具体细节中分离出抽象,以抽象方式对类进行耦合
不足
导致生成大量的类
假定所有的具体类都是会变化的,这也不总是正确的
DIP与设计模式
DIP以LSP为基础,是实现OCP的主要手段,是设计模式研究和应用的主要指导原则

接口隔离原则(ISP)恰当的划分角色和接口
接口的污染(Interface Contamination)一个没有经验的设计师往往想节省接口的数目,将一些功能相近或功能相关的接口合并,并将这看成是代码优化的一部分。
定义:从一个客户类的角度来讲:一个类对另外一个类的依赖性应当是建立在最小的接口上的。使用多个专门的接口比使用单一的总接口要好

合成/聚合复用原则(CARP)尽量使用合成/聚合、尽量不使用继承
定义:在一个新的对象里面使用一些已有的对象,使之成为新对象的一部分;新的对象通过向这些对象的委派达到复用这些对象的目的。

面向对象设计的11原则

  软件业曾经爆发过的所有变革里,其中曾经有两个派系如此广泛的深入人心,它们就是结构化编程和面向对象编程。所有主流的现代编程语言都被它们两个激烈的影响着。实际上,要想不像结构化和面向对象编程的样子来编写程序都是一件难事。我们的主流编程语言都没有goto,因此它们服从了结构化编程中最重要的禁令。我们的大多数主流编程语言都是基于类的,而且不支持在类以外定义函数或是变量,因此也避免了面向对象编程中最容易坠入的陷阱。

  用这些编程语言所编写的程序可能看起来是结构化的或是面向对象的,可是“看起来”是会欺骗人的。当今的编程语言经常不顾他们所从属那种派系的编程语言的基本原则。我会在另篇blog中再探讨结构化编程的原则,本篇,我想要谈论的是面向对象编程的基本原则。

  在1995年的三月,我写了一篇文章并发表在comp.object上,那是我第一次写OOD(译注1)原则的文章,此后就一发不可收拾的写了很多。你可以在我的PPP一书(译注2)中看到它们,在object mentor的很多文章中也都有,其中就有那篇众所周知的纲要(近期会译为中文,请关注)。

  这些原则着重于OOD中的依赖管理方面,而淡化抽象与建模方面。这并不是说OO在抽象方面不够强大,或是OO不适合构建模型。当然有很多人都在使用OO的这些部分,只是这些原则集中关注于依赖管理。

  依赖管理是我们每个人都要面对的问题,每当我们在屏幕面前打开那些彼此纠结又令人作呕的代码,我们就会遭受不良的依赖管理所带来的恶果。不良的依赖管理导致代码难以改变,易被破坏,而且不可重用。实际上,我在PPP一书中谈论过很多不同的设计坏味道,而这些都与依赖管理有关。从另一方面来说,如果依赖经过了良性的管理,代码就可以保持灵活性、健壮性和重用性。所以依赖管理和这些相关原则是程序员们渴求的让软件保持优良架构的基石。

  头五项原则是关于类设计的,它们是:

   SRP,单一职责原则,一个类应该有且只有一个改变的理由。
   OCP,开放封闭原则,你应该能够不用修改原有类就能扩展一个类的行为。
   LSP,Liskov替换原则,派生类要与其基类自相容。
   DIP,依赖倒置原则,依赖于抽象而不是实现。
   ISP,接口隔离原则,客户只要关注它们所需的接口。

  另外的六项是关于包的设计原则。在本文中,包是指一个二进制的可发布文件,比如.jar文件、或dll文件,而不是Java包或是C++的命名空间(译注3)。

  头三项包原则是关于包内聚性的,它们会告诉我们该把什么划分到包中:

   REP,重用发布等价原则,重用的粒度就是发布的粒度。
   CCP,共同封闭原则,包中的所有类对于同一类性质的变化应该是共同封闭的。 
   CRP,共同重用原则,一个包中的所有类应该是共同重用的。

  最后的三项原则是关于包之间的耦合性原则的,并且论述了评价系统中包结构优良与否的评判标准。

   ADP,无环依赖原则,在包的依赖关系图中不允许存在环。
   SDP,稳定依赖原则,朝着稳定的方向进行依赖。
   SAP,稳定抽象原则,包的抽象程度应该和其稳定程度一致。
 

 译注:

1,OOD,全称Object Oriented Design,即面向对象设计。

2,PPP,即Bob大叔的著作《敏捷软件开发 原则、模式与实践》一书以及其相关书籍,因都有“原则、模式与实践”,即Priciples, Patterns and Practices,故常简称为PPP。

3,命名空间,原文为namespace,也译作名字空间。它是一种特殊的作用域,它包含了处于该作用域内的所有标示符,且本身也用一个标示符来表示,这样便于将一系列在逻辑上相关的标示符用一个标示符来组织。就Java编程语言来说,命名空间是通过java包来表达的,所有代码都归属与一个包。来自其他包中的代码要通过指定包名来引用某项特定的标示符,例如,包java.lang中的String类要通过java.lang.String的形式引用。在C++中,命名空间常用来避免命名冲突,尽管现今的C++语言对命名空间做出了扩展,但过去的C++代码很少使用此项功能。

从设计原则到设计模式
  1、针对接口编程,而不是针对实现编程
  2、优行使用类组合,而不是类继承
  3、变化点封装
  4、使用重构得到模式--设计模式的应用不宜先入为主

0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:118743次
    • 积分:1413
    • 等级:
    • 排名:千里之外
    • 原创:10篇
    • 转载:50篇
    • 译文:0篇
    • 评论:14条
    最新评论