面对对象设计原则
上一篇 / 下一篇 2010-02-07 23:14:37 / 个人分类:设计
- 文件版本: V1.0
- 开发商: 来源网络
- 文件来源: 网络
- 界面语言: 简体中文
- 授权方式: 免费
- 运行平台: Win9X/Win2000/WinXP
第一 : 开闭原则
开闭原则:一个系统要对扩展开放,而对修改关闭。这里的所指的意思就是在不修改以前已经写好的代码或者尽量不改的情况下来扩展系统的功能。
如何做到开闭原则:
第一 : 我们就要对系统进行分析,抽象出最本质的东西。抽象的越深,系统的可扩展性就越好。在当今主流软件开发平台(J2EE&.NET)中都提供了具体的语法来支持抽象。
第二 :对可变性的封装。要想做到做尽量小修改来扩展系统,我们就要发现哪些东西是可变的,那些东西是不会变的,或者也可以说:我们可以让什么东西发生变化,而哪些东西不变化。我们做的就是要对这些变化的东西进行封装,不要把它们分散到代码中,并且我们还尽量不要把几种不同的可变性混合在一起。
第二 :里氏代换原则
1、里氏代换原则定义
若对于每一个类型S的对象o1,都存在一个类型T的对象o2,使得在所有针对T编写的程序P中,用o1替换o2后,程序P的行为功能不变,则S是T的子类型。
What is wanted here is something like the following substitution property: If for each object o1 of type S there is an object o2 of type T such that for all programs P defined in terms of T, the behavior. of P is unchanged when o1 is substituted for o2 then S is a subtype of T.
即,一个软件实体如果使用的是一个基类的话,那么一定适用于其子类。而且它觉察不出基类对象和子类对象的区别。也就是说,在软件里面,把基类都替换成它的子类,程序的行为没有变化。反过来的代换不成立,如果一个软件实体使用的是一个子类的话,那么它不一定适用于基类。
2、里氏代换原则与“开-闭”原则的关系
实现“开-闭”原则的关键步骤是抽象化。基类与子类之间的继承关系就是抽象化的体现。因此里氏代换原则是对实现抽象化的具体步骤的规范。违反里氏代换原则意味着违反了“开-闭”原则,反之未必。
3、里氏代换原则的四层含义
1)子类必须完全实现父类的方法。在类中调用其他类是务必要使用父类或接口,如果不能使用父类或接口,则说明类的设计已经违背了LSP原则。
2)子类可以有自己的个性。子类当然可以有自己的行为和外观了,也就是方法和属性
3)覆盖或实现父类的方法时输入参数可以被放大。即子类可以重载父类的方法,但输入参数应比父类方法中的大,这样在子类代替父类的时候,调用的仍然是父类的方法。即以子类中方法的前置条件必须与超类中被覆盖的方法的前置条件相同或者更宽松。
4)覆盖或实现父类的方法时输出结果可以被缩小。
4、里氏代换原则在设计模式中的体现
策略模式(Strategy)
如果有一组算法,那么就将算法封装起来,使得它们可以互换。客户端依赖于基类类型,而变量的真实类型则是具体策略类。这是具体策略焦色可以“即插即用”的关键。
合成模式(Composite)
合成模式通过使用树结构描述整体与部分的关系,从而可以将单纯元素与符合元素同等看待。由于单纯元素和符合元素都是抽象元素角色的子类,因此两者都可以替代抽象元素出现在任何地方。里氏代换原则是合成模式能够成立的基础。
代理模式(Proxy)
代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。代理模式能够成立的关键,就在于代理模式与真实主题模式都是抽象主题角色的子类。客户端只知道抽象主题,而代理主题可以替代抽象主题出现在任何需要的地方,而将真实主题隐藏在幕后。里氏代换原则是代理模式能够成立的基础。
5、总结
里氏代换原则是对开闭原则的扩展,它表明我们在创建基类的新的子类时,不应该改变基类的行为。也就是不要消弱基类的行为。
面向对象的设计关注的是对象的行为,它是使用“行为”来对对象进行分类的,只有行为一致的对象才能抽象出一个类来。我经常说类的继承关系就是一种“Is-A”关系,实际上指的是行为上的“Is-A”关系,可以把它描述为“Act-As”。
依赖倒置原则(Dependence Inversion Principle)
1、依赖倒置原则的定义
1)上层模块不应该依赖于底层模块,它们都应该依赖于抽象。
2)抽象不应该依赖于细节,细节应该依赖于抽象,要针对接口编程,不要针对实现编程。
Abstractions should not depend upon details,Details should depend upon abstractions.Program to an interface, not an implementation.
也就是说应当使用接口和抽象类进行变量类型声明、参数类型声明、方法返还类型说明,以及数据类型的转换等。而不要用具体类进行变量的类型声明、参数类型声明、方法返还类型说明,以及数据类型的转换等。要保证做到这一点,一个具体类应当只实现接口和抽象类中声明过的方法,而不要给出多余的方法。
基于这个原则,设计类结构的方式应该是从上层模块到底层模块遵循这样的结构:上层类--->抽象层--->底层类。 2、依赖倒置原则与开闭原则的关系
“开-闭”原则与依赖倒转原则是目标和手段的关系。如果说开闭原则是目标,依赖倒转原则是到达"开闭"原则的手段。如果要达到最好的"开闭"原则,就要尽量的遵守依赖倒转原则,依赖倒转原则是对"抽象化"的最好规范。里氏代换原则是依赖倒转原则的基础,依赖倒转原则是里氏代换原则的重要补充。
3、实例
下面是一个违反了依赖倒转原则的例子。我们有一个上层类Manager和底层类Worker。我们需要在程序中添加一个新模块,因为有新的特殊的工作者被雇用。为此,我们创建一个新的类SuperWorker。
假设Manager是一个包含非常复杂的逻辑的类,现在为了引入新的SuperWorker,我们需要修改它。让我们看一下这有哪些缺点:
(1)我们需要修改Manager类(记住,它是一个非常复杂的类,这需要一些时间和努力)。
(2)Manager类的一些现有功能可能会受到影响。
(3)需要重做单元测试。
所有的这些问题需要大量的时间去解决。但是如果程序的设计符合依赖倒转原则将会非常简单。意思是我们设计Manager类和一个IWorker接口以及一些实现了该接口的Worker类。当需要添加SuperWorker类时我们只需要让它实现IWorker接口。
- //Dependency Inversion Principle - Bad example
- class Worker {
- public void work() {
- // ....working
- }
- }
- class Manager {
- Worker m_worker;
- public void setWorker(Worker w) {
- m_worker=w;
- }
- public void manage() {
- m_worker.work();
- }
- }
- class SuperWorker {
- public void work() {
- //.... working much more
- }
- }
//Dependency Inversion Principle - Bad example class Worker { public void work() { // ....working } } class Manager { Worker m_worker; public void setWorker(Worker w) { m_worker=w; } public void manage() { m_worker.work(); } } class SuperWorker { public void work() { //.... working much more } }
下面是支持依赖倒转原则的代码。在这个新的设计中,我们增加了一个新的抽象层IWork接口。现在,上面的问题得到了解决:
不需要修改Manager类。
使对Manager类现有功能的影响最小化。
不需要对Manager类重新进行单元测试。
- //Dependency Inversion Principle - Good example
- interface IWorker {
- public void work();
- }
- class Worker implements IWorker{
- public void work() {
- // ....working
- }
- }
- class SuperWorker implements IWorker{
- public void work() {
- //.... working much more
- }
- }
- class Manager {
- IWorker m_worker;
- public void setWorker(IWorker w) {
- m_worker=w;
- }
- public void manage() {
- m_worker.work();
- }
- }
//Dependency Inversion Principle - Good example interface IWorker { public void work(); } class Worker implements IWorker{ public void work() { // ....working } } class SuperWorker implements IWorker{ public void work() { //.... working much more } } class Manager { IWorker m_worker; public void setWorker(IWorker w) { m_worker=w; } public void manage() { m_worker.work(); } }
4、总结
应用该原则意味着上层类不直接使用底层类,他们使用接口作为抽象层。这种情况下上层类中创建底层类的对象的代码不能直接使用new操作符。可以使用一些创建型设计模式,例如工厂方法,抽象工厂和原型模式。
模版设计模式是应用依赖倒转原则的一个例子。
当然,使用该模式需要额外的努力和更复杂的代码,不过可以带来更灵活的设计。不应该随意使用该原则,如果我们有一个类的功能很有可能在将来不会改变,那么我们就不需要使用该原则。
第四 :接口隔离原则
接口隔离原则(Interface Segregation Principle)
1、接口隔离原则的定义:
第一种定义: Clients should not be forced to depend upon interfaces that they don't use.客户端不应该依赖它不需用的接口。
第二种定义:The dependency of one class to another one should depend on the smallest possible interface。一个类对另外一个类的依赖性应当是建立在最小的接口上的。
换句话说,使用多个专门的接口比使用单一的总接口总要好,建立单一接口,不要建立臃肿庞大的接口。一个接口代表一个角色,不应当将不同的角色都交给一个接口。没有关系的接口合并在一起,形成一个臃肿的大接口,这是对角色和接口的污染。不应当将几个不同的角色都交给同一个接口,而应当交给不同的接口。
2、接口污染定义:
所谓接口污染就是为接口添加了不必要的职责。在接口中加一个新方法只是为了给实现类带来好处,以减少类的数目。持续这样做,接口就被不断污染,变胖。实际上,类的数目根本不是什么问题,接口污染会带来维护和重用方面的问题。最常见的问题是我们为了重用被污染的接口,被迫实现并维护不必要的方法。因此,我们必须分离客户程序,分离客户程序就是分离接口。
3、分离接口的实现方法:
分离接口的方式一般分为两种:
1)使用委托分离接口。(Separation through Delegation)
就把请求委托给别的接口的实现类来完成需要的职责,就是适配器模式(Adapter)。
2)使用多重继承分离接口。(Separation through Multiple Inheritance。)
该方法通过实现多个接口来完成需要的职责。
两种方式各有优缺点,通常我们应该先考虑后一个方案,如果涉及到类型转换时则选择前一个方案。
4、实例
假如有一个Door,有lock,unlock功能,另外,可以在Door上安装一个Alarm而使其具有报警功能。用户可以选择一般的Door,也可以选择具有报警功能的Door。
要遵循ISP设计原则,方案如下:
1、在IAlarm接口定义alarm方法,在IDoor接口定义lock,unlock方法。接口之间无继承关系。CommonDoor实现IDoor接口。
public interfaceIDoor {
publicvoidlock();
publicvoidunlock();
}
public interfaceIAlarm {
publicvoidalarm();
}
public classCommonDoor implementsIDoor {
publicvoidlock() {
System.out.println("CommonDoor is lock!");
}
publicvoidunlock() {
System.out.println("CommonDoor is unlock!");
}
}
AlarmDoor有2种实现方案:
1)同时实现IDoor和IAlarm接口。
public classAlarmDoor implementsIDoor, IAlarm {
publicvoidlock() {
System.out.println("AlarmDoor is lock!");
}
publicvoidunlock() {
System.out.println("AlarmDoor is unlock!");
}
publicvoidalarm() {
System.out.println("AlarmDoor is alarm!");
}
}
2)继承CommonDoor,并实现Alarm接口。该方案是继承方式的Adapter设计模式的实现。
此种方案更具有实用性。
public classAlarmDoor extendsCommonDoor implementsIAlarm {
publicvoidlock() {
super.lock();
}
publicvoidunlock() {
super.unlock();
}
publicvoidalarm() {
System.out.println("AlarmDoor is alarm!");
}
}
2、采用委让实现
public interfaceIDoor {
publicvoidlock();
publicvoidunlock();
}
public interfaceIAlarm {
public voidlock();
public voidunlock();
publicvoidalarm();
}
public classCommonDoor implementsIDoor {
publicvoidlock() {
System.out.println("CommonDoor is lock!");
}
publicvoidunlock() {
System.out.println("CommonDoor is unlock!");
}
}
采用委托的方式即采用对象适配器的方式
public classAlarmDoor implementsIAlarm {
privateCommonDoorcommdoor=newCommonDoor();
publicvoidlock() {
commdoor.lock();
}
publicvoidunlock() {
commdoor.unlock();
}
publicvoidalarm() {
System.out.println("AlarmDoor is alarm!");
}
}
5、小结
如果已经设计成了胖接口,可以使用适配器模式隔离它。像其他设计原则一样,接口隔离原则需要额外的时间和努力,并且会增加代码的复杂性,但是可以产生更灵活的设计。如果我们过度的使用它将会产生大量的包含单一方法的接口,所以需要根据经验并且识别出那些将来需要扩展的代码来使用它。
第五、迪米特原则
迪米特原则(Law of Demeter)
1、迪米特原则的定义
迪米特原则也叫最少知识原则(Least Knowledge Principle, LKP)简单的说,就是如果两个类不必彼此直接通信,那么这两个类就不应当发生直接的相互作用。如果一个类需要调用类另一个类的某个方法的话,应当通过第三个类来转发调用。迪米特法则可以简单说成:talk only to your immediate friends。
2、迪米特原则解释
一个软件实体应当尽可能少的与其他实体发生相互作用。每一个软件单位对其他的单位都只有最少的知识,而且局限于那些与本单位密切相关的软件单位。迪米特法则减少耦合的问题,类之间的耦合越弱,越有利于复用,一个处在弱耦合的类被修改,不会对有关系的类造成波及。也就是说,信息的隐藏促进了软件的复用。
3、迪米特原则含义
(1)、talk only to your immediate friends.只和直接朋友通信。在一个类中,出现在成员变量、方法的输入输出参数中的类被称为成员朋友类。
实例:张三李四是朋友关系,李四王五是朋友关系.张三和王五是陌生人关系,为了保证张三和王五不认识,而张三又能调用王五中的方法,通过一个李四作为中继,把张三和王五隔离开来。
- //抽像的,陌生人;
- public interface AbstractStranger {
- public abstract void operation3();
- }
- //某人;(张三)
- public class Someone {
- public void operation1(Friend friend){
- AbstractStranger stranger = friend.provide(); //里氏代换;
- stranger.operation3();
- }
- }
- //朋友;(李四)
- public class Friend {
- private AbstractStranger stranger = new Stranger();
- public void operation2(){
- &nbs
TAG: 面对对象
标题搜索
日历
|
|||||||||
日 | 一 | 二 | 三 | 四 | 五 | 六 | |||
1 | 2 | 3 | 4 | 5 | 6 | ||||
7 | 8 | 9 | 10 | 11 | 12 | 13 | |||
14 | 15 | 16 | 17 | 18 | 19 | 20 | |||
21 | 22 | 23 | 24 | 25 | 26 | 27 | |||
28 | 29 | 30 |
我的存档
数据统计
- 访问量: 9109
- 日志数: 18
- 文件数: 6
- 建立时间: 2009-10-24
- 更新时间: 2011-09-07