面向对象六大原则

单一职责原则

单一职责原则是指责任应该分配到单一的类或者模块中,使得这个类或者模块只需要负责单一的职责。这样可以提高代码的可维护性和可重用性,减少不必要的耦合。

下面用一个简单的例子来演示:
一个在线商城系统,其中订单(Order)是系统中的一个重要概念,我们来看看如何遵循单一职责原则进行设计。
首先,订单(Order)具有哪些职责呢?

  • 记录订单编号、下单时间、总价等属性;
  • 包含购买的商品明细,即一组订单项(OrderItem);
  • 处理订单状态的流转,例如确认订单、支付订单、发货、完成等。
    根据单一职责原则,我们需要将这些职责分别分配给不同的类或模块。

对于第一个职责,即记录订单基本属性的部分,我们可以设计一个名为 Order 的类,表示一个订单实例。示例代码如下:

public class Order {
    private String orderId;
    private Date orderTime;
    private BigDecimal totalPrice;

    // 构造函数、getter 和 setter 等
    // ...

    // 计算总价
    public void calculateTotalPrice() {
        // ...
    }
}

对于第二个职责,即包含购买的商品明细的部分,我们可以设计一个名为 OrderItem 的类,表示一个订单项,同时在 Order 类中使用 List 来存储所有的订单项。示例代码如下:

public class OrderItem {
    private String productId;
    private int quantity;
    private BigDecimal price;

    // 构造函数、getter 和 setter 等
    // ...
}
public class Order {
    private String orderId;
    private Date orderTime;
    private BigDecimal totalPrice;
    private List<OrderItem> orderItems;

    // 构造函数、getter 和 setter 等
    // ...
}

对于第三个职责,即处理订单状态的流转的部分,我们可以将其分配给一个名为 OrderService 的服务类,其中包含各种订单操作方法,例如确认订单、支付订单、发货、完成等。示例代码如下:

public class OrderService {
    public void confirmOrder(Order order) {
        // ...
    }

    public void payOrder(Order order) {
        // ...
    }

    public void shipOrder(Order order) {
        // ...
    }

    public void completeOrder(Order order) {
        // ...
    }
}

通过将订单(Order)的不同职责分配给不同的类或模块,我们遵循了单一职责原则的要求,使得代码更加清晰、可维护和可重用。

开闭原则

开闭原则是指一个软件实体应该对扩展开放,对修改关闭。这意味着一个模块、类或者方法等在需要增加新功能时,应该通过扩展来实现,而不是修改已有的代码来实现。遵循开闭原则可以使得代码更加健壮和灵活,减少代码的复杂度,并且易于维护和扩展。

例如假设我们要设计一个计算器程序,该程序可以执行加、减、乘、除等运算。首先,我们可以定义一个名为 Arithmetic 的接口,表示进行数学运算的一组操作。该接口只包含一个方法 calculate,用于执行具体的运算。代码如下:

public interface Arithmetic {
    double calculate(double num1, double num2);
}

然后,对于每一种运算,我们都可以实现一个相应的类来实现 Arithmetic 接口。例如,实现 Add 类来进行加法运算,Multiply 类来进行乘法运算等。示例代码如下:

public class Add implements Arithmetic {
    @Override
    public double calculate(double num1, double num2) {
        return num1 + num2;
    }
}

public class Multiply implements Arithmetic {
    @Override
    public double calculate(double num1, double num2) {
        return num1 * num2;
    }
}

// 其他类似的运算类...

现在,如果我们需要新增一种运算,例如取模(Mod)运算,我们不需要修改已有的代码,只需要新增一个相应的实现类就可以了。这样做即符合开闭原则的要求,也避免了对已有代码的影响。示例代码如下:

public class Mod implements Arithmetic {
    @Override
    public double calculate(double num1, double num2) {
        return num1 % num2;
    }
}

遵循开闭原则我们保持了代码的稳定性,并且能够容易地扩展新功能,当然在实际项目中我们还要考虑更多其他方面的细节,比如代码的复用、抽象程度等。

里氏替换原则

里氏替换原则是指父类对象可以被子类对象替换,并且不会影响程序的正确性。也就是说,在使用父类对象的地方,应该能够使用其任何子类对象,而不需要修改原有代码。遵循里氏替换原则可以保证程序的稳定性和可扩展性。
假设我们要设计一个图形库,其中包含一系列的图形类型,例如矩形(Rectangle)、正方形(Square)、圆形(Circle)等等。我们定义一个名为 Shape 的抽象类,作为所有图形类型的父类。Shape 类定义了一个名为 draw 的抽象方法,以及一些公共属性和方法。代码如下:

public abstract class Shape {
    protected int x;
    protected int y;

    public Shape(int x, int y) {
        this.x = x;
        this.y = y;
    }

    public abstract void draw();
    
    // 其他公共方法...
}

然后,我们定义各个具体图形类,并且继承自 Shape 类。这里我们以矩形(Rectangle)和正方形(Square)为例,其中 Square 类继承自 Rectangle 类。代码如下:

public class Rectangle extends Shape {
    protected int width;
    protected int height;

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

    @Override
    public void draw() {
        // 绘制矩形的逻辑...
    }

    // 其他方法...
}

public class Square extends Rectangle {

    public Square(int x, int y, int sideLength) {
        super(x, y, sideLength, sideLength);
    }

    // 重写父类的方法
    @Override
    public void draw() {
        // 绘制正方形的逻辑...
    }

    // 其他方法...
}

在这个设计中,Square 类继承自 Rectangle 类,符合里氏替换原则的要求。因为一个正方形也可以被当做一个特殊的矩形来看待,它具有矩形的所有属性和方法,同时又有一些额外的特定属性和方法。

如果我们遵循了里氏替换原则,我们应该能够使用 Shape 类型的对象来替换任何 Rectangle 或者 Square 的对象,而不会影响程序的正确性。例如,我们可以定义一个名为 drawAll 的方法,用于绘制多个图形,在该方法中可以使用 Shape 类型的数组作为参数,其中可以包含 Rectangle 或者 Square 类型的实例。示例代码如下:

public static void drawAll(Shape[] shapes) {
    for (Shape shape : shapes) {
        shape.draw();
    }
}

通过这种设计方式,我们不仅保证了程序的正确性,同时也增加了程序的灵活性和可扩展性。

依赖倒置原则

依赖倒置原则核心思想是高层次模块不应该依赖于低层次模块,而是应该通过抽象来依赖于底层模块。具体来说就是要面向接口或者抽象编程,而不是具体实现类编程,这样做可以减少组件间的耦合,提高系统的灵活性和可维护性。
包括一下几个方面:

  • 高层模块不应该依赖低层模块。高层模块和低层模块都应该依赖于抽象,并且抽象不应该依赖于具体实现。
  • 抽象不应该依赖于具体实现。抽象应该是稳定的、保持不变的,并且应该提供定义良好的接口。
  • 具体实现应该依赖于抽象。具体实现应该通过接口或抽象类来实现并向上层提供稳定的服务。
    假设我们有一个汽车销售系统,其中包含了 Salesman 类和 Customer 类,Salesman 类用于销售汽车,而 Customer 类用于购买汽车。下面是代码:
public class Car {
    // 汽车的属性和方法...
}

public class Salesman {
    public void sellCar(Car car) {
        // 销售汽车的逻辑...
    }
}

public class Customer {
    public void browseCars(List<Car> cars) {
        // 浏览汽车的逻辑...
    }
    
    public void buyCar(Car car) {
        // 购买汽车的逻辑...
    }
}

在上面的代码中,Salesman 的 sellCar 方法和 Customer 的 browseCars 和 buyCar 方法都是直接使用了 Car 类作为参数,这就说明了它们依赖了 Car 类。

这样的设计有一个缺点,如果我们要对 Car 类进行修改,那么 Salesman 和 Customer 类的代码也会受到影响,导致系统稳定性下降。因此,在面向对象设计中,依赖倒置原则建议我们避免直接依赖具体实现类,而是应该依赖于抽象,从而降低模块之间的耦合性。

下面是对上面代码进行改进后的示例。我们首先定义了一个名为 CarProvider 的接口,里面有一个 getAllCars 方法用于获取所有汽车的信息。Salesman 类和 Customer 类现在依赖于 CarProvider 接口,而不再直接依赖于 Car 类。

public interface CarProvider {
    List<Car> getAllCars();
}

public class Salesman {
    public void sellCar(CarProvider provider, Car car) {
        // 销售汽车的逻辑...
    }
}

public class Customer {
    public void browseCars(CarProvider provider) {
        List<Car> cars = provider.getAllCars();
        // 浏览汽车的逻辑...
    }
    
    public void buyCar(CarProvider provider, Car car) {
        // 购买汽车的逻辑...
    }
}

通过这种方式,Customer 类现在不再依赖于具体的 Car 类,而是依赖于一个提供汽车信息的 CarProvider 接口。这样,如果我们要修改 Car 类的实现,只需要修改它实现的 CarProvider 接口即可,而不必修改 Customer 类的代码,也不用考虑对 Salesman 类的影响。这种方式就是依赖倒置原则的应用。

接口隔离原则

接口隔离原则指类间的依赖关系应该建立在最小的接口上,它强调客户端不应该依赖于它不需要的接口。也就是说,一个类不应该依赖于它不需要的接口,而应该仅依赖于它需要的接口。这样可以避免出现臃肿、冗余的接口,同时减少代码耦合,提高系统的可维护性和扩展性。
接口隔离原则有一下几个具体的实现要点:

  1. 将大接口拆分成多个小接口,客户端只依赖自己需要的接口。
  2. 接口设计应该基于实际需求,而不是为了简化实现而进行的合并。
  3. 不要让客户端依赖他们不需要的接口。
    假设我们有一个名为 Shape 的接口,它定义了一个计算图形面积的方法,如下所示:
public interface Shape {
    double getArea();
}

现在我们有三种不同的图形:矩形、正方形和圆形。我们可以通过实现 Shape 接口来计算它们的面积,如下所示:

public class Rectangle implements Shape {
    // 矩形的属性和方法...
    @Override
    public double getArea() {
        // 计算矩形面积的逻辑...
    }
}

public class Square implements Shape {
    // 正方形的属性和方法...
    @Override
    public double getArea() {
        // 计算正方形面积的逻辑...
    }
}

public class Circle implements Shape {
    // 圆形的属性和方法...
    @Override
    public double getArea() {
        // 计算圆形面积的逻辑...
    }
}

在这个示例中,我们遵循了接口隔离原则。每个图形类都仅依赖于自己所需的方法,而不是强制性地实现其他方法。

假设我们现在要新增一个三角形类,它计算面积的方式与其他图形不同。如果按照原来的设计,我们需要重新定义 Shape 接口,并让所有类都去实现这个新方法。但是,在遵循接口隔离原则的情况下,我们可以考虑为三角形类定义一个新的接口,如 Triangle,让它只提供计算三角形面积的方法。这样,Shape 接口不需要变化,Triangle 只依赖于自己所需的方法,客户端代码也不用修改,从而避免了对整个系统的影响。

迪米特原则

迪米特原则也叫最小知道原则,它是指一个对象对其他对象保持最少的了解。也就是说一个对象应该尽可能少地依赖其他对象,减少对象间的耦合度,从而提高系统的可维护性和扩展性。
迪米特原则有以下几个实现要点:

  1. 一个对象应该对其他对象保持最少的了解,仅了解自己相关的对象。
  2. 对象之间应该通过接口进行通信,而不是直接依赖于具体的实现类。
  3. 在设计对象方法时,仅传递必要的参数,而不是整个对象。
    假设我们有一个名为 Team 的类,它表示一个团队。Team 类需要获取每个成员的姓名和年龄,并且需要统计出所有成员的平均年龄。我们可以定义成员类 Member 如下所示:
public class Member {
    private String name;
    private int age;

    public String getName() { return name; }

    public void setName(String name) { this.name = name; }

    public int getAge() { return age; }

    public void setAge(int age) { this.age = age; }
}

下面是 Team 类的实现:

public class Team {
    private List<Member> members = new ArrayList<>();

    public void addMember(Member member) { 
        members.add(member); 
    }

    public double getAverageAge() {
        int sum = 0;
        for (Member member : members) {
            sum += member.getAge();
        }
        return (double)sum / members.size();
    }

    public List<Member> getMembers() {
        return members;
    }
}

在上面的代码中,我们遵循了迪米特原则。Team 类仅依赖于 Member 类提供的 getAge 方法,而不用知道 Member 类的其他信息。这样,如果 Member 类发生变化,只要不影响到 getAge 方法,Team 类不需要进行任何修改。

另外,在实现 Team 类的时候,我们只传递了必要的参数,而不是整个 Member 对象。这样做的好处是,可以尽量减少对象之间的耦合度,防止对象之间的信息交互过于复杂,从而降低系统的复杂度,提高系统的可维护性和可扩展性。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值