OOP七大法则

197 阅读8分钟

开闭原则(The Open/Closed Principle,OCP)

该原则规定“软件中的对象(类、模板、函数等等)应该对于扩展是开放的,但是对于修改是封闭的”,这意味着一实体是允许在不改变它的源代码的前提下变更它的行为。

对扩展开放,对修改关闭在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。是程序扩展性好,易于维护和升级。

要达到这样的效果,我们需要使用接口和出抽象类。

image-20220412093045885

例如在这里,使用抽象的Animal来代表所有的动物,动物都有吃的行为,但不同动物不一样。当发现新的动物时,我们避免去修改AbstractAniaml的方法,只需要考虑继承其接着重写该方法

里氏替换原则(Liskov Substitution Principle LSP)

里氏代换原则中说,任何基类可以出现的地方,子类一定可以出现。 LSP是继承复用的基石,只有当衍生类可以替换掉基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为。里氏代换原则是对“开-闭”原则的补充。实现“开-闭”原则的关键步骤就是抽象化。而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。

通俗的理解:子类可以扩展父类的功能,但不能改变父类原有的功能。换句话说,子类继承父类时,除添加新的方法完成新增功能外,尽量不要重写父类的方法。

如果全都简单的继承父类重写方法完成新的功能,这样写起来虽然简单,但整个体系的复用性会较差。多态使用频繁时,程序运行出错概率较大。

里式替换原则是对开闭原则的补充,强调在继承父类时,尽可能的避免重写父类方法.

“正方形不是长方形”便是一个经典的例子。我们知道,在数学上,这是一个伪命题:正方形是特殊的长方形,它们的长和宽相等。

但在程序设计时,让正方形去继承长方形便是违反了LSP原则。

设计一个矩形类:

public class Rectangle {

    private int width;

    private int length;

    public int getWidth() {
        return width;
    }

    public void setWidth(int width) {
        this.width = width;
    }

    public int getLength() {
        return length;
    }

    public void setLength(int length) {
        this.length = length;
    }

    public int getArea() {
        return this.width * this.length;
    }

}

设计一个正方形类:

public class Square extends Rectangle {

    @Override
    public void setLength(int length) {
        super.setLength(length);
        super.setWidth(length);
    }

    @Override
    public void setWidth(int width) {
        super.setWidth(width);
        super.setLength(width);
    }

}

设计一个操作类:

public class Operate {

    public static void main(String[] args) {

        // 实例一
        Rectangle r1 = new Rectangle();
        r1.setWidth(3);
        r1.setLength(4);
        System.out.println(r1.getArea());

        // 实例二
        Rectangle r2 = new Square();
        r2.setWidth(3);
        r2.setLength(4);
        System.out.println(r2.getArea());

    }

}

结果:

12
16

进程已结束,退出代码0

实例二中,实例化了一个矩形对象,设定长宽分别为3和4,却得到结果16。由此,“正方形不是长方形”,当正方形继承长方形,且重写其方法时,在多态实例化时,带来了灾难性的错误。

现实生活中,类似的例子还有很多:

  • 企鹅不是鸟,因为其不会飞
  • 鸵鸟不是鸟,它也不会飞
  • 玩具手枪不是枪,它不能抵御敌人
  • ...

依赖倒置原则(Dependence Inversion Principle)

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

DIP就像它的定义一样抽象(流汗黄豆)

简单解释就是要求开发者对抽象进行编程,不要对实现进行编程,以降低客户与实现模块之间的耦合度。

逐句来理解:

  • 高层模块不应该依赖于低层模块,两者都应该依赖其抽象,也就是说在高层模块与底层模块之间加一层(抽象层)。高层模块依赖抽象层的功能去实现,而底层按照抽象层方法的定义去开发
  • 抽象不应该依赖细节,细节应该依赖抽象,举个例子,在定义接口方法时,不要为了要完成某个细节的功能而去定义一个方法,而是提供一个抽象的方法,让实现层去个性化的实现。这也就做到了”举一反三“的效果。

为什么是倒置,而不是转移

这也是我思考很久的问题,这里说说我的看法:

传统开发中,高层功能都紧紧依赖底层来实现:我底层提供了生产奔驰汽车的功能,高层便向外界提供开奔驰的服务API。当客户提出要开宝马时,高层便等待底层实现了生产宝马的方法,再向外界提供开宝马的API。这简直是对OOP的侮辱!于是,为了满足一个通用的业务:例如客户要享受不同的驾驶服务,今天开宝马,明天开特斯拉....引入一个抽象层,高度抽象一下实现的方法,然后交给实现层根据抽象的定义来实现。这样看来,由原来的高层依靠底层提供的功能提供API服务变成了高层先说明我要提供什么API服务,抽象层根据此来抽象业务,实现层再根据抽象层的定义完成逻辑业务,实现了依赖的反转

由高层依赖底层,反转为底层依赖高层。由抽象依赖细节,反转为细节依赖抽象。很多人不理解倒置,是因为我们在学习开源框架的过程中,框架就限制了我们按照这些原则进行开发,让我们忽略了设计模式的存在。其实,在SpringBoot的经典四层架构中,处处都是DIP的身影。

单一职责原则(Single responsibility principle,SRP)

不要存在多于一个导致类变更的原因

通俗的来说,一个类只负责一项职责

该原则提出对象不应该承担太多职责,如果一个对象承担了太多的职责,至少存在以下两个缺点:

  • 一个职责的变化可能会削弱或者抑制这个类实现其他职责的能力;

  • 当客户端需要该对象的某一个职责时,不得不将其他不需要的职责全都包含进来,从而造成冗余代码或代码的浪费。

单一职责原则的核心就是控制类的粒度大小、将对象解耦、提高其内聚性。

一类只负责一件事,提高了类的专一度。当有新的业务时,在修改一个类时,大大降低对其他类的干扰。

在Java开发中,SRP是一种默许的行为。

迪米特法则(Law of Demeter,LOD)

迪米特法则(Law of Demeter)又叫作最少知识原则(The Least Knowledge Principle),一个类对于其他类知道的越少越好,就是说一个对象应当对其他对象有尽可能少的了解,只和朋友通信,不和陌生人说话。

通俗来说:如果两个模块之间无须直接通信,那么就不应该直接的相互调用,可以通过第三方转发该调用。这样降低了模块间的耦合度,提高了模块的相对独立性。

我和朋友常将其称为社恐法则。当路上遇到小仙女时,却不敢要WX,可以通过社牛朋友代为交接,前去交涉,表达自己的爱慕之意。好吧,开个玩笑,例子可能不完全符合。也建议大家要勇敢示爱,否则就真正降低了和小仙女之间的耦合度了。

接口隔离法则(Interface Segregation Principle,ISP)

客户端不应该被迫依赖于它不使用的方法;一个类对另一个类的依赖应该建立在最小的接口上

ISP同单一职责原则,一类将类的功能原子化,一个将接口定义的功能原子化。以降低耦合度,提高内聚度。

例如这里,理发店可提供洗剪吹服务,于是我定义一个洗剪吹的接口服务。当客户要求只要洗剪而不吹时,就傻眼了。此时再来修改接口,就未被了OCP法则。因此我们需要将接口更加细化。

image-20220412115831936

合成复用原则(Composite Reuse Principle, CRP)

尽量先使用组合或者聚合等关联关系来实现,其次再考虑使用继承关系来实现

通常类的复用分为继承复用和合成复用两种。继承复用虽然简单易实现。但破坏类的封装性,提高了耦合度,限制了代码复用的灵活性。

采用组合或聚合复用时,将已有对象纳入新对象,使之成为新对象的一部分,新对象调用此对象的功能。实现is ahas a的转变。

很经典的例子:

image-20220412121919355

image-20220412122737339

使用组合一改继承的啰里啰唆,高效完成了汽车的分类。

今天总结了OOP开发七大法则,为设计模式的学习打下基础。配图都很卡哇伊啊,UML使用IDEA插件完成。

image-20220412123044674