面向对象编程(OOP)的 SOLID 设计原则

  • The Single Responsibility Principle (SRP): 单一责任原则
  • The Open-Closed Principle (OCP): 开放 - 封闭原则
  • The Liskov Substitution Principle (LSP): Liskov 替换原则
  • The Interface Segregation Principle (ISP): 接口隔离原则
  • The Dependency Inversion Principle (DIP): 依赖转置原则

单一责任原则(SRP)

  • 责任的含义:变化的原因(a reason for change)。
  • SRP 的核心在于:一个类应该只有一个引起它变化的原因,即一个类应该只负责一项职责。

当一个类包含了多个责任时,会引起以下不良后果:

  1. 代码难以理解和维护: 包含多个责任的类会导致代码逻辑复杂,不同责任的代码相互交织在一起,使得代码难以理解和维护。当需要修改其中一个责任时,可能会影响到其他责任,增加了修改的风险和成本。
  2. 耦合性增加: 不同责任的代码逻辑耦合在一起,使得类与其他类之间的耦合性增加。这会导致类的复用性降低,因为修改一个责任可能会影响到其他相关的功能,从而限制了类的灵活性。
  3. 难以测试: 包含多个责任的类通常会有更多的方法和行为,这会增加测试的复杂度。因为每个责任都可能需要不同的测试情况和测试数据,导致测试变得困难和耗时。

考虑一个描述汽车的类,它应该只关注汽车的属性和行为,而不应该包含关于维修或销售汽车的逻辑。

class Car {
    private String model;
    private int year;

    // Constructor, getters, and setters

    public void startEngine() {
        // Code to start the engine
    }

    public void accelerate() {
        // Code to accelerate the car
    }
}

class CarRepairShop {
    public void repairCar(Car car) {
        // Code to repair the car
    }
}

在这个示例中,Car 类只负责描述汽车的属性和行为,而 CarRepairShop 类负责维修汽车。如果将 repair() 方法在 Car 中实现,会导致类的职责不清晰,增加了类的复杂性和耦合性。

开放 - 封闭原则(OCP)

Classes should be:

  • open for extension (对扩展的开放)
  • but closed for modification (对修改的封闭)

考虑两个类,圆形(Circle)和矩形(Rectangle)。AreaCalculator 用于计算面积。

class Circle {
    private double radius;

    public Circle(double radius) {
        this.radius = radius;
    }

    public double area() {
        return Math.PI * radius * radius;
    }
}

class Rectangle {
    private double width;
    private double height;

    public Rectangle(double width, double height) {
        this.width = width;
        this.height = height;
    }

    public double area() {
        return width * height;
    }
}

class AreaCalculator {
    public double calculateArea(Object shape) {
        if (shape instanceof Circle) {
            Circle circle = (Circle) shape;
            return circle.area();
        } else if (shape instanceof Rectangle) {
            Rectangle rectangle = (Rectangle) shape;
            return rectangle.area();
        }
        return 0;
    }
}

在上述示例中,如果需要新增一种形状,如三角形,那么需要修改 AreaCalculator.calculateArea(),维护分支语句,较为繁琐。违背了 OCP。

我们希望遵循 OCP,在添加新的形状类型的同时,而不修改已有的代码,关键技术在于使用抽象

考虑增添一个形状(Shape)接口(或者抽象类),圆形(Circle)和矩形(Rectangle)分别继承这个接口。

interface Shape {
    double area();
}

class AreaCalculator {
    public double calculateArea(Shape shape) {
        return shape.area();
    }
}

class Circle implements Shape {
    private double radius;

    // Constructor, getters, and setters

    @Override
    public double area() {
        return Math.PI * radius * radius;
    }
}

class Rectangle implements Shape {
    private double width;
    private double height;

    // Constructor, getters, and setters

    @Override
    public double area() {
        return width * height;
    }
}

// 新增一个三角形类,不需要修改已有代码
class Triangle implements Shape {
    private double base;
    private double height;

    // Constructor, getters, and setters

    @Override
    public double area() {
        return 0.5 * base * height;
    }
}

在上述示例中,我们新增一个三角形(Triangle)类,不需要修改已有代码。

Liskov 替换原则(LSP)

子类型必须能够替换其基类型而不影响程序的正确性。

考虑一个图形类的层次结构,其中包含了正方形(Square)和矩形(Rectangle),正方形是矩形的子类。根据 LSP,子类正方形应该能够替换父类矩形而不影响程序的正确性。

class Rectangle {
    private int width;
    private int height;

    public Rectangle(int width, int height) {
        this.width = width;
        this.height = height;
    }

    public int getWidth() {
        return width;
    }

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

    public int getHeight() {
        return height;
    }

    public void setHeight(int height) {
        this.height = height;
    }

    public int area() {
        return width * height;
    }
}

class Square extends Rectangle {
    public Square(int size) {
        super(size, size);
    }

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

    @Override
    public void setHeight(int height) {
        super.setWidth(height);
        super.setHeight(height);
    }
}

接口隔离原则(ISP)

一个类不应该强迫它的客户端依赖于它们不需要使用的方法,即:只提供必要的接口。

ISP 要求我们不应设计 " 胖 " 接口," 胖 " 接口会导致不够聚合。

  • " 胖 " 接口应该分解为多个小的接口
  • 不同的接口向不同的客户端提供服务
  • 客户端只访问自己所需要的端口

考虑一个打印机(Printer)接口,可能包含打印 (print()) 和扫描 (scan()) 功能。

interface Printer {
    void print();
    void scan();
}

class SimplePrinter implements Printer {
    @Override
    public void print() {
        // Code to print
    }

    @Override
    public void scan() {
        throw new UnsupportedOperationException("This printer does not support scanning");
    }
}

class MultiFunctionPrinter implements Printer {
    @Override
    public void print() {
        // Code to print
    }

    @Override
    public void scan() {
        // Code to scan
    }
}

在上述示例中,SimplePrinter 类并不支持 scan() 功能,但却实现了 Printer 接口,进而违背了 ISP。

为了分解功能,我们重新设计两个接口 PrintableScanableSimplePrinter 只实现 Printable,而 MultiFunctionPrinter 同时实现 PrintableScanable

interface Printable {
    void print();
}

interface Scanable {
    void scan();
}

class SimplePrinter implements Printable {
    @Override
    public void print() {
        // Code to print
    }
}

class MultiFunctionPrinter implements Printable, Scanable {
    @Override
    public void print() {
        // Code to print
    }

    @Override
    public void scan() {
        // Code to scan
    }
}

依赖转置原则(DIP)

  • 高层模块不应该依赖于低层模块,二者都应该依赖于抽象
  • 抽象不应该依赖于具体实现,具体实现应该依赖于抽象。

考虑一个简单的电灯控制器(LightSwitch),能控制灯(Light)的开关。朴素的想法是让其直接依赖于 Light,如下代码所示:

class Light {
    public void turnOn() {
        // Code to turn on light
    }

    public void turnOff() {
        // Code to turn off light
    }
}

class LightSwitch {
    private Light light;

    public LightSwitch(Light light) {
        this.light = light;
    }

    public void turnOn() {
        light.turnOn();
    }

    public void turnOff() {
        light.turnOff();
    }
}

但是如果未来需求不只是控制电灯(Light),还需要控制 LED,白炽灯等。则需要修改 LightSwitch 的相关方法,改动分支语句,较为繁琐。这违背了 DIP。

为了改进,我们应该让 LightSwitch 不依赖于具体类 Light,而是抽象类或接口。我们设计一个 Switchable 接口,让 LightSwitch 直接依赖于这个抽象接口。

interface Switchable {
    void turnOn();
    void turnOff();
}

class Light implements Switchable {
    @Override
    public void turnOn() {
        // Code to turn on light
    }

    @Override
    public void turnOff() {
        // Code to turn off light
    }
}

class LightSwitch {
    private Switchable device;

    public LightSwitch(Switchable device) {
        this.device = device;
    }

    public void turnOn() {
        device.turnOn();
    }

    public void turnOff() {
        device.turnOff();
    }
}

此时,当再次面对变化时,例如增加一个照明设备,我们只需要新增一个类,实现 Switchable 即可。

  • 22
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值