No Regrets.

设计模式的基本原则

Posted on By Marin



design philosophy


是什么

设计模式是一套被反复使用、多数人知晓的、经过分类的、代码设计的 经验的总结。使用设计模式是为了可重用代码,让代码更容易被他人理解,保证代码可靠性。


设计模式的基本原则

1、开闭原则(Open Close Principle)

开闭原则的意思是:对扩展开放(提供方),对修改封闭(调用方)。在程序需要进行扩展的时候,不能去修改或影响原有的代码,实现一个热插拔的效果。简言之,是为了使程序的扩展性更好,易于维护和升级。想要达到这样的效果,我们需要使用接口和抽象类。

2、里氏代换原则(Liskov Substitution Principle)

里氏代换原则是面向对象设计的基本原则之一。 里氏代换原则中说,任何基类可以出现的地方,子类一定可以出现。换句话说就是所有引用基类的地方必须能透明地使用其子类对象;里氏代换原则是继承复用的基石,只有当子类可以替换掉基类,且软件单位的功能不受到影响时,基类才能真正被复用,而且子类也能够在基类的基础上增加新的行为。里氏代换原则是对开闭原则的补充。实现开闭原则的关键步骤就是抽象化,而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。
基于里氏代换原则我们在程序中尽量使用基类类型来对对象进行定义,而在运行时再确定其子类类型,用子类对象来替换父类对象。
里氏替换原则告诉我们,继承实际上让两个类的耦合性增强了,在适当的情况下,可以通过聚合,组合和依赖来解决问题。
在实际编程中,我们常常会通过重写父类的方法完成新的功能,这样写起来虽然简单,但整个继承体系的复用性会比较差,特别是运行多态比较频繁的时候。这个时候通用的做法是让父类和子类继承一个更加通用的基类;

判断两个类之间符不符合里氏代换原则,是要看具体的实现方法和属性,而不能只看单一的方面来进行判断。比如在我们以长、宽等属性的角度来看正方形和长方形,我们会得出结论正方形是一个特殊的长方形,所以可以通过正方形继承长方形。但如果结果具体的实现方法来看,我们可能会得出结论:正方形不是长方形。具体例子的实现代码如下: 长方形类:

class Rectangle {
  double length;
  double width;
  public double getLength() { return length; } 
  public void setLength(double height) { this.length = length; }   
  public double getWidth() { return width; }
  public void setWidth(double width) { this.width = width; } 
}

正方形类:

class Square extends Rectangle {
  public void setWidth(double width) {
    super.setLength(width);
    super.setWidth(width);   
 }
  public void setLength(double length) { 
    super.setLength(length);
    super.setWidth(length);   
  } 
}

从目前定义的两个类来看,它们完全是符合里氏替换原则的,但当将两者应用于特定的测试方法中,你会发现两者的区别: 测试类:

class TestRectangle {
  public void resize(Rectangle objRect) {
    while(objRect.getWidth() <= objRect.getLength()  ) {
        objRect.setWidth(  objRect.getWidth () + 1 );
    }
  }
}

我们运行一下这段代码就会发现,假如我们把一个普通长方形作为参数传入resize方法,就会看到长方形宽度逐渐增长的效果,当宽度大于长度,代码就会停止,这种行为的结果符合我们的预期;假如我们再把一个正方形作为参数传入resize方法后,就会看到正方形的宽度和长度都在不断增长,代码会一直运行下去,直至系统产生溢出错误。所以,普通的长方形是适合这段代码的,正方形不适合。 我们得出结论:在resize方法中,Rectangle类型的参数是不能被Square类型的参数所代替,如果进行了替换就得不到预期结果。因此,Square类和Rectangle类之间的继承关系违反了里氏代换原则,它们之间的继承关系不成立,正方形不是长方形。

3、依赖倒转原则(Dependence Inversion Principle)

这个原则是开闭原则的基础,核心内容:针对接口编程,高层模块不应该依赖底层模块,二者都应该依赖抽象而不依赖于具体。
抽象不应该依赖细节,细节应该依赖抽象;
依赖倒转的中心思想是面向接口编程;
依赖倒转原则是基于这样的设计理念:相对于细节的多变性,抽象的东西要稳定地多。以抽象为基础搭建的架构比以细节为基础的架构要稳定的多。在java中,抽象指的是接口或抽象类,细节就是具体的实现类。
使用接口或抽象类的目的是制定好规范,而不涉及任何具体的操作,把展现细节的任务交给他们的实现类去实现;

依赖关系传递的三种方式:
1、接口传递;
2、构造方法传递;
3、setter方式传递;

依赖倒转原则的注意事项和细节:
1、低层模块尽量都要有抽象类或接口,或者两者都有,程序稳定性更好;
2、变量的声明类型尽量是抽象类或接口,这样我们的变量引用和实际对象间,就存在一个缓冲层,利于程序扩展和优化;
3、继承时遵循里氏替换原则;

依赖倒转原则的具体实例可以是我们的翻译软件,我们翻译软件可以设置翻译内容的语言以及翻译成什么语言,在这个过程中我们就需要把获取不同语言的翻译内容以及把内容翻译成不同类型的语言这两个方法抽象化,然后通过继承来实现具体类的功能,而在对获取内容以及翻译方法的使用的过程中就直接注入抽象的方法来实现对不同情况的处理。这种做法和里氏替换原则有相同之处,所以可以说依赖倒转原则是对里氏替换原则的补充。

4、接口隔离原则(Interface Segregation Principle)

客户端不应该依赖它不需要的接口,即一个类对另一个类的依赖应该建立在最小接口的基础上;

这个原则的意思有两个:1、客户端不应该依赖那些它不需要的接口。2、使用多个隔离的接口,比使用单个庞大的接口要好。其目的在于降低耦合度。由此可见,其实设计模式就是从大型软件架构出发,便于升级和维护软件的设计思想。它强调低依赖、低耦合。

5、单一职责原则(Single Responsibility Principle)

类的职责要单一,不能将太多的职责放在一个类中。

可能有的人会觉得单一职责原则和前面的接口隔离原则很相似,其实不然。其一,单一职责原则原注重的是职责;而接口隔离原则注重对接口依赖的隔离。其二,单一职责原则主要约束的是类,其次才是接口和方法,它针对的是程序中的实现和细节;而接口隔离原则主要约束接口,主要针对抽象,针对程序整体框架的构建。

单一职责原则注意事项和细节:
1、降低类的复杂度,一个类只负责一项职责;
2、提高类的可读性、可维护性;
3、降低变更引起的风险;
4、通常情况下,我们应当遵守单一职责原则,只有逻辑足够简单,才可以在代码级违反单一职责原则,只有类中方法数量足够少,可以在方法级别保持单一职责原则;

单一职责原则的职责大致可以分为两类:数据职责和行为职责。数据职责通过属性确定,行为职责通过方法确定。

6、最少知道原则(Demeter Principle)

最少知道原则也叫迪米特法则,就是说:一个实体应当尽量少的与其他实体之间发生相互作用,使得系统功能模块相对独立。

一个对象应该对其他对象保持最少的了解。类与类之间的关系越密切,耦合度越大,当一个类发生改变时,对另一个类的影响也越大。如果两个类不必彼此直接通信,那么这两个类就不应当发生直接的相互作用。如果其中一个类需要调用另一个类的某一个方法的话,可以通过第三者转发这个调用。所以在类的设计上,每一个类都应当尽量降低成员的访问权限。

迪米特法则还有一个更简单的定义:只与直接朋友通信;
直接朋友:每个对象都会与其他对象有耦合关系,只要两个对象之间有耦合关系,我们就说这两个对象之间是朋友关系。耦合的方式很多,有依赖、关联、组合、聚合等。其中,我们称出现成员变量,方法参数,方法返回值中的类为直接的朋友,而出现在局部变量的类不是直接的朋友。也就是说,陌生的类最好不要以局部变量的形式出现在类的内部。

狭义的迪米特法则:在狭义的迪米特法则中,如果两个类之间不必彼此直接通信,那么这两个类就不应当发生直接的相互作用,如果其中的一个类需要调用另一个类的某一个方法的话,可以通过第三者转发这个调用。这样子可以降低类之间的耦合,但是会在系统中增加大量的小方法并散落在系统的各个角落,它可以使一个系统的局部设计简化,因为每一个局部都不会和远距离的对象有直接的关联,但是也会造成系统的不同模块之间的通信效率降低,使得系统的不同模块之间不容易协调。 广义的迪米特法则:指对对象之间的信息流量、流向以及信息的影响的控制,主要是对信息隐藏的控制。信息的隐藏可以使各个子系统之间脱耦,从而允许它们独立地被开发、优化、使用和修改,同时可以促进软件的复用,由于每一个模块都不依赖于其他模块而存在,因此每一个模块都可以独立地在其他的地方使用。一个系统的规模越大,信息的隐藏就越重要,而信息隐藏的重要性也就越明显。 迪米特法则的主要用途:在于控制信息的过载。 •在类的划分上,应当尽量创建松耦合的类,类之间的耦合度越低,就越有利于复用,一个处在松耦合中的类一旦被修改,不会对关联的类造成太大波及; •在类的结构设计上,每一个类都应当尽量降低其成员变量和成员函数的访问权限; •在类的设计上,只要有可能,一个类型应当设计成不变类; •在对其他类的引用上,一个对象对其他对象的引用应当降到最低。 10.34.0.201 7、合成复用原则(Composite Reuse Principle)

合成复用原则就是在一个新的对象里通过关联关系(组合关系、聚合关系)来使用一些已有的对象,使之成为新对象的一部分;新对象通过委派调用已有对象的方法达到复用功能的目的。简而言之,尽量多使用 组合/聚合 的方式,尽量少使用甚至不使用继承关系。

复用的两种方法:1、继承复用(白盒复用);2、组合/聚合复用(黑盒复用)。 继承复用:实现简单,易于扩展。破坏系统的封装性;从基类继承而来的实现是静态的,不可能在运行时发生改变,没有足够的灵活性;只能在有限的环境中使用。 组合/聚合复用:耦合度相对较低,选择性地调用成员对象的操作;可以在运行时动态进行。

设计模式原则的核心思想

1、找出应用中可能需要变化的地方,把它们独立出来,不要和那些不需要变化的代码混在一起;
2、针对接口编程,而不是针对实现编程;
3、为了交互对象之间的松耦合设计而努力;



有Marin的地方就有你的收获