类设计原则

来源:互联网 发布:如何评价魔兽世界 知乎 编辑:程序博客网 时间:2024/06/05 02:18

Java 面向对象的类的设计原则

1. 依赖倒置原则(Dependency Inversion Principle)

模块:指能够单独命名并独立 完成一定功能的程序语句集合。

依赖:在程序设计中,如果模块a调用了模块b,我们称模块a依赖模块b。

抽象:即抽象类或接口,是不能够实例化的。

细节:即具体的实现类,实现接口或者继承抽象类所产生的类,可以通过关键字new直接被实例化。

原则: 1. 高层模块不应该依赖低层模块,二者都应该依赖抽象。

             2. 抽象不应该依赖细节,细节应该依赖抽象。

依赖于抽象:建议不应该依赖于具体类,也就是说,程序中所有的依赖关系都应该终止于抽象类或者接口。

根据这个规则可知:

1. 任何变量都不应该持有一个指向具体类的指针或者引用。

2. 任何类都不应该从具体类中派生。

3.任何方法都不应该覆写它的任何基类中的已经实现的方法。

当然如果一个类不太会改变,并且也不会创建其他类型的派生类,那么依赖于它并不会造成损害。比如Java中的String具体类。

Robert C .Martin在原文中给出了Bad Design 的定义:

1. 系统很难改变,因为每个改变都会影响到其他很多部分。

2. 当你对某地方做一修改,系统的看似无关的其他部分都不工作了。

3. 系统很难被另外一个应用重用,因为你很难将要重用的部分从系统中分离开来。

依赖倒置原则提出的缘由:类A直接依赖类B,加入要将类A改成依赖类C,则必须通过改类A的代码来达成。这种场景下,类A一般是高层的模块,负责复杂的业务逻辑;类B

和类C是低层模块,负责基本的原子操作;修改了A则会带来不必要的风险。

依赖倒置原则解决方案:类A的依赖改成接口I,类B和类C来实现接口I,这样就行成了如图的模式:
   
这样,类A 通过接口I间接与类B 或者类C发生关系,则会大大降低修改类A的几率。
依赖倒置原则基于这样一个事实:相对于细节的多变性,抽象的东西要稳定的多,以抽象为基础搭建起来的框架比以细节建起来的狂建要稳定的多。
实际编程中需要注意:
1.低层模块尽量要抽象类或者接口。
2. 变量的声明类型尽量是抽象类或接口。
3. 使用继承时要遵循里氏替换原则 

2. 里氏替换原则(Liskov Substitution Principle)

1. 不应该在代码出现if/else之类类型进行判断的条件      

2. 类应可以代替父类能够出现的任何地方,或者说如果我们把代码中使用基类的地方用它的子类所代替,代码还能正常工作。

问题由来:类A封装了功能P1,子类B继承了类A,为了满足需求,需要扩展子类B的功能,然后重写了类A的封装方法实现新功能,这样就导致了原有功能在应用的时候发生

故障。

解决方案:在使用继承的时候,子类除了添加新功能外,尽量不要重写父类的方法,也尽量不要重载父类的方法。

Ps:重写的方法名和参数数目相同,参数类型兼容,子类方法覆盖父类的方法。

重载最常见的例子就是构造函数。

继承包含这样一层含义:父类中凡是已经实现好的方法,实际上是在舌钉一系列的规范和契约,虽然它不强制要求所有的子类必须遵从这些契约,但是如果子类对这些非抽

象方法任意修改,就会对整个继承体系造成破坏。

比如如下例子:  

public class A {    public int func1(int a, int b)    {        return a-b;    }}public class Client{    public static void main(String[] args)    {        A a = new A();        System.out.println("100-50="+a.func1(100,50));        System.out.println("100-80="+a.func1(100,80));    }}
100-50=50100-80=20

如果对类A进行继承重写方法:  

public class A {    public int func1(int a, int b)    {        return a-b;    }}class B extends A{    public int func1(int a,int b)    {        return a+b;    }    public int func2(int a, int b)    {        return func1(a,b)+100;    }}public class Client{    public static void main(String[] args)    {        B b = new B();        System.out.println("100-50=" + b.func1(100, 50));        System.out.println("100-80=" + b.func1(100, 80));        System.out.println("100+20+100="+b.func2(100,20));    }}


结果:

100-50=150100-80=180100+20+100=220

重写了方法,子类的实现就会和父类有差别,因为我们对父类的方法时熟知的,这样就会造成我们对结果的迷茫。

里氏替换原则通俗来讲就是:子类可以扩展父类的功能,但不能改变父类原有的功能。

里氏替换原则是使代码符合开闭原则的一个重要保证。

3. 开闭原则(Open Closed Principle)

  1. 对修改关闭。某模块被其他模块调用,如果该模块的源代码不允许修改,则该 模块修改关闭的。软件系统的功能上的稳定性,持续性要求是修改关闭的。

      2. 对扩展开放。某模块的功能是可扩展的,则该模块是扩展开放的。软件系统的功能上的可扩展性要求模块是扩展开放的。

开闭原则大致说:用抽象构件框架,用实现扩展细节(不够准确)。

问题由来:在软件的生命周期内,因为变化、升级和维护等原因需要对软件原有代码进行修改时,可能会给旧代码中引入错误,也可能会使我们不得不对整个功能进行重

构,并且需要原有代码经过重新测试。

解决方法:当软件需要变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有代码来实现变化。

其实开闭原则是一种整体总结性的原则,它好像说了所有的东西,又好像没说什么实际的,不过在真正操作的时候很难保证开闭原则。有时我们做项目的时候会遇到

各种情况,很多时候就会违反原则了

4. 单一职责原则(Single Responsibility Principle)

原则:只能让一个类有且仅有一个职责。

原因:如果一个类具有一个以上的职责,那么就会有多个不同的原因引起该类变化,而这种变化将影响到该类不同职责的使用者。

1. 一方面,如果一个职责使用了外部类库,则使用另一个职责的用户却也不得不包含这个未使用的 外部类库。

2. 另一方面,某个用户由于某种原因修改了其中一个职责,另外一个职责的用户也将受到影响,它将不得不重新编译和配置。这违反了设计的开闭原则。

如何划分职责:所谓一个类的一个职责是指引该类变化的一个原因。如果你能想到一个类存在多个使其改变的原因,那么这个类就存在多个职责。

例如:在设计接口Modem的时候,有拨号,挂断,发送数据和接收数据的功能,其中我们的连接管理(拨号和挂断)和数据传输(发送数据和接收数据)之间并没有相通的地方,它们会因为不同理由而改变。这样 就违反了SRP原则。

5. 接口分离原则(Interface Segregation Principle)

原则:

1. 接口的设计原则:接口的设计应该遵循最小接口原则,不要把用户不使用的方法塞在同一个接口里。

2. 接口依赖(继承)原则:如果一个接口a依赖另一个接口b,则接口a相当于集成了接口b的方法,那么继承了接口b后的所有接口a也应该遵循上述原则。

提出原因:如果用户被迫依赖他们不使用的接口,当接口发生变化时,他们也不得不跟着改变。换句话说,一个用户依赖了未使用但被其他用户使用的接口,当其他用户修改了该接口时,依赖该接口的所有用户将受到影响。这显然违反了开闭原则。

比如:

有一个Door,有lock和unlock功能 ,另外,可以在Door上安装一个Alarm而使其具有报警功能。用户可以选择一般的Door,也可以选择具有报警功能的Door。

设计方案:

在Alarm接口定义alarm方法,在Door接口定义lock和unlock方法。接口之间没有继承关系。CommonDoor实现Door接口,AlarmDoor同时实现Door 和Alarm接口。

如果我们直接把Alarm 功能集成在了Door上,那么我们的CommonDoor就用不到alarm功能。

0 0
原创粉丝点击