软件设计原则可以说是无数前辈在踩过无数坑之后总结出来的提醒后人遵循的一些基本思想、规范、模式。遵循这些原则,有利于我们做出良好的设计,比如达到高内聚低耦合、模块划分清晰、源码可读性可维护性良好的效果。下面整理了最广为人知的十一大原则。

1、DRY(Don't Repeat Yourself)原则

DRY简而言之,就是不要写重复的代码。原则本身很简单,但是,对于OOAD(面向对象的分析和设计)来说,有着非常重大的意义。

DRY利用的方法就是抽象:把共同的事物抽象出来,把代码抽取到一个地方去。这样就可以避免写重复的代码。

 

2、合成/聚合原则(Composite/Aggregate Reuse Principle,CARP) AKA 合成复用原则

及尽量使用合成/聚合,尽量不要使用类继承。换句话说,就是能用合成/聚合的地方,绝不用继承。 

为什么要尽量使用合成/聚合而不使用类继承?

1. 对象的继承关系在编译时就定义好了,所以无法在运行时改变从父类继承的子类的实现

2. 子类的实现和它的父类有非常紧密的依赖关系,以至于父类实现中的任何变化必然会导致子类发生变化

3. 当你复用子类的时候,如果继承下来的实现不适合解决新的问题,则父类必须重写或者被其它更适合的类所替换,这种依赖关系限制了灵活性,并最终限制了复用性。

 

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

每一个类/接口应该专注于做一件事情。 

why?可以降低类的复杂度,一个类只负责一项职责,其逻辑肯定要比负责多项职责简单的多;提高类的可读性,提高系统的可维护性;变更引起的风险降低,变更是必然的,如果单一职责原则遵守的好,当修改一个功能时,可以显著降低对其他功能的影响。需要说明的一点是单一职责原则不只是面向对象编程思想所特有的,只要是模块化的程序设计,都适用单一职责原则。

 

4、里氏替换原则(Liskov Substitution Principle)

超类存在的地方,子类是可以替换的。

 

5、依赖倒置原则(Dependence Inversion Principle)

实现尽量依赖抽象,不依赖具体实现。

why?采用依赖倒置原则可以减少类间的耦合性,提高系统的稳定性,减少并行开发引起的风险,提高代码的可读性和可维护性。

从大局看Java的多态就属于这个原则。

 

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

应当为客户端提供尽可能小的单独的接口,而不是提供大的总的接口。

从大局来说Java的接口可以实现多继承就是接口隔离原则的基础保障。

 

7、迪米特法则(Law Of Demeter) AKA 最少知道原则

即一个类对自己依赖的类知道的越少越好。也就是说,对于被依赖的类不管多少复杂,都尽量将逻辑封装在类的内部。对外除了提供的public 方法,不对外泄露任何信息

 

8、开闭原则(Open Close Principle)

面向扩展开放,面向修改关闭。

一个软件实体如类,模块和函数应该对扩展开放(对提供方),对修改关闭(对使用方)。用抽象构建框架,用实现扩展细节。

 

9、KISS原则

KISS是“keep it simple, stupid”的简称,这种观点认为,一个简单的系统往往比复杂的系统运转得更好,因此,在进行系统设计时应尽量保持简单,避免不必要的复杂性。 

这里强调的是不必要的复杂性,有时候一些复杂性不可避免,但我们无论在产品设计还是架构设计方面,在保持功能完整性的同时,应该尽量做减法,坚守KISS原则。

 

10、好莱坞明星原则

好莱坞明星原则叫IOC(Inversion of control)原则或者控制反转原则,英文里比较形象的说法就是“Don’t call me, I will call you back”,源于这么一个现象:好莱坞的经纪人们一般不希望你去联系他们,而是他们会在需要的时候来联系你,因为主动权确实是掌握在他们手上,耍大牌不用商量。   

传统的编程方式是,用户代码直接调用底层库函数,而控制反转原则是底层框架反过来调用用户编写的代码,类似基于事件的编程。当事件触发时,底层框架代码被调用,然后再反过来调用用户自定义的事件处理程序。这样来看的话,就是框架提供了程序的引擎和接口定义,用户代码负责接口实现,应用程序的开发模式就掌控在框架开发者的手中。引用台湾著名架构师高焕堂的话,就是好莱坞明星原则保证了强盛,保证了底层框架的龙头地位。    

这里我们举个简单的例子吧,比如我们在进行GUI编程时,通常要为控件编写一个OnCreate函数,但是这个函数我们从没有主动去调用它,而是框架在适当的时机来调用,这就是将控制权交给了底层框架,完成了控制反转。

 

11、面向接口原则

面向对象设计模式中有一个模式叫桥接模式(Bridge pattern),提倡基于接口编程,英文里边的说法是:Program to an interface, not an implementation。因为同一个接口可以衍生出各种不同的实现,接口和实现分离使得用户程序可以根据不同的情况选择不同的实现,实现的修改独立于接口的调用者,这样当需要修改实现代码时,接口调用者是无感知的,这样也就降低了软件的耦合度。

下面我们以Java代码为例来看一下如何面向接口编程,首先,我们有一个显示接口,定义如下:

interface displayModule {

  public void display();

}

显示器类,实现displayModule接口

public class Monitor implements displayModule{

   public void display() {  

    System.out.println(“Display through   Monitor”);

   }

}

投影仪类,实现displayModule接口

public class Projector implements displayModule

{

    public void display(){

  System.out.println(“Display through projector”);

    }

}

主机类,聚合了显示接口

public class Computer

{

   // 面向接口编程

   displayModule dm;   

   Public void setDisplayModule

   (displayModule dm)

   {

      this.dm=dm;

    }

   public void display()

   {

       dm.display();

   }

}

程序主函数,类似于一个装配器

public static void main(String args[]) 

{

  Computer cm = new Computer();

  displayModule dm = new Monitor();

  displayModule dm1=new Projector();

  cm.setDisplayModule(dm);

  cm.display();

  cm.setDisplayModule(dm1);

  cm.display();

}

上面的例子中,Computer类聚合了displayModule 接口,然后通过一个方法setDisplayModule来设置具体的接口实现对象,比如显示器或者投影仪。主程序就类似于装配器,程序作者就像装配工人,根据需要装配不同的插件(显示器或者投影仪对象)。如果Computer类聚合的不是接口而是某个实现类,也就是直接聚合某个插件,这样就不需要方法setDisplayModule了,但是失去了灵活性,装配工人也就无法根据需要装配不同的插件了。