一、设计原则
(一)单一职责原则(Single - Responsibility Principle,SRP)
- 定义
- 一个类应该只有一个引起它变化的原因。这意味着每个类应该专注于一项特定的职责,不要将多种不同的功能或业务逻辑混合在一个类中。例如,一个 “用户信息管理类” 应该只负责用户信息的操作,如添加用户、删除用户、查询用户信息等,而不应该同时承担订单处理、库存管理等其他职责。
- 优点
- 易于理解和维护:当一个类的职责单一,代码的可读性会大大提高。开发人员可以很容易地理解这个类的功能,并且在需要修改或扩展功能时,只需要关注这个特定的职责相关的代码部分,降低了维护成本。
- 降低耦合度:单一职责的类与其他类的关联通常更简单,减少了因为一个职责的变化而影响到其他不相关部分的可能性,从而降低了系统的耦合度。
- 应用场景
- 在各种软件系统的模块划分和类设计中广泛应用。例如,在企业资源规划(ERP)系统中,“财务管理模块”、“人力资源管理模块”、“供应链管理模块” 等都应该遵循单一职责原则,各自独立负责财务管理、人力资源管理和供应链管理相关的业务逻辑。
(二)开闭原则(Open - Closed Principle,OCP)
- 定义
- 软件实体(如类、模块、函数等)应该对扩展开放,对修改关闭。即当需要添加新功能时,应该通过扩展已有的代码来实现,而不是直接修改现有的稳定代码。例如,在一个图形绘制系统中,已经有绘制圆形和矩形的功能,若要添加绘制三角形的功能,应该通过新增一个绘制三角形的类或者方法来扩展系统,而不是去修改原来绘制圆形和矩形的代码。
- 优点
- 提高可维护性:遵循开闭原则可以减少对已有代码的修改,降低了引入新错误的风险。因为稳定的代码部分不需要频繁改动,系统的稳定性得以保持。
- 增强可扩展性:方便在不破坏原有系统结构的基础上添加新功能,使软件系统能够更好地适应业务需求的变化。
- 应用场景
- 在框架设计和插件式系统开发中尤为重要。例如,在一个游戏开发框架中,游戏开发者可以通过实现框架提供的接口来添加新的游戏角色、道具等功能,而框架的核心代码不需要修改,从而保证了框架的稳定性和扩展性。
(三)里氏替换原则(Liskov Substitution Principle,LSP)
- 定义
- 所有引用基类(父类)的地方必须能透明地使用其子类的对象。也就是说,子类对象能够替换父类对象,并且程序的行为不会发生改变。例如,在一个动物叫声模拟系统中,有一个动物类(Animal),其中有一个 “叫(makeSound)” 的方法。猫类(Cat)和狗类(Dog)继承自动物类,当使用动物类的引用指向猫类或狗类对象时,调用 “makeSound” 方法应该能够正确地发出猫叫或狗叫的声音,而不会出现错误。
- 优点
- 保证继承关系的正确性:确保了继承体系的合理性,使得子类在继承父类的同时,不会破坏父类原有的功能和行为约束。
- 提高代码的可维护性和可复用性:在遵循里氏替换原则的继承体系中,代码的逻辑更加清晰,父类的代码可以被多个子类复用,并且在替换子类时不会引发意外的错误。
- 应用场景
- 在面向对象编程的继承体系设计中非常关键。例如,在一个图形处理库中,各种图形类(如圆形类、矩形类等)继承自一个抽象图形类,当在一些图形处理算法中使用抽象图形类的引用时,应该可以用任何具体的图形子类对象来替换,而算法仍然能够正确执行。
(四)依赖倒置原则(Dependency Inversion Principle,DIP)
- 定义
- 高层模块不应该依赖低层模块,两者都应该依赖其抽象;抽象不应该依赖细节,细节应该依赖抽象。例如,在一个电商系统中,订单处理模块(高层模块)不应该直接依赖于具体的数据库存储模块(低层模块),而是两者都应该依赖于一个抽象的数据访问接口。这样,当数据库存储方式发生变化(如从关系型数据库改为非关系型数据库)时,只需要实现新的符合接口的存储模块,而订单处理模块不需要修改。
- 优点
- 降低耦合度:通过依赖抽象,减少了高层模块和低层模块之间的直接依赖关系,使得系统各模块之间的耦合更加松散,提高了系统的灵活性和可维护性。
- 便于独立开发和测试:各个模块可以基于抽象接口进行独立开发和测试,只要遵循相同的抽象规范,模块之间就可以相互协作,而不需要关注其他模块的具体实现细节。
- 应用场景
- 在分层架构和模块划分中经常使用。比如在企业级应用开发中,业务逻辑层和数据访问层之间通过抽象的数据访问接口进行交互,避免了业务逻辑层直接依赖于具体的数据存储方式,便于对不同的数据存储技术进行替换和升级。
(五)接口隔离原则(Interface Segregation Principle,ISP)
- 定义
- 客户端不应该依赖它不需要的接口;一个类对另一个类的依赖应该建立在最小的接口上。例如,在一个办公软件系统中,有一个打印设备接口,它包含了打印文档、扫描文档、传真文档等方法。但对于一个只需要打印功能的简单打印机设备来说,它不应该被迫实现扫描和传真等不需要的方法。应该将打印接口、扫描接口、传真接口进行分离,让设备类只实现它们需要的接口。
- 优点
- 提高接口的内聚性:使得接口的职责更加单一,每个接口只包含相关的方法,避免了接口的臃肿和混乱。
- 减少实现类的负担:实现类只需要实现自己真正需要的接口方法,降低了实现类的复杂度,提高了代码的可读性和可维护性。
- 应用场景
- 在接口设计和模块之间的交互设计中广泛应用。比如在一个智能家居系统中,智能灯设备只需要实现控制灯光开关、亮度调节等接口,而智能摄像头设备只需要实现视频拍摄、视频传输等接口,避免设备类实现不必要的接口,使系统的接口设计更加合理。
(六)迪米特法则(Law of Demeter,LoD),也叫最少知识原则
- 定义
- 一个对象应该对其他对象有最少的了解,即一个模块或对象尽量减少与其他模块或对象的交互,只和直接的朋友进行交互。例如,在一个学校管理系统中,学生类(Student)不应该直接访问教师类(Teacher)的私人信息(如教师的工资信息),学生类只应该和与自己相关的课程类(Course)、成绩类(Grade)等进行交互。
- 优点
- 降低耦合度:减少了对象之间不必要的关联,使得每个对象的职责更加独立,系统的耦合度降低,提高了系统的可维护性和可扩展性。
- 提高可维护性和可复用性:当一个对象的内部实现发生变化时,由于它与其他对象的交互较少,对其他对象的影响也较小,从而提高了代码的可维护性和可复用性。
- 应用场景
- 在各种复杂系统的对象关系设计中应用。比如在一个社交网络系统中,用户类只应该和自己的好友列表、动态消息等直接相关的模块进行交互,而不应该访问其他用户的隐私设置等无关信息,这样可以使系统的结构更加清晰,易于维护和扩展。
(七)组合 / 聚合复用原则(Composite/Aggregate Reuse Principle,CARP)
- 定义
- 尽量使用组合 / 聚合关系来实现复用,而不是继承关系。组合是指一个对象包含另一个对象作为其成员变量;聚合是指一个对象包含另一个对象的引用。例如,在一个汽车制造系统中,汽车类(Car)可以包含发动机类(Engine)、轮胎类(Tire)等对象作为成员变量(组合关系),当需要复用发动机或轮胎的功能时,通过这种组合关系来实现,而不是让汽车类直接继承发动机类或轮胎类。
- 优点
- 灵活性高:组合 / 聚合关系是一种比较松散的关系,当被包含的对象发生变化时,对包含它的对象的影响相对较小,而且可以方便地在运行时动态地改变组合 / 聚合的对象。
- 降低耦合度:相比于继承,组合 / 聚合关系使得类之间的耦合度更低,因为对象之间的依赖关系更加灵活,有助于提高系统的可维护性和可扩展性。
- 应用场景
- 在软件系统的对象关系设计和代码复用中广泛应用。比如在一个图形绘制系统中,一个复杂图形可以由多个简单图形组合而成,通过组合关系来复用简单图形的绘制功能,而不是通过继承关系,这样可以更加灵活地构建和修改图形对象。
二、设计模式
(一)创建型设计模式
-
单例模式(Singleton Pattern)
- 意图:确保一个类只有一个实例,并提供一个全局访问点来访问这个实例。例如,在一个操作系统中,系统的任务管理器通常是单例的,因为整个系统只需要一个实例来管理任务。
- 实现方式:
- 懒汉式:在第一次被调用时才创建实例。需要考虑线程安全问题,通常使用双重检查锁定(Double - Checked Locking)来实现。例如:
public class Singleton { private static volatile Singleton instance; private Singleton() {} public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); } } } return instance; } }
- 饿汉式:在类加载时就创建实例,这种方式简单直接,但可能会提前占用资源。例如:
public class Singleton { private static final Singleton instance = new Singleton(); private Singleton() {} public static Singleton getInstance() { return instance; } }
- 应用场景:适用于资源管理器(如数据库连接池、线程池)、日志系统等场景。例如,在数据库连接池场景中,只需要一个连接池实例来管理数据库连接,通过单例模式可以有效地控制连接池的创建和访问。
-
工厂模式(Factory Pattern)
- 意图:将对象的创建和使用分离,通过一个工厂类来负责创建对象,而不是在客户端代码中直接使用 “new” 关键字来创建对象。例如,在一个游戏开发中,游戏中的各种角色可以通过角色工厂来创建,根据不同的角色类型参数,工厂返回不同的角色对象。
- 实现方式:
- 简单工厂模式:一个工厂类创建多种产品对象。例如,有一个简单的图形工厂类,可以创建圆形、矩形等不同形状的图形:
class ShapeFactory { public Shape createShape(String shapeType) { if (shapeType.equals("Circle")) { return new Circle(); } else if (shapeType.equals("Rectangle")) { return new Rectangle(); } return null; } }
- 工厂方法模式:每个产品对象有对应的工厂子类来创建。例如,对于圆形和矩形分别有对应的工厂子类来创建:
abstract class ShapeFactory { abstract Shape createShape(); } class CircleFactory extends ShapeFactory { @Override Shape createShape() { return new Circle(); } } class RectangleFactory extends ShapeFactory { @Override Shape createShape() { return new Rectangle(); } }
- 抽象工厂模式:工厂类创建一组相关的产品对象。例如,在一个家具工厂中,可以创建一组相关的家具产品,如桌子和椅子:
interface FurnitureFactory { Table createTable(); Chair createChair(); } class ModernFurnitureFactory implements FurnitureFactory { @Override public Table createTable() { return new ModernTable(); } @Override public Chair createChair() { return new ModernChair(); } }
- 应用场景:适用于对象创建过程复杂(如需要读取配置文件、进行数据库查询等)或者需要根据不同条件创建不同类型对象的场景。例如,在图形绘制系统中,根据用户选择创建不同形状的图形;在软件系统的插件加载过程中,通过工厂模式创建不同类型的插件对象。
-
建造者模式(Builder Pattern)
- 意图:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。例如,在构建一个电脑对象时,电脑有 CPU、内存、硬盘等多个部件,建造者模式可以通过不同的建造者来构建不同配置的电脑。
- 实现方式:
- 通常有一个抽象建造者类定义建造方法,具体建造者类实现这些方法来构建对象的各个部分,还有一个指挥者类来控制建造过程,最后通过建造者得到构建好的对象。例如,构建一个电脑的建造者模式实现:
// 电脑部件类 class CPU { private String model; public CPU(String model) { this.model = model; } public String getModel() { return model; } } class Memory { private int capacity; public Memory(int capacity) { this.capacity = capacity; } public int getCapacity() { return capacity; } } class Computer { private CPU cpu; private Memory memory; public void setCPU(CPU cpu) { this.cpu = cpu; } public void setMemory(Memory memory) { this.memory = memory; } @Override public String toString() { return "Computer [cpu=" + cpu.getModel() + ", memory=" + memory.getCapacity() + "GB]"; } } // 抽象建造者类 abstract class ComputerBuilder { protected Computer computer = new Computer(); public abstract void buildCPU(); public abstract void buildMemory(); public Computer getComputer() { return computer; } } // 具体建造者类 class HighEndComputerBuilder extends ComputerBuilder { @Override public void buildCPU() { computer.setCPU(new CPU("Intel Core i9")); } @Override public void buildMemory() { computer.setMemory(new Memory(32)); } } // 指挥者类 class ComputerDirector { public Computer construct(ComputerBuilder builder) { builder.buildCPU(); builder.buildMemory(); return builder.getComputer(); } }
- 应用场景:适用于创建复杂对象,特别是当对象的构建过程比较复杂,且可能有多种不同的构建方式或配置组合时。如汽车制造过程、房屋建筑过程等,在这些场景中,可以通过不同的建造者来构建不同配置或风格的产品。
(二)结构型设计模式
- 代理模式(Proxy Pattern)
- 意图:为其他对象提供一种代理以控制对这个对象的访问。例如,在网络访问控制中,代理服务器可以作为真实服务器的代理,客户端请求先到达代理服务器,代理服务器可以进行权限验证、缓存等操作后再将请求转发给真实服务器。
- 实现方式:
- 静态代理:在编译时期就确定代理类和被代理类的关系。例如,一个简单的接口和其实现类以及代理类的示例:
interface Subject { void request(); } class RealSubject implements Subject { @Override public void request() { System.out.println("RealSubject is handling the request."); } } class ProxySubject implements Subject { private RealSubject realSubject; public ProxySubject() { this.realSubject = new RealSubject(); } @Override public void request() { System.out.println("ProxySubject is pre - handling the request."); realSubject.request(); System.out.println("ProxySubject is post - handling the request."); } }
- 动态代理:在运行时期通过反射机制动态生成代理类。在 Java 中可以使用
java.lang.reflect.Proxy
类来实现。例如:
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; interface Subject { void request(); } class RealSubject implements Subject { @Override public void request() { System.out.println("RealSubject is handling the request."); } } class DynamicProxyHandler implements InvocationHandler { private Object realSubject; public DynamicProxyHandler(Object realSubject) { this.realSubject = realSubject; } @Override