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

目录

引言

单一职责原则

开闭原则

里氏替换原则 

里氏替换原则的问题由来 

里氏替换的原则 

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

总结:

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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值