【设计模式精解】六大设计原则

发布于:2025-07-22 ⋅ 阅读:(21) ⋅ 点赞:(0)

目录

引言

单一职责原则

开闭原则

里氏替换原则 

里氏替换原则的问题由来 

里氏替换的原则 

Case 

Bad Impl(违反原则)

Better Impl (遵守原则)

迪米特法则 

Case 

Bad Impl(违反原则)

Better Impl (遵守原则)

接口隔离原则

优点

缺点 

适合场景 

Case 

Bad Impl(违反原则)

Better Impl (遵守原则)

依赖倒置原则

字面拆解:四个字的含义 

Case 

Bad Impl(违反原则)

Better Impl (遵守原则)

总结:


引言

要知道设计模式就是软件工程的方法经验的总结,也是可以认为是过去一段时间软件工程的一个最佳实践,要理解,不要死记硬背。掌握这些方法后,可以让你的程序获得以下好处:

  • 代码重用性(相同功能的代码,不用多次编写)
  • 可读性(编程规范性,便于其他程序员的阅读和理解)
  • 可扩展性(当需要增加新的功能时,非常的方便,称为可维护)
  • 可靠性(当我们增加新的功能后,对原来的功能没有影响)
  • 使程序呈现高内聚,低耦合的特性。

当然设计是有限度的,不能无限的考虑未来的变更情况,否则就会陷入设计的泥潭而无法自拔。方法是死的,人是活,用的时候一定灵活运用,才能发挥它的作用。设计模式中六大设计原则,这些原则可以认为是设计模式的灵昏。下面先对六大设计原则简单梳理一下,然后再详细分析每一个设计原则。

单一职责原则

我们现在的智能手机往往包含许多功能,拍照、打电话、打游戏等等。假设我们此时要拍一个美景,我们会发现手机拍下来的不如摄像机拍下来的清晰,因为摄像机就是专门干拍照这事儿的。
所以大多数时候,一件产品简单一些,职责单一一些,或许是更好的选择。
单一职责原则:就一个类而言,应该只有一个引起它变化的原因。
如果一个类承担的职责过多,就等于把职责都耦合在了一起,这样一个职责的变化可能削弱或者抑制这个类完成其他职责的能力。
就比如你要开发一个俄罗斯方块的游戏,它分为手机版和电脑版。游戏逻辑和界面是不互相影响的,游戏逻辑在哪个界面都是可以被复用的,所以将界面的变化和游戏逻辑分离开,单一职责,有利于界面的改动。

开闭原则

拿香港回归举例,如果当时一口咬定回归之后要改为社会主义制度,那回归的难度可谓困难重重,那既然不能改变原有的制度,我们可以考虑扩展,采用一国两制,是一种伟大的发明。
在设计模式中,这种不能修改但可以扩展的思想就是开闭原则。
开闭原则:软件实体(类、模块、函数等)应该可以扩展,但是不可修改 

里氏替换原则 

继承必须确保父类所拥有的性质在子类中仍然成立。 

里氏替换原则的问题由来 

有一功能 P1,由类 A 完成。
现需要将功能 P1 进行扩展,扩展后的功能为 P,其中P由原有功能 P1 与新功能 P2 组成。
新功能 P 由类 A 的子类 B 来完成,则子类 B 在完成新功能 P2 的同时,有可能会导致原有功能 P1 发生故障。 

里氏替换的原则 

如果S是T的子类型,那么所有T类型的对象都可以在不破坏程序的情况下被S类型的对象替换。
简单来说: 子类可以扩展父类的功能,但不能改变父类原有的功能。 也就是说,当子类继承父类时,除了添加新的方法完成新增功能外,尽量不要重写父类的方法。

四点含义:

  • 子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法
  • 子类可以增加自己特有的方法
  • 当子类的方法重载父类的方法时,方法的前置条件(即方法的输入参数)要比父类更宽松
  • 当子类的方法实现父类的方法(重写、重载或者实现抽象方法)时,方法的后置条件(即方法的输出或者返回值)要比父类的方法更严格或与父类的方法相等 

Case 

用个银行卡的场景来描述一下:
储蓄卡、信用卡都可以消费,但信用卡不宜提现,否则产生高额利息。两个类

  • 储蓄卡类
  • 信用卡类

Bad Impl(违反原则)

【储蓄卡】

public class CashCard {

    private Logger logger = LoggerFactory.getLogger(CashCard.class);

    /**
     * 提现
     *
     * @param orderId 单号
     * @param amount  金额
     * @return 状态码 0000成功、0001失败、0002重复
     */
    public String withdrawal(String orderId, BigDecimal amount) {
        // 模拟支付成功
        logger.info("提现成功,单号:{} 金额:{}", orderId, amount);
        return "0000";
    }

    /**
     * 储蓄
     *
     * @param orderId 单号
     * @param amount  金额
     */
    public String recharge(String orderId, BigDecimal amount) {
        // 模拟充值成功
        logger.info("储蓄成功,单号:{} 金额:{}", orderId, amount);
        return "0000";
    }

    /**
     * 交易流水查询
     * @return 交易流水
     */
    public List<String> tradeFlow() {
        logger.info("交易流水查询成功");
        List<String> tradeList = new ArrayList<String>();
        tradeList.add("100001,100.00");
        tradeList.add("100001,80.00");
        tradeList.add("100001,76.50");
        tradeList.add("100001,126.00");
        return tradeList;
    }

}

在储蓄卡中包括三个方法: 提现、储蓄、交易流水查询, 这都是模拟储蓄卡的基本功能。

接下来我们通过继承储蓄卡的功能实现信用卡的服务。

【信用卡】 

public class CreditCard extends CashCard {

    private Logger logger = LoggerFactory.getLogger(CashCard.class);

    @Override
    public String withdrawal(String orderId, BigDecimal amount) {
        // 校验
        if (amount.compareTo(new BigDecimal(1000)) >= 0){
            logger.info("贷款金额校验(限额1000元),单号:{} 金额:{}", orderId, amount);
            return "0001";
        }
        // 模拟生成贷款单
        logger.info("生成贷款单,单号:{} 金额:{}", orderId, amount);
        // 模拟支付成功
        logger.info("贷款成功,单号:{} 金额:{}", orderId, amount);
        return "0000";
    }

    @Override
    public String recharge(String orderId, BigDecimal amount) {
        // 模拟生成还款单
        logger.info("生成还款单,单号:{} 金额:{}", orderId, amount);
        // 模拟还款成功
        logger.info("还款成功,单号:{} 金额:{}", orderId, amount);
        return "0000";
    }

    @Override
    public List<String> tradeFlow() {
        return super.tradeFlow();
    }

 

信用卡的功能实现是在继承了储蓄卡后,进行方法的重写: 支付、还款。 其实交易流水可以复用,也可以不用重写这个。

那看看单元测试是如何使用的?

public class Test {

    private Logger logger = LoggerFactory.getLogger(ApiTest.class);

    @Test
    public void test_CashCard() {
        CashCard cashCard = new CashCard();
        // 提现
        cashCard.withdrawal("100001", new BigDecimal(100));
        // 储蓄
        cashCard.recharge("100001", new BigDecimal(100));
        // 交易流水
        List<String> tradeFlow = cashCard.tradeFlow();
        logger.info("查询交易流水,{}", JSON.toJSONString(tradeFlow));
    }

    @Test
    public void test_CreditCard() {
        CreditCard creditCard = new CreditCard();
        // 支付
        creditCard.withdrawal("100001", new BigDecimal(100));
        // 还款
        creditCard.recharge("100001", new BigDecimal(100));
        // 交易流水
        List<String> tradeFlow = creditCard.tradeFlow();
        logger.info("查询交易流水,{}", JSON.toJSONString(tradeFlow));
    }

}

这种继承父类方式的优点是复用了父类的核心逻辑功能, 但是也破坏了原有的方法。 此时继承父类实现的信用卡的类并不满足里氏替换的原则。也就是说,此时的子类不能承担原父类的功能,直接给储蓄卡使用。 

Better Impl (遵守原则)

信用卡和储蓄卡在功能上有些许类似,在实际开发的过程中也有很多共同的可服用的属性及逻辑。
实现这样的类的最好的方式就是提取出一个抽象类 , 由抽象类定义所有卡的共同核心属性、逻辑, 把卡的支付和还款等动作抽象成正向和逆向操作。 

抽象银行卡类 

public abstract class BankCard {

    private Logger logger = LoggerFactory.getLogger(BankCard.class);

    private String cardNo;   // 卡号
    private String cardDate; // 开卡时间

    public BankCard(String cardNo, String cardDate) {
        this.cardNo = cardNo;
        this.cardDate = cardDate;
    }

    abstract boolean rule(BigDecimal amount);

    // 正向入账,+ 钱
    public String positive(String orderId, BigDecimal amount) {
        // 入款成功,存款、还款
        logger.info("卡号{} 入款成功,单号:{} 金额:{}", cardNo, orderId, amount);
        return "0000";
    }

    // 逆向入账,- 钱
    public String negative(String orderId, BigDecimal amount) {
        // 入款成功,存款、还款
        logger.info("卡号{} 出款成功,单号:{} 金额:{}", cardNo, orderId, amount);
        return "0000";
    }

    /**
     * 交易流水查询
     *
     * @return 交易流水
     */
    public List<String> tradeFlow() {
        logger.info("交易流水查询成功");
        List<String> tradeList = new ArrayList<String>();
        tradeList.add("100001,100.00");
        tradeList.add("100001,80.00");
        tradeList.add("100001,76.50");
        tradeList.add("100001,126.00");
        return tradeList;
    }

    public String getCardNo() {
        return cardNo;
    }

    public String getCardDate() {
        return cardDate;
    }
}

抽象类中提供了卡的基本属性(卡号、开卡时间)及 核心方法。  

 

正向入账: 加钱 逆向入账: 减钱。
接下来我们继承这个抽象类,实现储蓄卡的功能逻辑 

储蓄卡实现类 

public class CashCard extends BankCard {

    private Logger logger = LoggerFactory.getLogger(CashCard.class);

    public CashCard(String cardNo, String cardDate) {
        super(cardNo, cardDate);
    }

    boolean rule(BigDecimal amount) {
        return true;
    }

    /**
     * 提现
     *
     * @param orderId 单号
     * @param amount  金额
     * @return 状态码 0000成功、0001失败、0002重复
     */
    public String withdrawal(String orderId, BigDecimal amount) {
        // 模拟支付成功
        logger.info("提现成功,单号:{} 金额:{}", orderId, amount);
        return super.negative(orderId, amount);
    }

    /**
     * 储蓄
     *
     * @param orderId 单号
     * @param amount  金额
     */
    public String recharge(String orderId, BigDecimal amount) {
        // 模拟充值成功
        logger.info("储蓄成功,单号:{} 金额:{}", orderId, amount);
        return super.positive(orderId, amount);
    }

    /**
     * 风险校验
     *
     * @param cardNo  卡号
     * @param orderId 单号
     * @param amount  金额
     * @return 状态
     */
    public boolean checkRisk(String cardNo, String orderId, BigDecimal amount) {
        // 模拟风控校验
        logger.info("风控校验,卡号:{} 单号:{} 金额:{}", cardNo, orderId, amount);
        return true;
    }

}

储蓄卡类继承抽象父类BankCard ,实现了核心的功能包括规则过滤rule、提现、储蓄 (super.xx), 以及新增的扩展方法:风险校控checkRisk.

这样的实现方式基本满足里氏替换的基本原则:既实现抽象类的抽象方法,又没有破坏父类中的原有方法。

接下来的信用卡类,既可以继承抽象父类,也可以继承储蓄卡类, 但无论那种实现方式,都需要遵从里氏替换原则,不可以破坏父类原有的方法。 

信用卡实现类

public class CreditCard extends CashCard {

    private Logger logger = LoggerFactory.getLogger(CreditCard.class);

    public CreditCard(String cardNo, String cardDate) {
        super(cardNo, cardDate);
    }

    boolean rule2(BigDecimal amount) {
        return amount.compareTo(new BigDecimal(1000)) <= 0;
    }

    /**
     * 提现,信用卡贷款
     *
     * @param orderId 单号
     * @param amount  金额
     * @return 状态码
     */
    public String loan(String orderId, BigDecimal amount) {
        boolean rule = rule2(amount);
        if (!rule) {
            logger.info("生成贷款单失败,金额超限。单号:{} 金额:{}", orderId, amount);
            return "0001";
        }
        // 模拟生成贷款单
        logger.info("生成贷款单,单号:{} 金额:{}", orderId, amount);
        // 模拟支付成功
        logger.info("贷款成功,单号:{} 金额:{}", orderId, amount);
        return super.negative(orderId, amount);

    }

    /**
     * 还款,信用卡还款
     *
     * @param orderId 单号
     * @param amount  金额
     * @return 状态码
     */
    public String repayment(String orderId, BigDecimal amount) {
        // 模拟生成还款单
        logger.info("生成还款单,单号:{} 金额:{}", orderId, amount);
        // 模拟还款成功
        logger.info("还款成功,单号:{} 金额:{}", orderId, amount);
        return super.positive(orderId, amount);
    }

}

信用卡类在继承父类后,使用了公共的属性,即卡号、开卡时间, 同时新增了符合信用卡的新方法: loan、repayment, 并且在两个方法中都使用了抽象类的核心功能。

另外,信用卡中新增了自己的规则rule2 , 并没有破坏储蓄卡中的校验方法rule .

以上的实现方式都遵循了里氏替换原则下完成的,即信用卡类(子类)可以随时替代储蓄卡类(父类) 

单元测试  

【测试储蓄卡】 

    @Test
    public void test_bankCard() {
        logger.info("里氏替换前,CashCard类:");
        CashCard bankCard = new CashCard("123456", "2023-01-01");
        // 提现
        bankCard.withdrawal("100001", new BigDecimal(100));
        // 储蓄
        bankCard.recharge("100001", new BigDecimal(100));
    }

 

【测试信用卡】 

     @Test
    public void test_CreditCard(){
        CreditCard creditCard = new CreditCard("123456", "2023-01-01");
        // 支付,贷款
        creditCard.loan("100001", new BigDecimal(100));
        // 还款
        creditCard.repayment("100001", new BigDecimal(100));
    }

 

【测试信用卡替换储蓄卡】 

    @Test
    public void test_bankCard() {
        logger.info("里氏替换后,CreditCard类:");
        CashCard creditCard = new CreditCard("123456", "2023-01-01");
        // 提现
        creditCard.withdrawal("100001", new BigDecimal(1000000));
        // 储蓄
        creditCard.recharge("100001", new BigDecimal(100));
    }

可以看到,储蓄卡功能正常, 继承储蓄卡实现的信用卡的功能也正常。
同时,原有储蓄卡的功能可以由信用卡类支持

总结起来,里氏替换原则强调了继承关系的正确使用,要求子类能够完全替代父类,而不破坏程序的正确性。遵循该原则可以提高代码的重用性、灵活性和可靠性 

迪米特法则 

迪米特法则:意义在于降低类之间的耦合。由于每个对象尽量减少对其他对象的了解,因此,很容易使得系统的功能模块功能独立,相互之间不存在(或很少有)依赖关系。 

Case 

Bad Impl(违反原则)

// 员工类
public class Employee {
    private String name;
    public Employee(String name) {
        this.name = name;
    }
    public String getName() {
        return name;
    }
}
// 部门类
public class Department {
    private List<Employee> employees = new ArrayList<>();
    private String name;

    public Department(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
    public void addEmployee(Employee employee) {
        employees.add(employee);
    }
    public List<Employee> getEmployees() {
        // 暴露内部细节
        return employees;
    }
}
// 公司类 - 直接访问了Department的内部结构(Employee)
public class Company {
    private List<Department> departments = new ArrayList<>();
    public void addDepartment(Department department) {
        departments.add(department);
    }
    
    // 违反迪米特法则:直接操作了间接关联的Employee对象
    public void printAllEmployees() {
        for (Department department : departments) {
            System.out.print(department.getName() + "部门员工列表:");
            // 直接访问部门内部的员工列表
            for (Employee employee : department.getEmployees()) { 
                System.out.print(employee.getName() + " ");
            }
            System.out.println();
        }
    }
}

测试程序

public class Test {
    public static void main(String[] args) {
        // 创建员工
        Employee zhang = new Employee("张三");
        Employee li = new Employee("李四");
        Employee wang = new Employee("王五");
        // 创建部门
        Department department = new Department("技术部");
        // 部门添加员工
        department.addEmployee(zhang);
        department.addEmployee(li);
        department.addEmployee(wang);

        Company company = new Company();
        company.addDepartment(department);
        // 公司查询所有部门员工
        company.printAllEmployees();
    }
}

 

问题:Company 直接访问了 Department 的内部结构(Employee 列表),这导致:

  1. 高层类依赖底层类实现细节
  2. 修改 Department 内部结构时会影响 Company
  3. 耦合度过高 

Better Impl (遵守原则)

// 员工类(不变)
class Employee {
    private String name;
    public Employee(String name) { this.name = name; }
    public String getName() { return name; }
}

// 部门类 - 封装对Employee的操作
public class Department {
    private List<Employee> employees = new ArrayList<>();
    private String name;

    public Department(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
    public void addEmployee(Employee employee) {
        employees.add(employee);
    }
    public void printDepartmentEmployees() {
        for (Employee e : employees) {
            System.out.print(e.getName() + " ");
        }
    }
}
// 公司类 - 只与直接朋友Department交互
public class Company {
    private List<Department> departments = new ArrayList<>();
    public void addDepartment(Department department) {
        departments.add(department);
    }

    // 遵守迪米特法则:仅调用直接关联对象的方法
    public void printAllEmployees() {
        for (Department d : departments) {
            // 委托给Department完成操作
            System.out.print(d.getName() + "部门员工列表:");
            d.printDepartmentEmployees();
            System.out.println();
        }
    }
}

测试程序 

public class Test {
    public static void main(String[] args) {
        // 创建员工
        Employee zhang = new Employee("张三");
        Employee li = new Employee("李四");
        Employee wang = new Employee("王五");
        // 创建部门
        Department department = new Department("技术部");
        // 部门添加员工
        department.addEmployee(zhang);
        department.addEmployee(li);
        department.addEmployee(wang);

        Company company = new Company();
        company.addDepartment(department);
        // 公司查询所有部门员工
        company.printAllEmployees();
    }
}

 

改进点:

  1. Company 只与直接朋友 Department 交互
  2. Department 封装了内部操作逻辑
  3. 底层实现变化时(如改用Set存储员工),Company 不受影响 

接口隔离原则

要求程序员尽量将臃肿庞大的接口拆分为更小和更具体的接口,让接口中只包含客户感兴趣的方法。
假设你是一名餐厅的服务员,你负责为客人提供服务。根据接口隔离原则,你应该将服务拆分为多个小功能,例如点菜、上菜、结账等。这样,当客人只需要点菜时,你只提供点菜的服务;而当客人需要结账时,你则提供结账的服务。通过拆分服务功能,你可以根据客人的需求提供最小集合的服务,避免不必要的依赖和冗余。 

优点

  1. 减少类之间的耦合:拆分接口可以减少类对接口的依赖,降低耦合度。
  2. 提高代码的可读性和可维护性:接口精简明确,使得代码更加清晰、易读、易于维护。 

缺点 

  1. 会增加接口的数量:拆分接口会增加接口的数量,可能导致接口过多的情况,需要权衡接口的设计。
  2. 可能引入接口的重复定义:当多个类需要相同的接口功能时,可能需要重复定义接口,增加了代码冗余。 

适合场景 

  1. 当一个接口定义过大,包含了多个不相关或不常用的方法时,可以考虑将其拆分为多个小接口
  2. 当一个类依赖的接口中包含了它不需要的方法时,可以通过接口隔离原则将接口拆分,使得类只依赖于自己所需的最小接口。 

Case 

Bad Impl(违反原则)

// 饭店接口
public interface RestaurantService {
    // 点菜
    void takeOrder(String dish);
    // 上菜
    void serveFood(String dish);
    // 结账
    void processPayment(double amount);
    // 清理
    void cleanTable();
    // 处理投诉
    void handleComplaint();
}

// 服务员
// 服务员必须实现所有方法,即使有些服务不是由服务员提供的
public class Waiter implements RestaurantService{
    private String name;

    public Waiter(String name) {
        this.name = name;
    }

    @Override
    public void takeOrder(String dish) {
        System.out.println(name + " 记录点菜: " + dish);
    }

    @Override
    public void serveFood(String dish) {
        System.out.println(name + " 上菜: " + dish);
    }

    @Override
    public void processPayment(double amount) {
        System.out.println(name + " 收银: ¥" + amount);
    }

    @Override
    public void cleanTable() {
        // 服务员不应该负责清洁桌子
        System.out.println(name + " 不情愿地清洁桌子...");
    }

    @Override
    public void handleComplaint() {
        System.out.println(name + " 处理投诉");
    }
}

测试程序 

public class Test {
    public static void main(String[] args) {
        Waiter waiter = new Waiter("小王");
        waiter.takeOrder("西红柿鸡蛋面");
        waiter.serveFood("西红柿鸡蛋面");
        waiter.processPayment(10);
        waiter.cleanTable();
        waiter.handleComplaint();
    }
}

测试结果如图

image.png

Better Impl (遵守原则)

interface OrderService {
    void takeOrder(String dish);
}

interface FoodService {
    void serveFood(String dish);
}

interface PaymentService {
    void processPayment(double amount);
}

interface CleaningService {
    void cleanTable();
}

interface ComplaintService {
    void handleComplaint();
}

// 服务员专注于点菜、上菜和处理投诉
class ProfessionalWaiter implements OrderService, FoodService, ComplaintService {
    private String name;
    
    public ProfessionalWaiter(String name) {
        this.name = name;
    }
    
    @Override
    public void takeOrder(String dish) {
        System.out.println(name + " 专业记录: " + dish);
    }
    
    @Override
    public void serveFood(String dish) {
        System.out.println(name + " 优雅地上菜: " + dish);
    }
    
    @Override
    public void handleComplaint() {
        System.out.println(name + " 礼貌地处理投诉");
    }
}

// 清洁工负责清洁工作
class Cleaner implements CleaningService {
    private String name;
    
    public Cleaner(String name) {
        this.name = name;
    }
    
    @Override
    public void cleanTable() {
        System.out.println(name + " 高效地清洁桌子");
    }
}

// 收银员负责结账
class Cashier implements PaymentService {
    private String name;
    
    public Cashier(String name) {
        this.name = name;
    }
    
    @Override
    public void processPayment(double amount) {
        System.out.println(name + " 专业收银: ¥" + amount);
    }
}
// 餐厅管理类
public class Restaurant {
    private List<OrderService> orderServices = new ArrayList<>();
    private List<FoodService> foodServices = new ArrayList<>();
    private List<PaymentService> paymentServices = new ArrayList<>();
    private List<CleaningService> cleaningServices = new ArrayList<>();
    private List<ComplaintService> complaintServices = new ArrayList<>();
    public void addOrderService(OrderService service) {
        orderServices.add(service);
    }

    public void addFoodService(FoodService service) {
        foodServices.add(service);
    }

    public void addPaymentService(PaymentService service) {
        paymentServices.add(service);
    }

    public void addCleaningService(CleaningService service) {
        cleaningServices.add(service);
    }

    public void addComplaintService(ComplaintService service) {
        complaintServices.add(service);
    }
    public void takeOrder(String dish) {
        if (!orderServices.isEmpty()) {
            orderServices.get(0).takeOrder(dish);
        }
    }

    public void serveFood(String dish) {
        if (!foodServices.isEmpty()) {
            foodServices.get(0).serveFood(dish);
        }
    }

    public void processPayment(double amount) {
        if (!paymentServices.isEmpty()) {
            paymentServices.get(0).processPayment(amount);
        }
    }

    public void cleanTable() {
        if (!cleaningServices.isEmpty()) {
            cleaningServices.get(0).cleanTable();
        }
    }

    public void handleComplaint() {
        if (!complaintServices.isEmpty()) {
            complaintServices.get(0).handleComplaint();
        }
    }
}

测试程序 

public class Test {
    public static void main(String[] args) {
        ProfessionalWaiter professionalWaiter = new ProfessionalWaiter("李四");
        Cleaner cleaner = new Cleaner("王阿姨");
        Cashier cashier = new Cashier("赵会计");

        // 创建餐厅并添加服务
        Restaurant restaurant = new Restaurant();
        restaurant.addOrderService(professionalWaiter);
        restaurant.addFoodService(professionalWaiter);
        restaurant.addPaymentService(cashier);
        restaurant.addCleaningService(cleaner);
        restaurant.addComplaintService(professionalWaiter);

        // 客人体验服务
        restaurant.takeOrder("西红柿鸡蛋面");
        restaurant.serveFood("西红柿鸡蛋面");
        restaurant.processPayment(10.0);
        restaurant.cleanTable();
        restaurant.handleComplaint();
    }
}

测试结果如图

image.png

依赖倒置原则

  1. 高层模块不应该依赖低层模块,二者都应该依赖抽象
  2. 抽象不应该依赖细节,细节应该依赖抽象

字面拆解:四个字的含义 

依赖:

  • 指代码中模块/类之间的使用关系(如A类调用B类的方法)
  • 例如:Service 依赖 Database 读写数据

倒置:

  • 把传统的依赖方向"反转"过来
  • 类比:
    • 传统:高层模块→直接依赖→底层模块(如Service→MySQL)
    • 倒置后:高层模块→依赖抽象←底层模块实现 

Case 

Bad Impl(违反原则)

// 低层模块 - 具体实现
class MySQLDatabase {
    public void saveData(String data) {
        System.out.println("使用MySQL保存数据: " + data);
    }
}

// 高层模块 - 直接依赖低层模块
class DataService {
    private MySQLDatabase database;
    
    public DataService() {
        this.database = new MySQLDatabase(); // 直接依赖具体实现
    }
    
    public void save(String data) {
        database.saveData(data);
    }
}

// 使用
public class BadExample {
    public static void main(String[] args) {
        DataService service = new DataService();
        service.save("测试数据");
    }
}

 测试程序

public class Test {
    public static void main(String[] args) {
        DataService dataService = new DataService();
        dataService.saveData("hello world");
    }
}

结果图

image.png


问题:如果要改用Oracle数据库,必须修改DataService类。 

Better Impl (遵守原则)

// 抽象接口 - 抽象不应该依赖细节
interface Database {
    void save(String data);
}

// 低层模块 - 细节依赖抽象
class MySQLDatabase implements Database {
    @Override
    public void save(String data) {
        System.out.println("使用MySQL保存数据: " + data);
    }
}

class OracleDatabase implements Database {
    @Override
    public void save(String data) {
        System.out.println("使用Oracle保存数据: " + data);
    }
}

// 高层模块 - 依赖抽象
class DataService {
    private Database database; // 依赖抽象
    
    // 依赖注入(构造函数注入)
    public DataService(Database database) {
        this.database = database;
    }
    
    public void save(String data) {
        database.save(data);
    }
}

// 使用
public class GoodExample {
    public static void main(String[] args) {
        // 可以灵活切换数据库实现
        Database mysql = new MySQLDatabase();
        DataService service1 = new DataService(mysql);
        service1.save("MySQL数据");
        
        Database oracle = new OracleDatabase();
        DataService service2 = new DataService(oracle);
        service2.save("Oracle数据");
    }
}

测试程序 

public class Test {
    public static void main(String[] args) {
        // 可以灵活切换数据库实现
        Database mysql = new MySQLDatabase();
        DataService dataService = new DataService(mysql);
        dataService.saveData("MySQL数据");

        Database oracle = new OracleDatabase();
        DataService dataService2 = new DataService(oracle);
        dataService2.saveData("Oracle数据");
    }
}

结果图

image.png

总结:

以上内容就是我对六大设计原则的重新理解,当然设计原则有人理解是六种,也有理解是七种的。不管理解是几种,设计模式和设计原则属于方法论的内容,是要帮助我们解决具体的业务问题的,当然需要具体问题具体对待。

如果我的内容对你有帮助,请辛苦动动您的手指为我点赞,评论,收藏。感谢大家!!


网站公告

今日签到

点亮在社区的每一天
去签到