七大著名设计原则
1.单一职责原则(SRP - Single Responsibility Principle)
就一个类而言,应该仅有一个引起它变化的原因,功能要单一
如果一个类承担的职责过多,就等于把这些职责耦合在一起,一个职责的变化可能会削弱或者抑制这个类完成其他职责的能力,这种耦合会导致脆弱的设计,当变化发生时,设计会遭受到意想不到的破坏
软件设计真正要做的许多内容,就是发现职责并把那些职责相互分离,如果你能够想到多于一个的动机去改变一个类,那么这个类就具备有多于一个的职责
2.开放-封闭原则(OCP - Open Closed Principle)
是说软件实体(类、模块、函数等等)应该可以扩展,但是不可修改
对于扩展是开放的,对于更改是封闭的
可以使得系统可以在第一个版本以后不断推出新的版本
无论模块是多么的‘封闭‘,都会在一些无法对之封闭的变化,既然不可能完全封闭,设计人员必须对他设计模式应该对哪种变化封闭做出选择,他必须先猜测最有可能发生的变化种类,然后构造抽象来隔离那些变化
等到变化发生时立即采取行动,在我们最初编写代码时,假设变化不会发生,当变化发生时,我们就创建抽象来隔离以后发生的同类变化
面对需求,对程序的改动是通过增加新代码的,而不是更改现有的代码
我们希望的是在开发工作展开不久就知道可能发生的变化,查明可能发生的变化所等待的时间越长,要创建正确的抽象就越困难
我们应该仅对程序中呈现出频繁变化的那些部分做出抽象,然而,对于应用程序中的每个部分都刻意地进行抽象同样不是一个好主意.拒绝不成熟的抽象和抽象本身一样重要
3.依赖倒转原则(DIP - Dependency Inversion Principle)
高层模块不应该依赖低层模块,两个都应该依赖抽象
抽象不应该依赖细节,细节应该依赖抽象,也就是 面向接口编程,不要对实现编程
其实就是 谁也不要依靠谁,除了约定的接口,大家都可以灵活自如
依赖倒转可以说是面向对象设计标志,用哪种语言编写程序不重要,如果编写时考虑的都是如何针对抽象编程而不是针对细节编程,即程序中所有的依赖关系都是终止于抽象类或者接口
4.李氏替换原则(LSP - Liskov Substitution Principle)
简单说,子类型必须能够替换掉它们的父类型
一个软件实体如果使用的是一个父类的话,那么一定适用于其子类而且它察觉不出父类对象和子类对象的区别,也就是说,在软件里面,把父类都替换成这的子类,程序的行为没有变化
只有当子类可以替换掉父类,软件单位的功能不受到影响时,父类才能真正被利用,而子类也能够在父类的基础上增加新的行为
由于子类型的可替换性才使得使用父类类型的模块在无需修改的情况下就可以扩展
5.迪米特法则(LoD - Law of Demeter)(最少知识原则)
如果两个类不必彼此直接通信,那么这两个类就不应当发生直接的相互作用,如果其中一个类需要调用另一个类的某一个方法的话,可以通过第三者转发这个调用
在类的结构设计上,每一个类都应当尽量降低成员的访问权限
其根本思想是强调了类之间的松耦合,类之间的耦合越弱,越有利于复用,一个处在弱耦合的类被修改,不会对有关系的类造成波及
6.合成/聚合复用原则(CARP - Composite/Aggregate Reuse Principle)
在新对象中聚合已有对象,使之成为新对象的成员,从而通过操作这些对象达到复用的目的
合成方式比继承方式耦合更松散,所以应该少继承、多聚合
合成(组合)和聚合都是关联的特殊种类,聚合表示一种弱的‘拥有‘关系,体现的是A对象可以包含B对象,但B对象不是A对象的一部分;合成则是一种强的‘拥有‘关系,体现了严格的部分和整体的关系,部分和整体的生命周期一样
好处是 优先使用对象的合成/聚合将有助于你保持每个类被封装,并被集中在单个任务上,这样类和继承层次会保持较小规模,并且不太可能增长为不可控制的庞然大物
7.接口隔离原则(ISP - Interface Segregation Principle)
不应该强迫客户程序依赖于它们不用到的方法
尽量应用专门的接口,而不是单一的总接口,接口应该面向用户,将依赖建立在最小的接口上
这个也实现了 单一职责原则,一个类只做特定的事,不要什么都做
GRASP模式中的设计原则
Craig Larman在《Applying UML and Patterns》一书中提出了GRASP设计模式的概念。
作者称其为设计模式,其实,更好的理解应该为设计原则。
GoF设计模式是针对特定问题而提出的解决方法
GRASP则是站在面向对象设计的角度,告诉我们怎么样设计问题空间中的类与它们的行为责任,以及明确类之间的相互关系等等。
GRASP可以说是GoF等设计模式的基础。
GRASP:
General Responsibility Assignment Software patterns
通用职责分配软件模式
核心思想:
“职责分配(Responsibility Assignment)”
用职责设计对象:"Designing Objects with Responsibilities“
进一步理解核心思想:
自己干自己的事(职责的分配)
自己干自己的能干的事(职责的分配)
自己只干自己的事(职责的内聚)
五个基本模式
1,信息专家(Information expert)
2,创建者(Creator)
3,高内聚(High Cohesion)
4,低耦合(Low coupling)
5,控制器(Controller)
四个扩展模式
6,多态性(Polymorphism)
7,纯虚构(Pure Fabrication)
8,间接性(Indirection)
9,防止变异(Protected Variations)
--- 信息专家(Information expert)
GRASP模式中解决类的职责分配问题的最基本的模式。
问题:
当我们为系统发现完对象和职责之后,职责的分配原则(职责将分配给哪个对象执行)是什么?
解决方案:
职责的执行需要某些信息(information),把职责分配给该信息的拥有者。换句话说,某项职责的执行需要某些资源,只有拥有这些资源的对象才有资格执行职责。 --“有能者为之”
满足了面向对象设计的封装性的设计,一般情况下都会满足Information Expert模式。因为Information Expert是对类的属性(信息),以及对类的属性的操作的封装,它符合对象封装性的概念。
优点:
- 信息的拥有者类同时就是信息的操作者类,可以减少不必要的类之间的关联。
- 各类的职责单一明确,容易理解
--- 创建者(Creator)
GRASP模式中解决类的实例的创建职责问题的模式。
问题:
类的实例的创建职责,应该分配给什么样的类?或者说类的实例应该由谁创建?
解决方案:
以下条件之一为真的情况,类A的实例的创建职责就分配给类B。1,B包含A2,B聚集A3,B记录A4,B频繁使用A5,B有A初始化数据
提倡类的实例(对象)创建职责由聚集或包含该对象的对象创建
优点:
- 整个结构清晰易懂
- 有利于类或组件的重用
- 防止职责的分散
- 降低耦合性
--- 高内聚(High Cohesion)
GRASP模式中为降低类的复杂程度,简化控制而提出的面向对象设计的原则性模式。高内聚(High Cohesion)与低耦合(Low Coupling)模式是GRASP其他模式的根本。
问题:
怎么做才能降低类的复杂程度,简化控制?
解决方案:
紧密相关的功能(职责)应该分配给同一个类。 --“各司其职”
所谓内聚,是指单个物体(类)内部的功能聚集度。比如,只包含有相互关联的功能的类,具有高内聚性,同时,它的外部表现(作用,意图)也就明显;反之,如果一个类由一些不相关的功能构成,它的内聚性就低,它的外部表现就不明显,一方面很难理解它的作用和意图,另一方面,一旦需求变化,扩展性就差。
优点:
- 聚集相关功能,结构清晰,容易理解
- 只聚集相关功能,使得类的职责单一明确,从而降低类的复杂程度,使用简单
--- 低耦合(Low coupling)
GRASP模式中为降低类的复杂程度,简化控制而提出的面向对象设计的原则性模式。高内聚(High Cohesion)与低耦合(Low Coupling)模式是GRASP其他模式的根本。
问题:
怎么做才能降低类之间关联程度,能适应需求的变化呢?
解决方案:
为类分配职责时,应该尽量降低类之间的关联关系(耦合性)。亦即,应该以降低类之间的耦合关系作为职责分配的原则。
所谓耦合,是指多个物体(类)之间的物理或者意思上的关联程度。在面向对象方法中,类是最基本的元素,耦合主要指不同类之间相互关联的紧密程度。面向对象里的关联,主要指一个类对另一个类的调用,聚合(包含),参数传递等关系。比如,所谓2个关联得非常紧密的类(高耦合),是指其中一个类发生变化(修改)时,另一个类也不得不跟着发生变化(修改)。
面向对象设计要求类之间满足“低耦合”原则,它是衡量一个设计是否优良的的一个重要标准,因为“低耦合”有助于使得系统中某一部分的变化对其它部分的影响降到最低程度。
优点:
1,一方面,高内聚要求把紧密关联的功能(职责)聚集在同一个类中,防止功能的扩散和类的无谓增加,从而减少类之间的关联,降低类之间的发生耦合的机率。
2,另一方面,高内聚要求把不相关的功能分散到不同的类,类增加了,势必造成相互关联类的增加,从而增大类之间发生耦合的机率。
--- 控制器(Controller)
GRASP模式中解决事件处理职责问题的模式。
问题:
在UI层之外,应该由哪个类来处理(控制)系统操作(事件)呢?或者说,当触发一个系统事件时,应该把对事件的处理职责分配给UI层之外的哪个类呢?
解决方案:
把系统事件的处理职责分配给Controller(控制器)类。
Controller模式提倡用一个专门的类来处理所有的系统事件。或者说Controller模式把所有系统事件的处理职责分配给一个专门的类集中处理。
优点:
- 防止同类职责的分散。满足高内聚,低耦合原则。
- 有利于共通处理(前处理,后处理等)。
- 变化的高适应能力。能够把变化的修改范围控制在最小范围(控制器)之内。
--- 多态性(Polymorphism)
问题:
如何处理基于类型的选择?如何创建可插拔的软件构件?
解决方案:
当相关选择或行为随类型(类)有所不同时,使用多态操作为变化的行为类型分配职责。
--- 间接性(Indirection)
问题:
为了避免两个或多个事物之间的直接耦合,应该如何分配职责?如何使对象解耦合,以支持低耦合并提供复用性潜力?
解决方案:
将职责分配给中介对象,避免它们之间的直接耦合。中介实现了间接性。
大量GOF模式,如适配器、外观等等都是间接性的体现。
--- 纯虚构(Pure Fabrication)
问题:
当你并不想违背高内聚和低耦合或其它目标,但是基于专家模式所提供的方案又不合适时,哪些对象应该承担这一职责?(很多情况下,只对领域对象分配职责会导致不良内聚或耦合,或者降低复用潜力)
解决方案:
对人为制造的类分配一组高内聚的职责,该类并不代表问题领域的概念——虚构的事物,用以支持高内聚,低耦合和复用。
所有GOF设计模式(或其它模式)都是纯虚构。
--- 防止变异(Protected Variations)
问题:
如何设计对象、子系统和系统,使其内部的变化或不稳定性不会对其它元素产生不良影响?
解决方案:
识别预计变化或不稳定之处,分配职责用以在这些变化之外创建稳定接口。
几乎所有的软件或架构设计技巧,都是防止变异的特例,比如封装、多态、接口、虚拟机、配置文件等
防止变异的核心机制
1、数据驱动设计(Data-Driven Design):
通过外置、读取并判断变化因素,防止数据、元数据或说明性变量等对系统产生影响。
2、服务查询(Service Lookup):
Data-Driven Design的特例,如JNDI,UDDI,通过使用查询服务的稳定接口,客户能够避免服务位置变化的影响。
3、解释器驱动的设计(Interpreter-Driven Design):
系统通过外置、读取、解释逻辑而避免了逻辑变化的影响。
4、反射或元级的设计(Reflective or Meta-Level Design):
可避免逻辑或外部代码变化的影响
5、统一访问(Uniform Access):
如C#中的XXX.Name
6、标准语言(Standard Language)
7、Liskov替换原则(LSP):
在对T有任何替换实现或子类(成为S)情况下,引用类型T(某接口或抽象超类)的软件(方法,类……)应该正常或按照预期工作。
8、得墨忒耳定律(Law of Demeter):
不要经历远距离的对象结构路径去向远距离的间接对象发送消息。
9、开放-封闭原则(OCP):
模块应该同时(对扩展、可适应性)开放和(对影响客户的更改)封闭。
--- GRASP软件开发模式-总结
GRASP和GoF都是称为软件开发模式,只是描述的内容和角度不同
GRASP和GoF是不同类型的模式, 出发点不同。 GRASP是解决类之间如何交互, 如何设计合理, 和具体问题无关。
GoF往往是解决一些具体的问题,比如类的具体创建方式等等,而GRASP是解决对象分析的一些基本原则,即你如何去设计你的问题空间中的类和它们的行为,是原则性的东西。
GRASP适用于对象分析和设计中,即在RUP的制作分析模型和设计模型阶段,GoF更适用于在实际编码过程中作为更加具体的指导思想