240508-java面向对象设计原则

92 阅读4分钟

何为设计原则?

我的理解,就是写代码的模板规范,按着这个规范写代码,可以提高代码的可读性、可复用性、可扩展性,总之一句话,提高代码质量。

五大设计原则(通常被称为SOLID原则)

先copy过来:

  1. 单一职责原则:一个类只负责一项职责(只有一个发生变化的原因)
  2. 开闭原则:对扩展开放,对修改封闭
  3. 里氏替换原则:基类适用的,子类一定适用(子类可以扩展父类的功能,但不能改变父类原有的功能)
  4. 依赖倒置原则:依赖抽象,不要依赖具体(面向接口编程
  5. 接口隔离原则:使用多个专门的接口,而不要使用一个单一的(大)接口(接口单一职责

设计原则一:单一职责原则

(Single Responsibility Principle, SRP)
一个类只负责一个功能

设计原则二:开闭原则

(Open-Closed Principle, OCP)
主要依赖于面向对象三大特性

graph LR
内部代码 --接口--> 外部代码
  • 抽象(继承):定义一个稳定的抽象层,为不同的具体实现提供一个统一的接口。
  • 封装变化:将系统中可能变化的部分封装起来,使外部代码不依赖于这些变化的细节。
  • 多态:利用多态性,可以在运行时动态地选择不同的实现。

示例

// 抽象接口(用于外部代码调用)
public interface Shape {
    void draw();
}

// 实现类1
public class Circle implements Shape {
    public void draw() {
        System.out.println("Drawing a circle.");
    }
}

// 实现类2
public class Rectangle implements Shape {
    public void draw() {
        System.out.println("Drawing a rectangle.");
    }
}

// 外部代码调用(只需要调用抽象层)
public class ShapeDrawer {
    public void drawShape(Shape shape) {
        shape.draw();
    }
}

// 若要扩展功能,可增加实现类3
public class Triangle implements Shape {
    public void draw() {
        System.out.println("Drawing a triangle.");
    }
}

设计原则三:里氏替换原则

(Liskov Substitution Principle, LSP)
把基类所有使用的地方,换成子类,代码仍然可以正常运行

  • 继承关系

    • 里氏替换原则强调了继承关系中父类与子类之间的关系。子类不应该改变父类的行为,而应该扩展父类的功能。
  • 行为一致性

    • 子类的所有实例都应该能够替换掉它们的父类实例,且不影响程序的行为。这要求子类在行为上与父类保持一致。
  • 扩展性

    • 子类可以添加新的方法或属性,但不应该覆盖或改变父类的已有方法的行为。
  • 异常规则

    • 子类在方法实现中不应抛出父类未抛出的异常类型,除非是更具体的异常类型。
// 基类
public class Bird {
    public void fly() {
        System.out.println("The bird is flying");
    }
}

// 不符合里氏替换原则,因为将用Bird的地方换为Sparrow后,调fly方法会抛异常
public class Sparrow extends Bird {
    @Override
    public void fly() {
        throw new RuntimeException("Sparrow is too tired to fly");
    }
}

// 符合里氏替换原则写法
public class Sparrow extends Bird {
    @Override
    public void fly() {
        System.out.println("The Sparrow cannot flying");
    }
}

设计原则四:依赖倒置原则

(Dependency Inversion Principle, DIP)

高层模块不应依赖于低层模块:即service可以依赖dao层,而不要dao层依赖service层 依赖抽象不应依赖于具体:即service层要注入dao层的抽象接口,而不是dao层具体实现类

设计原则五:接口隔离原则

(Interface Segregation Principle, ISP) 抽象接口的定义不能大而全,要合理定义接口

示例

假设我们有一个Animal接口,它包含了所有动物的共同特征:

public interface Animal {
    void eat();
    void sleep();
    void fly();
}

现在,我们有一个Dog类,它实现了Animal接口:

public class Dog implements Animal {
    public void eat() {
        // 狗吃东西的逻辑
    }

    public void sleep() {
        // 狗睡觉的逻辑
    }

    public void fly() {
        // 狗不能飞,这里可以抛出异常或者返回
    }
}

在这个例子中,Dog类实现了fly()方法,但实际上狗并不会飞。这违反了接口隔离原则,因为Dog类并不需要fly()方法。

为了遵循接口隔离原则,我们可以将Animal接口拆分为更细粒度的接口:

public interface Mammals {
    void eat();
    void sleep();
}

public interface FlyingAnimals {
    void fly();
}

然后,我们让Dog类只实现Mammals接口:

复制
public class Dog implements Mammals {
    public void eat() {
        // 狗吃东西的逻辑
    }

    public void sleep() {
        // 狗睡觉的逻辑
    }
}

参考文章

zhuanlan.zhihu.com/p/457307426