面向对象设计原则

面向对象设计原则是一组用于指导良好的软件设计的基本准则。这些原则帮助开发人员创建可维护、可扩展和易于理解的代码,所有的设计模式都会遵循这些基本原则。

1、单一职责原则

单一职责原则强调一个类只有一个单一职责,将一个类的功能限制在一个明确定义的领域内。

// 不遵循单一职责原则的示例

class Employee {
    private String name;
    private double salary;

    public Employee(String name, double salary) {
        this.name = name;
        this.salary = salary;
    }

    public void saveToDatabase() {
        // 将员工信息保存到数据库
        // ...
    }

    public void logTransaction() {
        // 记录员工信息保存操作的日志
        // ...
    }
}

上面Employee类既负责将员工数据保存到数据库(saveToDatabase方法),又负责保存操作日志(logTransaction),它违反了单一职责原则,因为它有两个不同的职责:数据持久化和日志记录,日志记录是应用程序常见任务,它可被视为一个独立的职责,应该分离出来。重构后代码如下:

// 遵循单一职责原则的示例

class Employee {
    private String name;
    private double salary;

    public Employee(String name, double salary) {
        this.name = name;
        this.salary = salary;
    }
}

class EmployeeRepository {
    public void saveToDatabase(Employee employee) {
        // 将员工信息保存到数据库
        // ...
    }
}

class Logger {
    public void logTransaction(String message) {
        // 记录日志
        // ...
    }
}

2、开闭原则

开闭原则它强调软件实体应该对扩展开放,对修改关闭,就是当要添加新功能时,应该通过扩展现有代码,而不是修改现有代码。

// 不遵循开闭原则的示例

class Rectangle {
    double width;
    double height;

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

class Circle {
    double radius;

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

class AreaCalculator {
    public double calculateRectangleArea(Rectangle rectangle) {
        return rectangle.width * rectangle.height;
    }

    public double calculateCircleArea(Circle circle) {
        return Math.PI * circle.radius * circle.radius;
    }
}

在上述设计中,AreaCalculator 类负责计算矩形和圆形的面积。然而,如果我们想要添加更多的图形类型,例如三角形、椭圆等,就需要不断修改 AreaCalculator 类,违反了开闭原则。

现在,让我们使用开闭原则来改进这个设计。我们可以引入一个抽象图形类,并为每种具体的图形创建子类。然后,我们可以使用多态性来计算图形的面积,而不需要修改 AreaCalculator 类。

// 遵循开闭原则的示例

abstract class Shape {
    abstract double calculateArea();
}

class Rectangle extends Shape {
    double width;
    double height;

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

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

class Circle extends Shape {
    double radius;

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

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

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

通过这种改进,我们可以轻松地添加新的图形类型,只需创建新的图形子类并实现 calculateArea 方法,而不需要修改现有代码。这遵循了开闭原则,使得程序更加灵活和可扩展。

3、里氏替换原则

如果一个类型是另一个类型的子类,那么它应该可以替代父类型而不引起错误,但是反过来则不成立。

class Bird {
    void fly() {
        System.out.println("Bird can fly");
    }
}

class Sparrow extends Bird {
    // Sparrow类继承了Bird类,并且保持了相同的行为
}

class Ostrich extends Bird {
    @Override
    void fly() {
        System.out.println("Ostrich can't fly"); // 重写了fly方法,但仍然遵循Bird类的行为
    }
}

public class LSPExample {
    public static void main(String[] args) {
        Bird bird1 = new Sparrow();
        Bird bird2 = new Ostrich();

        bird1.fly(); // 输出:Bird can fly
        bird2.fly(); // 输出:Ostrich can't fly
    }
}

在这个示例中,Bird 是基类,而 SparrowOstrich 是继承自 Bird 的子类。虽然 Ostrich 重写了 fly 方法,但它仍然遵循了 Bird 类的行为,即打印出一条关于飞行能力的消息。这个示例展示了里氏替换原则的应用,即子类可以替代父类而不破坏程序的正确性,同时可以在子类中扩展或修改行为。这提高了代码的灵活性和可扩展性。

4、依赖倒转原则

依赖倒转原则强调高层模块不应该依赖于低层模块,它们都应该依赖于抽象。同时,抽象不应该依赖于具体实现,具体实现应该依赖于抽象。

// 不符合依赖倒转原则的示例

class Switch {
    void turnOn() {
        // 打开电子设备
    }

    void turnOff() {
        // 关闭电子设备
    }
}

上面例子Switch 类用于控制电子设备的开关,高层模块依赖于具体的Switch类,这违反了依赖倒转原则。

// 使用依赖倒转原则来

interface Device {
    void turnOn();

    void turnOff();
}

然后,我们修改 Switch 类,使其依赖于抽象的 Device 接口:

class Switch {
    private Device device;

    public Switch(Device device) {
        this.device = device;
    }

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

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

现在,Switch 类不再直接控制具体的电子设备,而是通过 Device 接口来控制.

这种改进遵循了依赖倒转原则,如果需要添加新的电子设备,只需创建一个实现 Device 接口的新类,并将其传递给 Switch 对象即可,而不需要修改现有代码。

5、接口隔离原则

接口隔离原则强调客户不应该它们不需要的接口,一个类对其它的类有最小的依赖。

// 不遵循接口隔离原则示例

interface MediaPlayer {
    void playAudio();

    void playVideo();
}

class AudioPlayer implements MediaPlayer {
    @Override
    public void playAudio() {
        System.out.println("Playing audio...");
    }

    @Override
    public void playVideo() {
        // 实际上不需要实现这个方法
    }
}

class VideoPlayer implements MediaPlayer {
    @Override
    public void playAudio() {
        // 实际上不需要实现这个方法
    }

    @Override
    public void playVideo() {
        System.out.println("Playing video...");
    }
}

这个设计违反了接口隔离原则,因为每个具体类都不需要实现的方法都被强制实现了。例如,AudioPlayer 不需要实现 playVideo 方法,而 VideoPlayer 不需要实现 playAudio 方法。

//  遵循接口隔离原则示例

采用更小的接口,分别用于音频播放和视频播放:

interface AudioPlayer {
    void playAudio();
}

interface VideoPlayer {
    void playVideo();
}

class BasicAudioPlayer implements AudioPlayer {
    @Override
    public void playAudio() {
        System.out.println("Playing audio...");
    }
}

class BasicVideoPlayer implements VideoPlayer {
    @Override
    public void playVideo() {
        System.out.println("Playing video...");
    }
}

改进后的设计中,我们定义了两个更小的接口:AudioPlayerVideoPlayer,分别用于音频播放和视频播放。每个类只需要实现其自身所需的接口,不再需要强制性地实现不需要的方法。

6、合成复用原则

合成复用原则强调在软件设计中应该尽量使用合成(组合)而不是继承来实现代码的复用。

// 不遵循合成复用原则示例

class Shape {
    void draw() {
        // 绘制图形的通用代码
    }
}

class Rectangle extends Shape {
    @Override
    void draw() {
        // 绘制矩形的具体代码
    }
}

class Circle extends Shape {
    @Override
    void draw() {
        // 绘制圆形的具体代码
    }
}

在这个设计中,我们使用继承来实现不同类型的图形,它违反了合成复用原则。如果以后需要添加新的图形类型,我们可能会不断创建新的子类,这会导致类层次结构的膨胀和复杂性的增加。

// 遵循合成复用原则示例

interface Drawable {
    void draw();
}

class Rectangle implements Drawable {
    @Override
    public void draw() {
        // 绘制矩形的具体代码
    }
}

class Circle implements Drawable {
    @Override
    public void draw() {
        // 绘制圆形的具体代码
    }
}

class DrawingBoard {
    private List<Drawable> shapes = new ArrayList<>();

    public void addShape(Drawable shape) {
        shapes.add(shape);
    }

    public void render() {
        for (Drawable shape : shapes) {
            shape.draw();
        }
    }
}

改进后的设计中,我们定义了一个 Drawable 接口,表示可以绘制的对象。每个图形类实现了这个接口。然后,我们创建了一个 DrawingBoard 类,它负责管理要绘制的图形对象。通过合成复用,我们可以轻松地添加新的图形类型,只需实现 Drawable 接口并将其添加到 DrawingBoard 中即可。

7、迪米特法则

迪米特法则强调了模块之间应该保持松散的耦合,一个对象应该尽可能少地了解其他对象的内部结构。

// 不遵循迪米特法则示例

class TVRemote {
    private TV tv;

    public TVRemote(TV tv) {
        this.tv = tv;
    }

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

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

class TV {
    public void turnOn() {
        System.out.println("TV is turned on.");
    }

    public void turnOff() {
        System.out.println("TV is turned off.");
    }
}

上面代码是一个电视遥控器设计,在这个设计中,TVRemote 类需要了解 TV 类的内部细节,它直接与 TV 类交互来控制电视的开关。这种设计违反了迪米特法则。

// 遵循迪米特法则示例

class TVRemote {
    public void turnOn(TV tv) {
        tv.turnOn();
    }

    public void turnOff(TV tv) {
        tv.turnOff();
    }
}

class TV {
    public void turnOn() {
        System.out.println("TV is turned on.");
    }

    public void turnOff() {
        System.out.println("TV is turned off.");
    }
}

改进后的设计中,TVRemote 类不再持有 TV 对象,而是通过参数传递 TV 对象来进行操作。这降低了对象之间的依赖关系,使得代码更加松散耦合,符合迪米特法则的要求。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值