设计模式- 一、设计原则-2

一、设计原则

5、依赖倒置原则

5.1 简介

依赖倒置原则(Dependency Inversion Principle,简称DIP):它强调高层模块不应该依赖于低层模块的具体实现方式,而是应该依赖于抽象

  • 说人话就是,当我们设计代码时,应该通过抽象来定义模块之间的关系,而不是直接依赖于具体的实现细节。
  • 这样做的好处是,提高了代码的灵活性和可维护性。高层模块不需要知道低层模块的具体实现,只需依赖于抽象接口。这样,当低层模块的实现发生变化时,高层模块不受影响。
  • 另外,依赖倒置原则也鼓励通过依赖注入等方式来实现模块之间的解耦,提高了代码的可测试性和可扩展性。

5.2 例子

举例一:UserController 和 UserService / UserServiceImpl

假设我们正在开发一个用户管理系统,其中包括用户控制器(UserController)和用户服务(UserService)以及其具体实现(UserServiceImpl)。

按照依赖倒置原则的设计,UserController 不应该直接依赖于 UserServiceImpl,而是应该依赖于抽象的 UserService 接口。

public interface UserService {
    void addUser(User user);
    void deleteUser(int userId);
    User getUser(int userId);
    List<User> getAllUsers();
}

public class UserServiceImpl implements UserService {
    // 具体的实现代码
    // ...
}

public class UserController {
    private UserService userService;

    // 通过构造函数进行依赖注入
    public UserController(UserService userService) {
        this.userService = userService;
    }

    // 控制器方法调用用户服务方法
    public void addUser(User user) {
        userService.addUser(user);
    }

    public void deleteUser(int userId) {
        userService.deleteUser(userId);
    }

    public User getUser(int userId) {
        return userService.getUser(userId);
    }

    public List<User> getAllUsers() {
        return userService.getAllUsers();
    }
}

通过依赖倒置原则的设计,UserController 依赖于抽象的 UserService 接口,而不是具体的 UserServiceImpl 实现类。这样做的好处是,当我们需要更换或扩展用户服务的具体实现时,只需创建新的实现类并实现 UserService 接口即可,而不需要修改 UserController 的代码。这样实现了高层模块(UserController)不依赖于低层模块(UserServiceImpl)的具体实现,提高了代码的灵活性和可维护性。

举例二:Tomcat

Tomcat 是一个常用的 Java Web 服务器,它也遵循了依赖倒置原则。Tomcat 作为高层模块,不直接依赖于具体的 Servlet 实现类,而是通过 Servlet 接口与具体的 Servlet 容器进行交互。

这样的设计允许我们在不改变 Tomcat 的代码的情况下,可以使用不同的 Servlet 容器来运行我们的 Web 应用程序。例如,我们可以在 Tomcat 上部署一个基于 Apache Tomcat 的 Web 应用,也可以在 Jetty 上部署一个基于 Eclipse Jetty 的 Web 应用,因为 Tomcat 和 Jetty 都实现了 Servlet 接口。

举例三:Spring 的 IOC 容器

当我们使用 Spring 的 IOC 容器来实现依赖注入时,依赖倒置原则发挥了重要作用。让我们结合之前的例子来说明。

假设我们有一个接口 A 和实现类 AImpl,我们想要将 AImpl 注入到容器中并使用接口 A 来引用它。首先,我们需要在 Spring 的配置文件中将 AImpl 的实例注入到容器中:

<bean id="a" class="com.example.AImpl" />

现在,我们可以在需要使用 A 接口的地方通过依赖注入来引用 AImpl 的实例。例如,在 MyClass 类中添加一个依赖注入的属性 a

public class MyClass {
    private A a;

    public void setA(A a) {
        this.a = a;
    }

    public void doSomething() {
        a.doSomething();
    }
}

通过依赖注入,我们可以让 Spring 的 IOC 容器自动将 AImpl 的实例注入到 MyClass 类中。这样,我们就可以通过调用 a.doSomething() 来使用 AImpl 的功能。

现在,假设我们还需要另外一个实现类来实现接口 A。我们创建一个名为 AnotherAImpl 的类来实现接口 A

public class AnotherAImpl implements A {
    @Override
    public void doSomething() {
        System.out.println("Doing something in AnotherAImpl");
    }
}

然后,我们在 Spring 的配置文件中将 AnotherAImpl 的实例注入到容器中:

<bean id="anotherA" class="com.example.AnotherAImpl" />

现在,你可以在需要使用 A 接口的任何地方通过依赖注入来引用 AnotherAImpl 的实例。在 MyClass 类中添加一个新的依赖注入的属性 anotherA

public class MyClass {
    private A a;
    private A anotherA;

    public void setA(A a) {
        this.a = a;
    }

    public void setAnotherA(A anotherA) {
        this.anotherA = anotherA;
    }

    public void doSomething() {
        a.doSomething();
        anotherA.doSomething();
    }
}

现在,你可以同时使用 AImplAnotherAImpl 的实例来执行不同的操作。

通过依赖倒置原则和 Spring 的 IOC 容器,你可以轻松地切换不同的实现类,只需修改配置文件即可。这种灵活性使你能够根据需求随时替换和扩展实现,而无需修改依赖它们的类的代码。

总结起来,依赖倒置原则与 Spring 的 IOC 容器相辅相成。通过将接口作为抽象的约定,将实现类的选择和创建交给容器管理,我们实现了高层模块和低

层模块之间的解耦和灵活性。无论是使用 AImpl 还是 AnotherAImplMyClass 类只需依赖于接口 A,从而提高了代码的可维护性和可扩展性。

5.3 总结

依赖倒置原则(DIP)是面向对象设计中的一条重要原则。它的核心思想是高层模块不应该依赖于低层模块,而应该依赖于抽象接口或抽象类。这样可以降低模块之间的耦合性,提高代码的灵活性和可维护性。通过依赖注入等方式实现依赖倒置原则可以使代码更易于扩展和修改,同时也能提升代码的可测试性和可复用性。

6、KISS原则

6.1 简介

KISS原则(Keep It Simple, Stupid),它强调保持代码简单易懂的重要性。在编写代码时,应避免过度设计和复杂化,而是以简洁的方式解决问题。KISS原则鼓励我们使用简单直接的方法来实现功能,避免过多的复杂性和不必要的抽象。

  • 说人话就是,写代码的时候要保持简单,不要过度设计和增加复杂性,不要花里胡哨。要选择简洁直接的方法来解决问题,避免不必要的复杂性和抽象。这样做的好处是,代码更易于理解、调试和维护,降低出错的概率,并且提高开发效率。

6.2 例子

当我们设计一个用户管理系统时,假设有以下需求:

  1. 用户可以注册账号。
  2. 用户可以登录账号。
  3. 用户可以查看自己的个人信息。
  4. 用户可以修改个人信息。

以下是一个违反KISS原则的示例代码:

public class UserManagementSystem {
    private UserRepository userRepository;
    private EmailService emailService;
    
    public UserManagementSystem() {
        userRepository = new UserRepository();
        emailService = new EmailService();
    }
    
    public void registerUser(String username, String password) {
        // 一些注册逻辑
        userRepository.saveUser(username, password);
        emailService.sendEmail(username, "Welcome to our system!");
    }
    
    public void loginUser(String username, String password) {
        // 一些登录逻辑
        // ...
    }
    
    public void displayUserInfo(String username) {
        // 一些显示用户信息的逻辑
        // ...
    }
    
    public void updateUserProfile(String username, String newEmail) {
        // 一些更新用户信息的逻辑
        userRepository.updateEmail(username, newEmail);
        emailService.sendEmail(username, "Your profile has been updated.");
    }
}

在上述代码中,UserManagementSystem类承担了太多的责任,既包含了用户管理逻辑,又包含了与用户相关的邮件服务逻辑。这导致类的职责过重,代码复杂度高,并且增加了对UserRepositoryEmailService的直接依赖。

下面是符合KISS原则的重构后的示例代码:

public class UserManagementSystem {
    private UserRepository userRepository;
    private EmailService emailService;
    
    public UserManagementSystem(UserRepository userRepository, EmailService emailService) {
        this.userRepository = userRepository;
        this.emailService = emailService;
    }
    
    public void registerUser(String username, String password) {
        userRepository.saveUser(username, password);
        emailService.sendWelcomeEmail(username);
    }
    
    // 其他方法的实现省略...
}

在重构后的代码中,我们将与用户相关的逻辑拆分成了两个独立的类:UserRepository负责用户数据的持久化,EmailService负责发送邮件。UserManagementSystem类只关注用户管理的核心逻辑,并通过构造函数依赖注入的方式获取UserRepositoryEmailService实例。

通过拆分职责,每个类的责任更加清晰,代码也更加简洁和可维护。同时,减少了类之间的直接依赖关系,提高了代码的灵活性和可测试性。

6.3 总结

KISS原则(Keep It Simple, Stupid)是一种设计原则,强调保持代码简单易懂的重要性。以下是对KISS原则的总结:

  • KISS原则建议在编写代码时避免过度设计和复杂化,以简洁的方式解决问题。
  • 简单的代码更易于理解、调试和维护,降低了引入错误和bug的风险。
  • 通过避免不必要的复杂性,可以提高代码的可读性,使其更容易被团队成员理解和使用。
  • 简单的代码更容易进行扩展和重构,使系统更具灵活性和可维护性。
  • 遵循KISS原则可以减少代码的冗余和复杂度,提高开发效率,并降低项目的成本和风险。

总而言之,KISS原则是一种鼓励简洁和直观代码的设计原则,它强调避免过度复杂化(代码多不是复杂),以简单的方式解决问题。通过遵循KISS原则,我们可以提高代码的可读性、可维护性和可扩展性,从而为项目的成功和可持续发展奠定坚实基础。

7、DRY原则

7.1 简介

DRY原则(Don’t Repeat Yourself),它强调避免重复代码的产生

7.2 例子

在 Java 编程中,我们可以通过以下方法遵循 DRY 原则:

(1)使用方法(functions):当你发现自己在多处重复相同的代码时,可以将其抽取为一个方法,并在需要的地方调用该方法。

public class DryExample {
    public static void main(String[] args) {
        printHello("张三");
        printHello("李四");
    }
    
    public static void printHello(String name) {
        System.out.println("你好," + name + "!");
    }
}

在这个例子中,我们使用 printHello 方法避免了重复的 System.out.println 语句。

(2)使用继承和接口:当多个类具有相似的行为时,可以使用继承和接口来抽象共享的功能,从而减少重复代码。

public abstract class Animal {
    public abstract void makeSound();
    
    public void eat() {
        System.out.println("动物在吃东西");
    }
}

public class Dog extends Animal {
    public void makeSound() {
        System.out.println("汪汪");
    }
}

public class Cat extends Animal {
    public void makeSound() {
        System.out.println("喵喵");
    }
}

在这个例子中,我们使用抽象类 Animal 和继承来避免在 DogCat 类中重复 eat 方法的代码。

(3)重用代码库和框架:使用成熟的代码库和框架可以避免从零开始编写一些通用功能。例如,使用 Java 标准库、Apache Commons 或 Google Guava 等库。

遵循 DRY 原则可以帮助我们编写更高质量的代码,并更容易进行维护和扩展。同时,要注意不要过度优化,以免影响代码的可读性和理解性。

7.3 总结

DRY原则(Don’t Repeat Yourself)是一种软件设计原则,强调避免重复代码的重要性。它鼓励开发人员在编写代码时避免重复的逻辑、功能或信息。

  • 首先,它提高了代码的可维护性和可读性。通过将重复的代码抽取到单独的方法、函数或模块中,我们可以避免在多个地方修改相同的代码,降低了出错的风险,并使代码更易于理解和修改。
  • 其次,DRY原则促进了代码的重用和模块化。通过将通用的逻辑抽象为可复用的组件,我们可以在不同的地方调用它们,避免了重复编写相同的代码。这样可以减少代码量,提高开发效率,并增加系统的灵活性和可扩展性。
  • 同时,DRY原则还有助于降低代码的耦合性。通过将重复的代码抽象为单一的实现,我们可以减少代码之间的依赖关系,使系统的各个部分更加独立和解耦。这样可以提高代码的可测试性,降低修改一个功能对其他部分造成的影响。
  • 需要注意的是,DRY原则并不意味着完全消除重复代码。重复的代码只有在处理相同逻辑和功能时才被认为是违反DRY原则的。在评估重复代码时,我们需要考虑业务差异和上下文变化。如果代码的相似之处只是因为业务上的差异或上下文的变化,那么这种重复可能是合理的。

8、迪米特原则

8.1 简介

迪米特原则(Law of Demeter,简称LoD),也被称为最少知识原则(Principle of Least Knowledge),它强调对象之间的松耦合和信息隐藏。

  • 说人话就是,当我们设计软件时,对象之间的交互应该尽量简单,避免直接访问其他对象的内部细节,而是通过调用公共方法来间接进行通信。

迪米特原则的核心思想是将对象设计为尽可能少地依赖其他对象,只与自己的直接朋友对象进行交互。这样做的好处是:

  1. 减少耦合:对象之间的直接依赖越少,耦合度越低。当一个对象只与少数几个朋友对象进行交互时,修改一个对象的内部结构或实现不会对其他对象产生太大的影响。

  2. 提高灵活性:由于对象之间的关系简单明确,系统更容易进行扩展和修改。当需要修改系统时,我们只需关注与当前对象直接相关的部分,而不需要了解其他对象的内部细节。

  3. 提升可维护性:迪米特原则使得系统中的对象独立性更强,易于单独测试和调试。当一个对象的实现发生变化时,不会对其他对象产生连锁影响,减少了代码的藕合度。

遵循迪米特原则可以帮助我们设计出更加松耦合、可维护和可扩展的软件系统。同时,要注意避免过度设计,遵循适度原则,不要违背其他设计原则或增加不必要的复杂性。

8.2 例子

假设我们有一个订单处理系统,其中包括订单(Order)、用户(User)和库存(Inventory)三个核心概念:

public class Order {
    private User user;
    private Inventory inventory;

    public Order(User user, Inventory inventory) {
        this.user = user;
        this.inventory = inventory;
    }

    public void processOrder() {
        String userName = user.getName();
        int availableQuantity = inventory.getAvailableQuantity();
        // 处理订单逻辑
        // ...
    }
}

public class User {
    private String name;

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

    public String getName() {
        return name;
    }
}

public class Inventory {
    private int availableQuantity;

    public Inventory(int availableQuantity) {
        this.availableQuantity = availableQuantity;
    }

    public int getAvailableQuantity() {
        return availableQuantity;
    }
}

在这个例子中,Order 类直接依赖于 User 和 Inventory 类,通过调用它们的方法来获取用户信息和库存信息。这种直接依赖关系导致了较高的耦合性,当 User 或 Inventory 类发生变化时,需要修改 Order 类的代码。

现在,让我们看看遵循迪米特法则的情况下的代码:

public class Order {
    private OrderService orderService;

    public Order(OrderService orderService) {
        this.orderService = orderService;
    }

    public void processOrder() {
        String userName = orderService.getUserName();
        int availableQuantity = orderService.getAvailableQuantity();
        // 处理订单逻辑
        // ...
    }
}

public interface OrderService {
    String getUserName();
    int getAvailableQuantity();
}

public class UserService implements OrderService {
    private User user;

    public UserService(User user) {
        this.user = user;
    }

    public String getUserName() {
        return user.getName();
    }

    public int getAvailableQuantity() {
        // 调用库存服务获取库存信息
        // ...
        return availableQuantity;
    }
}

public class InventoryService implements OrderService {
    private Inventory inventory;

    public InventoryService(Inventory inventory) {
        this.inventory = inventory;
    }

    public String getUserName() {
        // 调用用户服务获取用户信息
        // ...
        return userName;
    }

    public int getAvailableQuantity() {
        return inventory.getAvailableQuantity();
    }
}

public class User {
    private String name;

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

    public String getName() {
        return name;
    }
}

public class Inventory {
    private int availableQuantity;

    public Inventory(int availableQuantity) {
        this.availableQuantity = availableQuantity;
    }

    public int getAvailableQuantity() {
        return availableQuantity;
    }
}

在这个例子中,我们引入了一个中间层接口 OrderService,并有两个实现类 UserService 和 InventoryService。Order 类通过依赖 OrderService 接口来获取用户信息和库存信息,而不直接依赖于具体的 User 和 Inventory 类。这种间接的依赖关系降低了耦合性,当 User 或 Inventory 类发生变化时,只需修改对应的实现类,而不需要修改 Order 类的代码。

通过遵循迪米特法则,我们实现了类之间的解耦,提高了代码的灵活性和可维护性。中间层的引入使得系统的模块职责更加清晰,提升了代码的可读性和可测试性。

8.3 总结

迪米特法则在设计和编写代码时,强调对象之间的松耦合,通过减少对象之间的直接依赖关系来提高代码的质量。它带来的优势包括降低耦合性、提高代码的灵活性、可维护性、模块化和封装性,同时也有助于代码的可读性和可测试性:

  1. 迪米特法则的核心思想是减少对象之间的直接依赖关系。一个对象应该尽可能少地了解其他对象的细节和内部结构。

  2. 迪米特法则鼓励使用中间层或接口来实现对象之间的通信,而不是直接依赖具体的对象。这样做可以降低耦合性,提高代码的灵活性和可维护性。

  3. 迪米特法则能够促进代码的模块化和封装性。对象只需关注与其密切相关的对象,不需要了解其他对象的具体实现细节。这样可以提高代码的可读性和可理解性。

  4. 遵循迪米特法则可以降低代码的脆弱性。当一个对象发生变化时,只有直接依赖它的对象需要进行相应的修改,而其他无关的对象不会受到影响。

  5. 迪米特法则有助于提高代码的可测试性。由于对象之间的依赖关系更加简化和清晰,测试对象的行为变得更加容易,可以更好地进行单元测试和模块测试。

    public Inventory(int availableQuantity) {
    this.availableQuantity = availableQuantity;
    }

    public int getAvailableQuantity() {
    return availableQuantity;
    }
    }


在这个例子中,我们引入了一个中间层接口 OrderService,并有两个实现类 UserService 和 InventoryService。Order 类通过依赖 OrderService 接口来获取用户信息和库存信息,而不直接依赖于具体的 User 和 Inventory 类。这种间接的依赖关系降低了耦合性,当 User 或 Inventory 类发生变化时,只需修改对应的实现类,而不需要修改 Order 类的代码。

通过遵循迪米特法则,我们实现了类之间的解耦,提高了代码的灵活性和可维护性。中间层的引入使得系统的模块职责更加清晰,提升了代码的可读性和可测试性。



### 8.3 总结

迪米特法则在设计和编写代码时,强调对象之间的松耦合,通过减少对象之间的直接依赖关系来提高代码的质量。它带来的优势包括降低耦合性、提高代码的灵活性、可维护性、模块化和封装性,同时也有助于代码的可读性和可测试性:

1. 迪米特法则的核心思想是减少对象之间的直接依赖关系。一个对象应该尽可能少地了解其他对象的细节和内部结构。

2. 迪米特法则鼓励使用中间层或接口来实现对象之间的通信,而不是直接依赖具体的对象。这样做可以降低耦合性,提高代码的灵活性和可维护性。

3. 迪米特法则能够促进代码的模块化和封装性。对象只需关注与其密切相关的对象,不需要了解其他对象的具体实现细节。这样可以提高代码的可读性和可理解性。

4. 遵循迪米特法则可以降低代码的脆弱性。当一个对象发生变化时,只有直接依赖它的对象需要进行相应的修改,而其他无关的对象不会受到影响。

5. 迪米特法则有助于提高代码的可测试性。由于对象之间的依赖关系更加简化和清晰,测试对象的行为变得更加容易,可以更好地进行单元测试和模块测试。

你可能感兴趣的:(设计模式,设计模式,依赖倒置,迪米特法则,KISS原则,DRY原则)