面向对象的七大设计原则

在开发中,为了提高系统的可维护性和可复用性,增加软件的可扩展性和灵活性,要尽量遵循以下7条原则来进行开发。

开闭原则

定义:软件实体应当对扩展开放,对修改关闭。

开闭原则是面向对象程序设计的终极目标。

里氏替换原则

定义1:如果对每一个类型为S的对象o1,都有类型为T的对象o2,使得以T定义的所有程序P在所有的对象o1都替换成o2时,程序P的行为没有发生变化,那么类型S是类型T的子类型。

定义2:所有引用基类的地方必须能透明地使用其子类的对象。

定义3:继承必须确保超类所拥有的性质在子类中仍然成立。

如何理解?

通俗来讲就是,子类可以扩展父类的功能,但不能改变父类原有的功能,也就是尽量不要重写父类方法。

但是Java的多态又是支持子类重写父类的,这似乎是一种矛盾。看似矛盾,其实不然,个人理解就是建议尽量不要重写可直接实例化的父类的方法,而是基于抽象继承。

里氏替换原则是针对继承而言的,所谓的继承就是为了共享方法,那么共享的父类方法就应该保持不变。当子类通过新增方法来扩展功能而不是继承的时候,调用父类方法的地方可以替换为子类,此时逻辑一致,并不会出现问题。

依赖倒置原则

定义:高层模块不应该依赖低层模块,两者都应该依赖其抽象;抽象不应该依赖细节;细节应该依赖抽象。

核心思想:面向接口编程,不要面向实现编程。

如何理解?

在Java中抽象指接口或抽象类,细节就是实现类。实现类之间的依赖关系要通过接口或抽象类,也就是细节应该依赖抽象。

如果在抽象类中的属性是某个实现类,或者在抽象类的方法中调用实现类的方法,那么当实现类发生变动的时候,抽象类也会发生变动,抽象类发生变动的时候,低下的所有实现类也可能发生变动,这有点绕,总结起来就是:当抽象依赖细节的时候,很可能牵一发动全身,这是很危险的,因此抽象不应该依赖细节。

另外依赖是可以传递的,A依赖B,B依赖C,C依赖D,.......,此时如果做到抽象依赖,那么多层依赖也无所畏惧,但实际上应该没有人会写出很多层依赖的代码,这让人感觉很愚蠢。

怎么做?

1、通过构造函数传递依赖对象

2、通过Setter依赖注入

如下代码,Driver类中的 car 属性是抽象的,可以通过构造函数或者set方法注入具体实现类,可以根据不同情况放入法拉利、奔驰等汽车,从而降低了类之间的耦合性。

public interface IDriver {
    void driver();
}

public class Driver implements IDriver {
    private ICar car;
    
    // 
    public Driver(ICar car) {
        this.car = car;
    }
    
    @Orivide
    public void driver() {
        this.car.run();
    }
    
    public void setCar(ICar car) {
        this.car = car;
    }
    
}

最佳实践

  • 每个类尽量提供接口或抽象类,或者两者都具备

  • 变量的声明类型尽量是接口或者是抽象类

  • 任何类都不应该从具体类派生

  • 尽量不覆写基类方法

  • 使用继承时尽量遵循里氏替换原则

优点:

  • 降低类之间的耦合性

  • 提高系统的稳定性

  • 降低并行开发引起的风险

  • 提高代码可读性和可维护性

单一职责原则

定义:一个类应该有且仅拥有一个引起它变化的原因,否则类应该被拆分。

核心思想:控制类的粒度大小、将对象解耦、提高其内聚性。

如何理解?

光看定义有点难以理解。假如:类A负责职责1和职责2,当职责1需求变更而变更A,可能造成职责2执行错误,为此不得不改变职责2,此时引起类A变化的原因就有两个了。这显然降低了代码的可维护性。

怎么做?

其实就是在设计类或者接口的时候,类所拥有职责应该清晰,不应该混杂其他相关性不强的职责,因此我们在设计类的时候,要对类的不同职责进行划分和界定,将其封装到不同的类或者模块中。

举个例子:

现在有一些职责需要抽象成类

  • 获取用户基本信息

  • 用户进行登录

  • 用户退出登录

这个时候如何进行类的设计呢?

第一种:

既然都是与用户相关,那么我抽象出一个用户类不就可以了吗,类图如下:


image-20210629003814900.png

很明显,上图的用户类混杂着与用户类不相关的职责或者相关性不强的职责——登录和退出登录。当改变其中的某一职责时,可能导致其他职责也需要进行改变。

第二种:

既然改变某个职责会影响其他职责,那么就将相关性相同的抽离出来,将类进行拆分。如下,当用户信息类发送改变,并不会导致用户行为类发生改变。

面向对象设计原则——单一职责02.png

单一职责适用于类或接口,也适用于方法,也就是说一个方法只做一件事情,比如获取用户头像,不要把这个方法放到“获取用户基本信息”中去,否则这个方法颗粒度很粗,我们应该将其进行拆分。

优点:

  • 降低类复杂度

  • 提高类可读性

  • 提高系统可维护性

  • 降低变更引起的风险

接口隔离原则

定义1:客户端不应该被迫依赖于它不使用的方法。

定义2:一个类对另一个的依赖应该建立在最小的接口上。

和单一职责原则的区别?

单一职责原则强调的是,类和接口的职责要单一,也就是说一个类最好只负责一类事情。

而接口隔离原则强调的是对接口依赖的隔离,接口要尽量小。

如何做?

  • 一个接口只服务于一个子模块或业务逻辑

  • 只提供调用者需要的方法,屏蔽不需要的方法

  • 根据业务进行最好的接口设计

迪米特法则

定义:只与直接朋友交谈,不跟陌生人说话

如何理解?

什么是直接朋友?两个对象之间存在耦合关系,如:组合、聚合、依赖等

如果两个类无须直接通信,那么就不应该发生互相调用,应当通过第三方转发调用。例如:买房时,买家无须与卖家交谈,而是通过中介进行通信。

怎么做?

在类的划分上,尽量创建弱耦合的类

在类的结构设计上,尽量降低类成员的访问权限

在类的设计上,优先考虑将一个类设置成不变类

在对其他类的引用上,将引用其他对象的次数降低到最低

不暴露类的属性成员,而应该提供相应的get/set方法

合成复用原则

定义:在软件复用时,要尽量先使用组合或者聚合等关联关系来实现,其次才考虑使用继承关系来实现。如果要使用继承关系,则必须严格遵循里氏替换原则。合成复用原则同里氏替换原则相辅相成的,两者都是开闭原则的具体实现规范。

总结

开闭原则:对扩展开放,对修改关闭

里氏替换原则:继承必须确保超类所拥有的性质在子类中仍然成立

依赖倒置原则:面向接口编程,不要面向实现编程

单一职责原则:控制类的粒度,类的职责要单一

接口隔离原则:设计接口要精简单一

迪米特法则:只与直接朋友交谈,不跟陌生人说话

合成复用原则:优先使用组合或聚合关系,其次才是继承

你可能感兴趣的:(面向对象的七大设计原则)