设计模式的精髓

设计软件的几个原则,这个也是设计模式的精髓所在:

 

一、 开闭原则(OCP)

开闭原则(Open-Closed Principle):一个软件实体应当对扩展开放,对修改关闭。

客户的需求是不稳定的,通过扩展已有的软件系统而不是通过修改软件系统来满足客户的需求,这样的软件系统就满足开-闭原则,即软件系统要有一定的灵活性和适应性。

已有的模块,特别是抽象层的模块不能修改,保证软件系统的稳定性和延续性。解决问题的关键是抽象化,把它与具体实现分离开来。接口(interface),抽象类的应用对可变性封装:将可变性封装到一个对象里。优点是通过扩展已有软件系统,可以提供新的行为,以满足对软件的新的需求,使变化中的软件有一定的适应性和灵活性。已有软件模块,特别是最重要的抽象层模块不能再修改,这使变化中的软件系统有一定的稳定性和延续性。

 

实现开闭原则的关键:

抽象化是解决问题的关键,在面向对象的编程语言里,可以给系统定义出一套相对较为固定的抽象设计,此设计允许无穷无尽的行为在实现层被实现。在语言里,可以给出一个或多个抽象类或者接口,规定出所有的具体类必须提供的方法的特征作为系统设计的抽象层。这个抽象层预见了所有的可扩展性,因此,在任何扩展情况下都不会改变。这就使得系统的抽象不需要修改,从而满足了开闭原则的第二条,对修改关闭。
同时,由于从抽象层导出一个或多个新的具体类可以改变系统的行为,因此系统的设计对扩展是开放的,这就满足了开闭原则的第一条。

对可变性的封装原则:

这是对开闭原则的另外一种描述,它讲的是找到一个系统的可变因素,将之封装起来。该原则意味着两点:
 一种可变性不应当散落在代码的很多角落,而应当封装到一个对象里面。继承应当被看做是封装变化的方法,而不应该被认为是一种从一般对象生成特殊对象的方法。
 一种可变性不应当与另外一种可变性混合在一起。这意味着一般的继承层次不会超过两层。

关键知识点:

 开闭原则的概念,软件实体对扩展开发,对修改关闭;

 实现开闭原则的关键,利用接口或抽象类抽象出系统的抽象层,抽象层不变,利用实现层进行扩展;

 对可变性的封装,将可变的元素封装起来,防止改变扩散到整个应用;

 注意控制封装的粒度,不要将两种可变性封装到一起;

 继承是用来封装可变性的,一般的继承层次不要超过两层;

 策略模式是对开闭原则的很好诠释,其他还有工厂模式、建造模式、桥接模式、门面模式、调停者模式、访问者模式和迭代子模式等;

 对“将条件转移语句改写成多态性”的重构行为应当遵循开闭原则,防止多态性污染;

 java下的单方法接口通常用来实现函数指针或者委托的功能;

 任何一棵继承树都要以抽象类为根,具体类不是用来继承的,更不要从工具类继承;

 抽象类要拥有尽可能多的共同代码,同时拥有尽可能少的数据。

 Coad条件全部满足时,才应当考虑使用继承:派生类是基类的一个特殊种类,而不是其的一个角色,也就是说要区分“Has-a”和“Is-a”;永远不会出现需要将派生类换成另外一个类的派生类的情况;派生类具有扩展基类的责任而不是具有置换或注销基类的责任;只有在分类学角度上有意义时,才可以使用继承。

 

 

二、 里氏代换原则(LSP)

里氏代换原则(Liskov Substitution Principle):子类型必须能够替换它们的基类型。反过来的代换不成立。

当两个具体类关系违反里氏代换原则时,一种办法是抽象出一个基类,作为这两个类的父类,一种是应用组合聚合关系建立关系。不要为了使用某些类的方法(功能)而滥用继承。

 

关键知识点:
 里氏替换原则的概念,基类可以出现的地方,派生类同样可以出现;
 里氏替换原则的反命题不成立;
 对于AB两个类,BA派生,如果这种继承违反里氏替换原则,可以采用如下方法进行重构:将AB的共同行为抽象出来,建立一个抽象类CAB都是C的派生类

 

三、 依赖倒置原则(DIP)

依赖倒置原则(Dependence Inversion Principle):具体要依赖于抽象,抽象不要依赖于具体。

简单的说,依赖倒置原则要求客户端依赖于抽象耦合。原则表述:抽象不应当依赖于细节;细节应当依赖于抽象;要针对接口编程,不针对实现编程。传递参数,或者在组合聚合关系中,尽量引用层次高的类。主要是在构造对象时可以动态的创建各种具体对象,当然如果一些具体类比较稳定,就不必再弄一个抽象类做它的父类,这样有画蛇添足的感觉。

 

 三种耦合关系

 零耦合关系,如果两个类没有耦合关系,就称之为零耦合;

 具体耦合,具体耦合发生在两个具体的类之间,经由一个类对另外一个具体类的直接引用造成的。

 抽象耦合关系,抽象耦合关系发生在一个具体类和一个抽象类之间,使用两个必须发生关系的类之间存在有最大的灵活性。

 依赖倒转原则的另外一种表述是:

要针对接口编程,不要针对实现编程(Program to an interface, not an implementation)[GOF95]。同样,在处理类之间的耦合关系时,尽量使用抽象耦合的形式。

 里氏替换原则是依赖倒转原则的基础。

 

 工厂模式、模板模式、迭代子模式都是对依赖倒转原则的体现。

java对抽象类型的支持

     在设计中,常用的抽象方式为:先抽象出逻辑上固定的东西,定义出接口,再定义一个抽象类实现该接口,作为该接口默认实现的抽象类应该实现大部分公共的代码,具体的类通常由该抽象类派生。

 

 

四、 接口隔离原则(ISP)

接口隔离原则(Interface Segregation Principle):使用多个专门的接口比使用单一的总接口总要好。

换而言之,从一个客户类的角度来讲:一个类对另外一个类的依赖性应当是建立在最小接口上的。过于臃肿的接口是对接口的污染。不应该强迫客户依赖于它们不用的方法。定制服务的例子,每一个接口应该是一种角色,不多不少,不干不该干的事,该干的事都要干。

 

     接口隔离原则讲的是为同一个角色提供宽、窄不同的接口,以应对不同客户端的需求,下例以set为例讲解:

TreeSet是一种使用树状数据结构的可排序的Set容器,它既实现了Set接口(通过继承AbstractSet),又实现了SortedSet接口。这里并没有提供一个总的既有排序功能又有Set功能的总接口,而是针对不同的需求,将两种角色分别定义成两种接口,这样的设计,是符合接口隔离原则。

 

 接口污染

将不同角色的接口合并为一个臃肿的接口就是对接口的污染。这种做法同时违反了可变性封装原则,它将不同的可变性封装到了同一个软件实体中。

 

 对接口隔离原则的具体应用可以参考备忘录模式和迭代子模式。

 

 

五、 合成/聚合复用原则(CARP)

合成/聚合复用原则(Composite/Aggregate Reuse Principle或CARP):就是在一个新的对象里面使用一些已有的对象,使之成为新对象的一部分;新对象通过向这些对象的委派达到复用已有功能的目的。

简而言之,要尽量使用合成/聚合,尽量不要使用继承。区分Has a和Is a的问题。

合成:一荣俱荣,一损俱损,整体和部分的生命周期是一样的。是比聚合关系强的关系。它要求普通的聚合关系中代表整体的对象负责代表部分对象的生命周期,合成关系是不能共享的。代表整体的对象需要负责保持部分对象和存活,在一些情况下将负责代表部分的对象湮灭掉。代表整体的对象可以将代表部分的对象传递给另一个对象,由后者负责此对象的生命周期。换言之,代表部分的对象在每一个时刻只能与一个对象发生合成关系,由后者排他地负责生命周期。

聚合:部分可以是整体的一部分,也可以脱离整体而存在。例如,汽车类与引擎类、轮胎类,以及其它的零件类之间的关系便整体和个体的关系。聚合关系也是通过实例变量实现的。但是关联关系所涉及的两个类是处在同一层次上的,而在聚合关系中,两个类是处在不平等层次上的,一个代表整体,另一个代表部分。

 

六、 迪米特法则(LoD)

迪米特法则(Law of Demeter或简写LoD)又叫最少知识原则(Least Knowledge Principle或简写为LKP):一个对象应当对其它对象有尽可能少的了解。不要和陌生人说话。

 

ξ 1 迪米特法则的各种表述

 只与你直接的朋友们通信;

 不要跟“陌生人”说话;

 每一个软件单位对其他的单位都只有最少的知识,而且局限于那些与本单位密切相关的软件单位。

 

ξ 2 狭义的迪米特法则

 如果两个类不必彼此直接通信,那么这两个类就不应当发生直接的相互作用。如果其中的一个类需要调用另外一个类的某一个方法,可以通过第三者转发这个调用。

 

参考下例,SomeoneFriendStranger三个类。

 

 

Someone类有一个方法接受一个Friend类型的变量:

 

 

[java]  view plain copy
  1. public class Someone{  
  2.     public void operation1( Friend friend ){  
  3.         Stranger stranger = friend.provide() ;  
  4.         stranger.operation3() ;  
  5.     }  
  6. }  
[java]  view plain copy
  1. public class Friend {  
  2.     private Stranger stranger = new Stranger() ;  
  3.     public void operation2()...{}  
  4.     public Stranger provide() {  
  5.         return stranger ;  
  6.     }  
  7. }  

所以SomeoneFriend是朋友类(直接通讯的类)。

同理,Friend类持有一个Stranger类的私有对象,他们是朋友类:

 

 

在这里,Someone类和Stranger类不是朋友类,但Someone类却通过Friend类知道了Stranger类的存在,这显然违反迪米特法则。

现在,我们对SomeoneFriend类进行重构。首先在Friend类里添加一个方法,封装对Stranger类的操作:

[java]  view plain copy
  1. public class Friend {  
  2.     private Stranger stranger = new Stranger() ;  
  3.     public void operation2()...{}  
  4.     public Stranger provide() {  
  5.         return stranger ;  
  6.     }  
  7.   
  8.     public void forward() {  
  9.         stranger.operation3() ;  
  10.     }  
  11. }  

然后,我们重构Someoneoperation1方法,让其调用新提供的forward方法:

[java]  view plain copy
  1. public class Someone {  
  2.     public void operation1( Friend friend ) {  
  3.         friend.forward() ;  
  4.     }  
  5. }  

现在SomeoneStranger的依赖完全通过Friend隔离,这样的结构已经符合狭义迪米特法则了。

仔细观察上述结构,会发现狭义迪米特法则一个明显的缺点:会在系统里造出大量的小方法,散落在系统的各个角落。这些方法仅仅是传递间接的调用,因此与系统的商务逻辑无关,当设计师试图从一张类图看出总体的框架时,这些小的方法会造成迷惑和困扰。遵循迪米特法则会使一个系统的局部设计简化,因为每一个局部都不会和远距离的对象有直接关联。但是,这也会造成系统的不同模块之间的通信效率降低,也会使系统的不同模块之间不容易协调。

结合依赖倒转原则,我们对代码进行如下重构来解决这个问题,首先添加一个抽象的Stranger类,使Someone依赖于抽象的“Stranger”角色,而不是具体实现:

 

 

[java]  view plain copy
  1. public abstract class AbstractStranger {  
  2.     abstract void operation3() ;  
  3. }  
  

 

然后,让Stranger从该类继承:

[java]  view plain copy
  1. public class Stranger extends AbstractStranger {  
  2.     public void operation3() ...{}  
  3. }  

 

 

随后,我们重构Someone使其依赖抽象的Stranger角色:

[java]  view plain copy
  1. public class Someone {  
  2.     public void operation1( Friend friend ) {  
  3.         AbstractStranger stranger = friend.provide() ;  
  4.         stranger.operation3() ;  
  5.     }  
  6. }  

 

 

 

最后,我们重构Friendprovide方法,使其返回抽象角色:

 

现在,AbstractStranger成为Someone的朋友类,而Friend类可以随时替换掉AbstractStranger的实现类,Someone不再需要了解Stranger的内部实现细节。下图是重构后的UML类图:

 

 

广义迪米特法则
 在将迪米特法则运用到系统的设计中时,应注意的几点:
 在类的划分上,应该创建有弱耦合的类;
 在类的结构设计上,每一个类都应当尽量降低成员的访问权限;
 在类的设计上,只要有可能,一个类应当设计成不变类;
 在对其他类的引用上,一个对象对其它对象的引用应当降到最低;
 尽量降低类的访问权限;
 谨慎使用序列化功能;
 不要暴露类成员,而应该提供相应的访问器(属性)

 

 

 

七、抽象类原则

抽象类不会有实例,一般作为父类为子类继承,一般包含这个系的共同属性和方法。

注意:好的继承关系中,只有叶节点是具体类,其他节点应该都是抽象类,也就是说具体类是不被继承的。将尽可能多的共同代码放到抽象类中。

 

 

[java]  view plain copy
  1. public class Friend {  
  2.     private Stranger stranger = new Stranger() ;  
  3.     public void operation2(){}  
  4.     public AbstractStranger provide() {  
  5.         return stranger ;  
  6.     }  
  7. }  
原文地址:http://blog.csdn.net/lifuxiangcaohui/article/category/774459
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值