Java 设计原则:七种方法帮助你更好地设计软件

本文介绍了Java的七大设计原则,包括单一职责原则、开放封闭原则、里氏替换原则、接口隔离原则、依赖倒转原则、合成复用原则和迪维恩原则。这些原则有助于提升软件的稳定性和可扩展性,降低系统耦合度,提高代码可维护性和测试性。通过实例解析,阐述了每个原则的核心思想和应用,帮助开发者更好地设计和构建软件系统。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一.前言

Java 七大设计原则是:

  1. 单一职责原则(Single Responsibility Principle,简称 SRP):一个类应该仅有一个引起它变化的原因。

  2. 开放封闭原则(Open-Closed Principle,简称 OCP):软件实体(类、模块、函数等)应该是可以扩展的,但是不可修改。

  3. 里氏替换原则(Liskov Substitution Principle,简称 LSP):子类型必须能够替换它们的基类型。

  4. 接口隔离原则(Interface Segregation Principle,简称 ISP):客户端不应该依赖它不需要的接口。

  5. 依赖倒转原则(Dependence Inversion Principle,简称 DIP):高层模块不应该依赖于低层模块,两者都应该依赖于抽象。

  6. 合成复用原则(Composite Reuse Principle,简称 CRP)

  7. 迪维恩原则(Demeter Principle,也称为最少知识原则)

这七个原则可以帮助软件工程师更好地设计软件系统,使得系统更加稳定、可扩展。遵循这些原则可以帮助减少系统中的耦合度,使得系统更加易于维护和扩展。下面我将分别介绍这七大原则,让你的代码变得那么的高雅!

二.七大设计原则

2.1.单一职责原则(Single Responsibility Principle,简称 SRP)

单一职责原则(Single Responsibility Principle,简称 SRP)是指一个类只负责一项职责。这个职责可以是维护一个特定的数据,提供一组相关的操作,或者是封装一组相关的接口。

这个原则的核心思想是:类的职责应该尽量简单,一个类只应该负责一个单独的任务或功能。这样的类通常比较容易设计、维护和理解。

比如,在一个汽车租赁系统中,可能会有一个 Car 类来维护汽车的信息,如品牌、型号、车牌号等。这个 Car 类应该只负责这些信息的维护,而不应该涉及到租赁流程的处理。

在这种情况下,我们可以将租赁流程的处理放到另一个 Rental 类中,这样就实现了单一职责原则。具体的代码实现如下所示:

class Car {
    private String brand;
    private String model;
    private String licensePlate;
    // 省略其他属性和方法
}

class Rental {
    private Car car;
    private Date startDate;
    private Date endDate;
    private Customer customer;
    // 省略其他属性和方法

    public void rentCar() {
        // 处理租车流程
    }

    public void returnCar() {
        // 处理还车流程
    }
}

在这种情况下,Car 类只负责维护汽车的信息,而 Rental 类负责处理租车流程。这样就实现了单一职责原则,使得类的职责尽量简单,更容易设计、维护和理解。

在实际项目开发中,单一职责原则是一个很重要的设计原则。它可以帮助我们将系统划分成若干个简单的类或模块,使得系统的结构更加清晰、可维护。同时,单一职责原则也有助于提高代码的可测试性和可扩展性。

2.2.开放封闭原则(Open-Closed Principle,简称 OCP)

开放封闭原则(Open-Closed Principle,简称 OCP)是指一个软件实体应该对扩展开放,对修改封闭。也就是说,在设计一个软件系统时,应该尽量使得这个系统能够扩展新的功能,而不需要修改现有的代码。

这个原则的核心思想是:在软件开发过程中,对于已经实现的代码,应该尽量避免对其进行修改,而是使用新的代码来扩展系统的功能。这样的设计可以使得系统更加稳定,也更加易于维护和扩展。

举个例子,假设有一个 Order 类,这个类负责处理订单的管理。我们可以使用开放封闭原则来设计这个类,如下所示:

abstract class Order {
    public abstract double getTotalAmount();
}

class NormalOrder extends Order {
    private List<Item> items;
    public NormalOrder(List<Item> items) {
        this.items = items;
    }
    public double getTotalAmount() {
        double amount = 0;
        for (Item item : items) {
            amount += item.getPrice();
        }
        return amount;
    }
}

class DiscountOrder extends Order {
    private List<Item> items;
    private double discount;
    public DiscountOrder(List<Item> items, double discount) {
        this.items = items;
        this.discount = discount;
    }
    public double getTotalAmount() {
        double amount =0;
    for (Item item : items) {
        amount += item.getPrice();
     }
       return amount * (1 - discount);
    }
}

在这种情况下,我们使用了抽象类 Order 来表示订单的管理,并在 Order 中定义了一个抽象方法 getTotalAmount() 用来获取订单的总金额。然后我们实现了两个类 NormalOrder 和 DiscountOrder 分别表示普通订单和折扣订单。 在这种情况下,我们满足了开放封闭原则。如果我们需要增加新的订单类型,只需要定义一个新的子类,并实现 getTotalAmount() 方法即可。而不需要修改 Order 类或者 NormalOrder 和 DiscountOrder 类。这样就使得系统更加稳定,也更加易于维护和扩展。 

2.3.里氏替换原则(Liskov Substitution Principle,简称 LSP)

里氏替换原则(Liskov Substitution Principle,简称 LSP)是指在软件设计中,子类应该能够替换掉它们的基类。这个原则的核心思想是:在使用继承关系来扩展系统的功能时,应该保证子类不会破坏基类的特性和行为。

这个原则的意思是:对于每一个父类,应该都有一个不变的引理,可以完全定义基类的行为。子类可以实现父类的抽象方法,但是不能改变父类的引理。

举个例子,假设有一个 Rectangle 类表示矩形,这个类具有两个属性 length 和 width 分别表示长和宽。我们可以使用里氏替换原则来设计这个类,如下所示:

class Rectangle {
    protected int length;
    protected int width;
    public void setLength(int length) {
        this.length = length;
    }
    public void setWidth(int width) {
        this.width = width;
    }
    public int getArea() {
        return length * width;
    }
}

在这种情况下,Rectangle 类满足了里氏替换原则。我们可以定义一个 Square 类来表示正方形,并继承自 Rectangle 类。

class Square extends Rectangle {
    @Override
    public void setLength(int length) {
        super.setLength(length);
        super.setWidth(length);
    }
    @Override
    public void setWidth(int width) {
        super.setLength(width);
        super.setWidth(width);
    }
}

 也满足了里氏替换原则。这是因为,我们在定义 Square 类的 setLength() 和 setWidth() 方法时,没有改变矩形的长和宽的引理,也就是说,对于任何一个矩形,它的长度和宽度总是相等的。

在实际项目开发中,里氏替换原则是一个很重要的设计原则。它可以帮助我们在使用继承关系扩展系统功能时,保证子类不会破坏基类的特性和行为。同时,里氏替换原则也有助于提高代码的可拓展性。

2.4.接口隔离原则(Interface Segregation Principle,简称 ISP)

接口隔离原则(Interface Segregation Principle,简称 ISP)是一种软件设计原则,它规定了软件系统中的接口应该尽量细化,从而使得每个接口都只包含一个职责。

接口隔离原则的核心思想是:在软件设计中,接口应该尽量细化,从而使得每个接口都只包含一个职责。这样的设计可以使得系统更加稳定,也更加易于维护和扩展。

在 Java 中,接口是一种特殊的类型,它只包含抽象方法的声明。例如,我们可以定义一个接口来表示动物的行为,如下所示:

interface Animal {
    void eat();
    void run();
    void sleep();
}

在这种情况下,Animal 接口包含了三个方法的声明,分别用于表示动物吃东西、跑步和睡觉的行为。

但是,这样的设计并不符合接口隔离原则。因为 Animal 接口包含了太多的职责,也就是说,它包含了太多的方法声明。这样的设计导致了接口的耦合度增加,也使得系统更加难以维护和扩展。

为了避免这种情况,我们可以将 Animal 接口细化为多个更小的接口,从而使得每个接口都只包含一个职责。例如,我们可以将 Animal 接口细化为如下几个接口:

interface Animal {
    void eat();
}

interface Runner {
    void run();
}

interface Sleeper {
    void sleep();
}

 在这种情况下,每个接口都只包含了一个方法声明,从而使得每个接口都只负责一个职责。这样的设计使得系统更加稳定,也更加易于维护和扩展。

总结一下,接口隔离原则是一种软件设计原则,它规定了软件系统中的接口应该尽量细化,从而使得每个接口都只包含一个职责。这样的设计可以使得系统更加稳定,也更加易于维护和扩展。

2.5.依赖倒转原则(Dependence Inversion Principle,简称 DIP)

依赖倒转原则(Dependence Inversion Principle,简称 DIP)是一种软件设计原则,它提倡高层模块不应该依赖于低层模块,两者都应该依赖于抽象。这样可以降低系统的耦合度,使得系统更加稳定、可扩展。

依赖倒转原则的具体内容可以分为以下几点:

  • 高层模块不应该依赖于低层模块,两者都应该依赖于抽象。
  • 抽象不应该依赖于细节,细节应该依赖于抽象。
  • 依赖倒转可以通过接口或抽象类来实现。

依赖倒转原则是设计模式的核心原则之一,它是 SOLID 原则的一部分(SOLID 原则是五个软件设计原则的首字母缩写)。遵循依赖倒转原则可以帮助软件工程师更好地设计软件系统,使得系统更加稳定、可扩展。

问题1:什么是高层模块不应该依赖于低层模块,那我们先来看高层模块依赖于低层模块例子

假设有一个订单系统,其中包含一个 Order 类和一个 Payment 类。

在这种情况下,如果 Order 类直接依赖于 Payment 类,那么就会出现高层模块(Order 类)依赖于低层模块(Payment 类)的情况。

具体的代码实现可能如下所示:

class Order {
    private Payment payment;
    public Order(Payment payment) {
        this.payment = payment;
    }
    public void processOrder() {
        // 使用 Payment 类的方法处理订单
        payment.processPayment();
    }
}

class Payment {
    public void processPayment() {
        // 处理支付
    }
}

在这种情况下,Order 类依赖于 Payment 类,而不是依赖于抽象。这会导致系统的耦合度较高,当需要更换 Payment 类时,会带来一些麻烦。

为了避免这种情况,我们可以使用依赖倒转原则来设计系统,即使用抽象来替代具体的依赖。

具体的代码实现可能如下所示:

interface Payment {
    void processPayment();
}

class Order {
    private Payment payment;
    public Order(Payment payment) {
        this.payment = payment;
    }
    public void processOrder() {
        // 使用 Payment 接口的方法处理订单
        payment.processPayment();
    }
}

class CreditCardPayment implements Payment {
    public void processPayment() {
        // 处理信用卡支付
    }
}

class PayPalPayment implements Payment {
    public void processPayment() {
        // 处理 PayPal 支付
    }
}

class CashPayment implements Payment {
    public void processPayment() {
        // 处理现金支付
    }
}

根据升级改造后的例子,避免了刚开始那种修改类带来的麻烦。

问题2:什么是抽象不应该依赖于细节呢?我们也先来看看一个抽象依赖细节的例子

假设有一个抽象类形状 Shape,它有一个抽象方法 draw()。然后,我们有一个具体的子类 Circle,它继承了 Shape 类并实现了 draw() 方法。

在这种情况下,如果 Shape 类直接依赖于 Circle 类的 draw() 方法,那么就会出现抽象依赖于细节的情况。

具体的代码实现可能如下所示:

abstract class Shape {
    public abstract void draw();
}

class Circle extends Shape {
    public void draw() {
        // 绘制圆形
    }
}

在这种情况下,Shape 类依赖于 Circle 类的 draw() 方法,而不是依赖于抽象。这会导致系统的耦合度较高,当需要更换 Circle 类时,会带来一些麻烦。

为了避免这种情况,我们可以使用依赖倒转原则来设计系统,即使用抽象来替代具体的依赖。

具体的代码实现可能如下所示

interface Shape {
    void draw();
}

class Circle implements Shape {
    public void draw() {
        // 绘制圆形
    }
}

关于依赖倒转的第三个特点:依赖倒转原则是通过接口(参考问题1内容)或者抽象类的方式实现,下面我会列举一个关于抽象类的实现方式(根据问题2进行改造):

abstract class Shape {
    public abstract void draw();
}

class Drawing {
    private Shape shape;
    public Drawing(Shape shape) {
        this.shape = shape;
    }
    public void drawShape() {
        shape.draw();
    }
}

class Circle extends Shape {
    public void draw() {
        // 绘制圆形
  }
}

class Rectangle extends Shape {
public void draw() {
// 绘制矩形
  }
}

在这种情况下,Drawing 类依赖于 Shape 类的抽象,而不是依赖于具体的 Circle 或 Rectangle 类。这样可以降低系统的耦合度,使得系统更加稳定、可扩展。 总之,通过接口或抽象类来实现依赖倒转可以降低系统的耦合度,使得系统更加稳定、可扩展。

2.6.合成复用原则(Composite Reuse Principle,简称 CRP)

合成复用原则(Composite Reuse Principle,简称 CRP)是一种软件设计原则,它规定了软件系统应该尽量使用合成/聚合的方式来重用代码,而不是使用继承的方式。

合成复用原则的核心思想是:在软件设计中,应该尽量使用合成/聚合的方式来重用代码,而不是使用继承的方式。这样的设计可以使得系统更加稳定,也更加易于维护和扩展。

合成/聚合指的是将多个对象组合起来形成一个更大的对象,这样的设计可以使得系统更加灵活。例如,假设我们有一个类 A 表示学生信息,这个类有一个方法 getName() 用来获取学生的姓名。我们可以使用合成/聚合的方式来设计这个类,如下所示:

class Student {
    private String name;
    public Student(String name) {
        this.name = name;
    }
    public String getName() {
        return name;
    }
}

class Classroom {
    private List<Student> students;
    public Classroom(List<Student> students) {
        this.students = students;
    }
    public List<String> getStudentNames() {
        List<String> names = new ArrayList<>();
        for (Student student : students) {
            names.add(student.getName());
        }
        return names;
    }
}

在这种情况下,Classroom 类可以通过将多个 Student 类组合起来来获取所有学生的姓名,而不是使用继承的方式。

与合成复用原则相比,继承的方式在软件设计中会带来一些问题。首先,继承是一种静态的关系,也就是说,在编译期间就已经确定了。如果在继承关系中修改了基类的行为,那么所有依赖于这个基类的子类都会受到影响。这样的设计导致了系统的不稳定,也更加难以维护和扩展。

其次,继承往往会导致类之间的耦合度增加。如果有多个类之间存在继承关系,那么这些类之间就会产生较强的耦合度。这样的设计导致了系统的不稳定,也更加难以维护和扩展。

相反,合成/聚合的方式可以使得系统更加稳定,也更加易于维护和扩展。合成/聚合的方式可以使得系统更加灵活,也更加易于扩展。

总结一下,合成复用原则是一种软件设计原则,它规定了软件系统应该尽量使用合成/聚合的方

2.7.迪维恩原则(Demeter Principle,也称为最少知识原则)

指在软件设计中,一个对象应该对其他对象有最少的了解。也就是说,一个类应该对自己需要而直接的对象有了解,而不应该对不需要的对象有了解。

这个原则的意思是:在软件设计中,应该尽量减少对象之间的交互,只留下最少的接口。这样的设计可以使得系统更加稳定,也更加易于维护和扩展。

举个例子,假设我们有一个类 A 表示学生信息,这个类有一个方法 getName() 用来获取学生的姓名。我们可以使用迪维恩原则来设计这个类,如下所示:

class Student {
    private String name;
    public Student(String name) {
        this.name = name;
    }
    public String getName() {
        return name;
    }
}

class Classroom {
    private Student student;
    public Classroom(Student student) {
        this.student = student;
    }
    public String getStudentName() {
        return student.getName();
    }
}

在这种情况下,Classroom 类只需要与 Student 类有直接的交互,并不需要知道 Student 类的其他细节。这样的设计可以减少对象之间的交互,从而使得系统更加稳定。

如果 Classroom 类直接调用了 Student 类的其他方法,而不是通过 getStudentName() 方法来获取学生的姓名,那么 Classroom 类就会对 Student 类有过多的了解,这样的设计就不符合迪维恩原则。

总结一下,迪维恩原则的核心思想是:一个对象应该对其他对象有最少的了解,从而使得系统更加稳定。这个原则可以帮助我们在软件设计中减少对象之间的交互,从而使得系统更加易于维护和扩展。

三.全文总结

总结一下,设计模式中的7大原则是:单一职责原则、开放封闭原则、里氏替换原则、依赖倒转原则、迪维恩原则、合成复用原则和接口隔离原则。这些原则都是在软件设计中常用的原则,它们可以帮助我们在设计软件系统时避免一些常见的问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值