设计模式01-面向对象设计的几个基本原则
2014-06-16 15:17
281 查看
面向对象设计的几个基本原则
要考试了,一些面向对象设计的原则小结一下:
单一职责原则[b](SRP,Single Responsibility Principle)[/b]
就一个类而言,应该仅有一个引起它变化的原因;
例子:手机的过多功能违反了单一职责原则;
如果一个类承担的职责过多,就等于把这些职责耦合在一起,一个职责的变化可能会削弱或者抑制这个类完成其他职责的能力。这种耦合会导致脆弱的设计,当变化发生时,设计会遭受到意想不到的破坏;
软件设计真正要做的许多内容,就是发现职责并把那些职责相互分离。要去判断是否应该分离出类来,也不难,那就是如果你能够想到多于一个的动机去改变一个类,那么这个类就具有多于一个的职责,就应该考虑类的职责分离;
【例子:调制解调器类】
完成两个职责:建立/中断连接、发送/接收数据。
问题:如果应用程序会改变数据连接方式,而必须修改Modem类,使任何依赖Modem类的元素都要重新编译,包括不用到连接功能的应用。
解决办法:重构Modem类,建立两个接口,一个专门负责连接、另一个专门负责数据发送。依赖Modem类的元素根据职责的不同分别依赖不同的接口。
最后由ModemImplementation类实现这两个接口。
使用SRP注意点:
1、 SRP实质上规定了类的粒度划分 ;
2、如果职责没有变化的征兆,则不应用SRP;
3、如果程序总是要求两个职责同时变化,也不必分离职责;
5、职责分离后,可以用Facade或Proxy模式对外提供一个聚合的操作接口 ;
优点/作用:
有助于理清思路。
使我们的编码、测试和维护变得简单。
有助于代码的重用。
有助于系统的扩展。
过度的分离会导致不必要的复杂性;
开放-封闭原则
软件实体(类、模块、函数等)应该可以扩展,但是不可修改。该原则有两个特征,一个是"对于扩展是开放的(open for extension)",另一个是"对于更改是封闭的(closed for modification)";
变化不太好预测,但在变化发生时,就要立即创建抽象来隔离以后发生的同类变化;
开放-封闭原则的精神——面对需求,对程序的改动是通过增加新代码(扩展原有的类)来进行的,而不是更改现有的代码;
[开闭原则应用例子]:
将条件转移语句改写成为多态性的代码重构
条件转移语句——某种可变性。
将条件转移的商业逻辑封装到不同的具体子类中。
用多态性代替条件转移语句。
一个引申的问题:多态性的滥用——”多态性污染“
任何语言都提供条件转移功能。
使用多态性代替条件转移意味着创建大量的类。
一个类如果有M个方法,每个方法都有一个N段的条件转移语句。如果都用多态性代替,会造成M*N个不同的类。
无原则改写条件转移语句为多态性是一种没有意义的浪费。
何时使用重构做法:
条件转移语句确实封装了商务逻辑的可变性
条件转移语句随着时间在变化——需要改变
需要扩展——有意义的可变性
拒绝不成熟的抽象和抽象本身一样重要!
依赖倒转原则
表述一:应该让框架调用你的代码,而不要由你主动调用框架本身。
表述二:抽象不应依赖于细节,细节应当依赖于抽象。
表述三:要针对接口编程,不要针对实现编程。
抽象可以理解成电脑主板上的一个个插槽,细节则是CPU、内存、硬盘等部件的工作原理;
高层模块(电脑部件)不应该依赖低层模块(主板),两个都应该依赖抽象(主板插槽);
依赖倒转原则很经典,可以说是面向对象设计的标志!
依赖倒转原则的实现是建立在里氏代换原则之上的。
优点:
不必知道对象的类型,只要求对象遵守接口。不必知道对象的类,只需知道定义接口的抽象类。——降低了子系统间的实现依赖。
依赖倒转关系强调了系统内的实体之间关系的灵话性。从具体耦合变为抽象耦合。
三种耦合关系:
零耦合:两个类没有耦合关系。
抽象耦合:耦合发生在一个具体类和一个抽象类之间,两个必须发生关系的类之间存有最大的灵活性。
具体耦合;耦合发生在两个具体的(可实例化的)类之间,一个类直接引用另一个具体类。
运用依赖倒转原则时可能碰到的问题:
创建对象要避免对具体类的直接引用,有时需要创建抽象类。
会导致大量的类。维护难度大。
倒转原则假定所有的具体类都是会变化的,不排除有一些具体类相当稳定。
里氏代换原则
一个软件实体如果使用的是一个父类的话,那么一定适用于其子类,而且它察觉不出父类对象和子类对象的区别。
也就是说,在软件里面,把父类都替换成它的子类,程序的行为没有变化,简单地说,子类型必须能够替换掉它们的父类型;
正因为有了这个原则,使得继承利用成为了可能,只有当子类可以替换掉父类,软件单位的功能不受到影响时,父类才能真正被复用,而子类也能够在父类的基础上增加新的行为;
由于子类型的可替换性才使得使用父类类型的模块在无需修改的情况下就可以扩展。
[例]:假设有两个类: Base 类,Derived类,且 Derived 类是 Base类的子类。
一个方法如果可以接受一个基类对象 b 的话: method (b),那么它必然可以接受一个子类对象 d ,也可以有method (d)。
反过来的代换不成立,即如果一个软件实体使用的是一个子类的话,那么它不一定适用于基类。
里氏代换原则是继承复用的基石,是对“开—闭”原则的补充,是对实现抽象化的具体步骤的规范,里氏代换原则是开—闭原则的必要条件。
一般而言,违反里氏代换原则的,也违背“开—闭”原则,反过来不一定成立。
只有当衍生类可以替换掉基类,软件单位的功能不会受到影响时,基类才能真正被复用。而衍生类也才能够在基类的基础上增加新的行为。
一个对象应当对其他对象有尽可能少的了解。
迪米特法则表述:
表述一:只与你直接的朋友们通信
表述二:不要跟“陌生人”说话
表述三:每一个软件单元对其他的单元都只有最少的知识,而且局限于那些与本单位密切相关的软件单位。
“直接”、“陌生”和“密切”——模糊化的概念,以便在不同的环境下可以有不同的解释。
狭义的迪米特法则:
如果两个类不必彼此直接通信,那么这两个类就不应当发生直接的相互调用。如果其中的一个类需要调用另一个类的某一个方法的话,可以通过第三者转发这个调用。
调用转发( Call Forwarding)
隐藏‘’陌生人”的存在,使得“某人”仅知道“朋友”,而不知道’‘陌生人”。认为所调用的这个方法是“朋友”的方法。
朋友条件:
当前对象本身
以参量形式传入当前对象方法中的对象
当前对象的实例变量直接引用的对象
当前对象的实例变量如果是一个聚集,聚集中的元素都是朋友
“接口”:一个类所提供的所有方法的特征集合的逻辑概念。
接口的划分直接影响类型的划分。
接口→剧本中的角色;接口实现→相当于演员。
接口隔离原则=角色隔离原则:每一个角色都应当由一个特定的接口代表。
表述一:一个类对另一个类的依赖性应当建立在最小的接口上。
如果系统涉及到多个角色的话,一个接口在它的整个生命周期中都只应当代表一个角色,实现一个接口对象。几个不同的角色不应当都交给同一个接口,而应当交给不同的接口。
表述二:使用多个专门的接口比使用单个的总接口要好。
表述三:为同一个角色不同的客户端,提供宽、窄不同的接口,叫做定制服务。
接口污染:过于臃肿的接口
一个没有经验的设计师往往想节省接口的数目,将差不多的接口合并,认为是代码优化,这是错误的。将没有关系的接口合并在一起,是对角色和接口的污染。
和模块化原则的关系:模块的功能内聚。
SRP:将一个具备强大功能的类分割成多个小类,每个小类仅提供某个单一的功能。
ISP:如果设计一个类提供给多个客户调用,则类中某些方法对某些客户是无用的,这会造成客户对方法的错误调用。通过划分接口让每个客户只使用其特定的接口而不是一个类实例,可以避免此类错误。
多用组合少用继承原则
为什么说多用组合,少用继承?
对类的功能的扩展,要多用组合,少用继承。
对于类的扩展,在面向对象的编程过程中,我们首先想到的是类的继承,由子类继承父类,从而完成了对子类功能的扩展。但是,面向对象的原则告诉我们,对类的功能的扩展要多用组合,而少用继承。其中的原因有以下几点:
第一、子类对父类的继承是全部的公有和受保护的继承,这使得子类可能继承了对子类无用甚至有害的父类的方法。换句话说,子类只希望继承父类的一部分方法,怎么办?
第二、实际的对象千变万化,如果每一类的对象都有他们自己的类,尽管这些类都继承了他们的父类,但有些时候还是会造成类的无限膨胀。
第三、 继承的子类,实际上需要编译期确定下来,这满足不了需要在运行内才能确定对象的情况。而组合却可以比继承灵活得多,可以在运行期才决定某个对象。
要考试了,一些面向对象设计的原则小结一下:
单一职责原则[b](SRP,Single Responsibility Principle)[/b]
就一个类而言,应该仅有一个引起它变化的原因;
例子:手机的过多功能违反了单一职责原则;
如果一个类承担的职责过多,就等于把这些职责耦合在一起,一个职责的变化可能会削弱或者抑制这个类完成其他职责的能力。这种耦合会导致脆弱的设计,当变化发生时,设计会遭受到意想不到的破坏;
软件设计真正要做的许多内容,就是发现职责并把那些职责相互分离。要去判断是否应该分离出类来,也不难,那就是如果你能够想到多于一个的动机去改变一个类,那么这个类就具有多于一个的职责,就应该考虑类的职责分离;
【例子:调制解调器类】
完成两个职责:建立/中断连接、发送/接收数据。
问题:如果应用程序会改变数据连接方式,而必须修改Modem类,使任何依赖Modem类的元素都要重新编译,包括不用到连接功能的应用。
解决办法:重构Modem类,建立两个接口,一个专门负责连接、另一个专门负责数据发送。依赖Modem类的元素根据职责的不同分别依赖不同的接口。
最后由ModemImplementation类实现这两个接口。
使用SRP注意点:
1、 SRP实质上规定了类的粒度划分 ;
2、如果职责没有变化的征兆,则不应用SRP;
3、如果程序总是要求两个职责同时变化,也不必分离职责;
5、职责分离后,可以用Facade或Proxy模式对外提供一个聚合的操作接口 ;
优点/作用:
有助于理清思路。
使我们的编码、测试和维护变得简单。
有助于代码的重用。
有助于系统的扩展。
过度的分离会导致不必要的复杂性;
开放-封闭原则
软件实体(类、模块、函数等)应该可以扩展,但是不可修改。该原则有两个特征,一个是"对于扩展是开放的(open for extension)",另一个是"对于更改是封闭的(closed for modification)";
变化不太好预测,但在变化发生时,就要立即创建抽象来隔离以后发生的同类变化;
开放-封闭原则的精神——面对需求,对程序的改动是通过增加新代码(扩展原有的类)来进行的,而不是更改现有的代码;
[开闭原则应用例子]:
将条件转移语句改写成为多态性的代码重构
条件转移语句——某种可变性。
将条件转移的商业逻辑封装到不同的具体子类中。
用多态性代替条件转移语句。
一个引申的问题:多态性的滥用——”多态性污染“
任何语言都提供条件转移功能。
使用多态性代替条件转移意味着创建大量的类。
一个类如果有M个方法,每个方法都有一个N段的条件转移语句。如果都用多态性代替,会造成M*N个不同的类。
无原则改写条件转移语句为多态性是一种没有意义的浪费。
何时使用重构做法:
条件转移语句确实封装了商务逻辑的可变性
条件转移语句随着时间在变化——需要改变
需要扩展——有意义的可变性
拒绝不成熟的抽象和抽象本身一样重要!
依赖倒转原则
表述一:应该让框架调用你的代码,而不要由你主动调用框架本身。
表述二:抽象不应依赖于细节,细节应当依赖于抽象。
表述三:要针对接口编程,不要针对实现编程。
抽象可以理解成电脑主板上的一个个插槽,细节则是CPU、内存、硬盘等部件的工作原理;
高层模块(电脑部件)不应该依赖低层模块(主板),两个都应该依赖抽象(主板插槽);
依赖倒转原则很经典,可以说是面向对象设计的标志!
依赖倒转原则的实现是建立在里氏代换原则之上的。
优点:
不必知道对象的类型,只要求对象遵守接口。不必知道对象的类,只需知道定义接口的抽象类。——降低了子系统间的实现依赖。
依赖倒转关系强调了系统内的实体之间关系的灵话性。从具体耦合变为抽象耦合。
三种耦合关系:
零耦合:两个类没有耦合关系。
抽象耦合:耦合发生在一个具体类和一个抽象类之间,两个必须发生关系的类之间存有最大的灵活性。
具体耦合;耦合发生在两个具体的(可实例化的)类之间,一个类直接引用另一个具体类。
运用依赖倒转原则时可能碰到的问题:
创建对象要避免对具体类的直接引用,有时需要创建抽象类。
会导致大量的类。维护难度大。
倒转原则假定所有的具体类都是会变化的,不排除有一些具体类相当稳定。
里氏代换原则
一个软件实体如果使用的是一个父类的话,那么一定适用于其子类,而且它察觉不出父类对象和子类对象的区别。
也就是说,在软件里面,把父类都替换成它的子类,程序的行为没有变化,简单地说,子类型必须能够替换掉它们的父类型;
正因为有了这个原则,使得继承利用成为了可能,只有当子类可以替换掉父类,软件单位的功能不受到影响时,父类才能真正被复用,而子类也能够在父类的基础上增加新的行为;
由于子类型的可替换性才使得使用父类类型的模块在无需修改的情况下就可以扩展。
[例]:假设有两个类: Base 类,Derived类,且 Derived 类是 Base类的子类。
一个方法如果可以接受一个基类对象 b 的话: method (b),那么它必然可以接受一个子类对象 d ,也可以有method (d)。
反过来的代换不成立,即如果一个软件实体使用的是一个子类的话,那么它不一定适用于基类。
里氏代换原则是继承复用的基石,是对“开—闭”原则的补充,是对实现抽象化的具体步骤的规范,里氏代换原则是开—闭原则的必要条件。
一般而言,违反里氏代换原则的,也违背“开—闭”原则,反过来不一定成立。
只有当衍生类可以替换掉基类,软件单位的功能不会受到影响时,基类才能真正被复用。而衍生类也才能够在基类的基础上增加新的行为。
迪米特法则
又称最少知识原则,最初是用来作为面向对象的系统设计风格的一种法则,一个对象应当对其他对象有尽可能少的了解。
迪米特法则表述:
表述一:只与你直接的朋友们通信
表述二:不要跟“陌生人”说话
表述三:每一个软件单元对其他的单元都只有最少的知识,而且局限于那些与本单位密切相关的软件单位。
“直接”、“陌生”和“密切”——模糊化的概念,以便在不同的环境下可以有不同的解释。
狭义的迪米特法则:
如果两个类不必彼此直接通信,那么这两个类就不应当发生直接的相互调用。如果其中的一个类需要调用另一个类的某一个方法的话,可以通过第三者转发这个调用。
调用转发( Call Forwarding)
隐藏‘’陌生人”的存在,使得“某人”仅知道“朋友”,而不知道’‘陌生人”。认为所调用的这个方法是“朋友”的方法。
朋友条件:
当前对象本身
以参量形式传入当前对象方法中的对象
当前对象的实例变量直接引用的对象
当前对象的实例变量如果是一个聚集,聚集中的元素都是朋友
接口隔离原则(ISP,Interfacesegregation Principle )
面向对象的设计的一个重要内容:准确而恰当地划分角色以及角色所对应的接口。“接口”:一个类所提供的所有方法的特征集合的逻辑概念。
接口的划分直接影响类型的划分。
接口→剧本中的角色;接口实现→相当于演员。
接口隔离原则=角色隔离原则:每一个角色都应当由一个特定的接口代表。
表述一:一个类对另一个类的依赖性应当建立在最小的接口上。
如果系统涉及到多个角色的话,一个接口在它的整个生命周期中都只应当代表一个角色,实现一个接口对象。几个不同的角色不应当都交给同一个接口,而应当交给不同的接口。
表述二:使用多个专门的接口比使用单个的总接口要好。
表述三:为同一个角色不同的客户端,提供宽、窄不同的接口,叫做定制服务。
接口污染:过于臃肿的接口
一个没有经验的设计师往往想节省接口的数目,将差不多的接口合并,认为是代码优化,这是错误的。将没有关系的接口合并在一起,是对角色和接口的污染。
和模块化原则的关系:模块的功能内聚。
SRP:将一个具备强大功能的类分割成多个小类,每个小类仅提供某个单一的功能。
ISP:如果设计一个类提供给多个客户调用,则类中某些方法对某些客户是无用的,这会造成客户对方法的错误调用。通过划分接口让每个客户只使用其特定的接口而不是一个类实例,可以避免此类错误。
多用组合少用继承原则
为什么说多用组合,少用继承?
对类的功能的扩展,要多用组合,少用继承。
对于类的扩展,在面向对象的编程过程中,我们首先想到的是类的继承,由子类继承父类,从而完成了对子类功能的扩展。但是,面向对象的原则告诉我们,对类的功能的扩展要多用组合,而少用继承。其中的原因有以下几点:
第一、子类对父类的继承是全部的公有和受保护的继承,这使得子类可能继承了对子类无用甚至有害的父类的方法。换句话说,子类只希望继承父类的一部分方法,怎么办?
第二、实际的对象千变万化,如果每一类的对象都有他们自己的类,尽管这些类都继承了他们的父类,但有些时候还是会造成类的无限膨胀。
第三、 继承的子类,实际上需要编译期确定下来,这满足不了需要在运行内才能确定对象的情况。而组合却可以比继承灵活得多,可以在运行期才决定某个对象。
相关文章推荐
- 01、设计模式之简介及面向对象的几个基本原则
- 设计模式的几个基本原则
- 面向对象设计模式的几个基本原则及其核心思想简介
- 面向对象设计的六大基本原则(设计模式6大原则)
- Java设计模式——面向对象的几个基本原则
- 设计模式中的几个设计原则总结
- 设计模式之抽象工厂(从模式讲到设计模式再到面向对象设计模式)
- 面向对象设计模式学习(一):面向对象设计模式与原则
- 设计模式基本原则
- 整理几个关于设计模式、架构的博客专栏和文章
- 设计模式01-简单工厂模式
- Asp.net开发过程中的几个模式(设计,源,cs or vb)切换
- 设计模式学习笔记(一)——面向对象设计模式与原则
- C#面向对象设计模式纵横谈(1):面向对象设计模式与原则
- 大家一起学面向对象设计模式系列Chapter 02 软件设计的基本原则
- GoF 23个经典的设计模式01--创建模式之抽象工厂(未完代续)
- 整理几个关于设计模式、架构的博客专栏和文章
- 学习设计模式 几个不错的系列
- C#设计模式(学习笔记[01])
- Command设计模式应用时的几个问题