设计模式六大原则:构建高质量代码的基石

282 阅读6分钟

设计模式六大原则:构建高质量代码的基石

设计模式六大原则就像“做菜六步法”—— 专菜专做、老锅新菜、子承父业、各用各筷、调料抽象、不看后厨,让代码炒出一盘好菜!


一、核心原则:单一职责(SRP)与开闭原则(OCP)

所有设计原则的基石是单一职责原则(Single Responsibility Principle)开闭原则(Open-Closed Principle) ,它们定义了类和模块应如何组织。

1. 单一职责原则(SRP):职责边界的划定

原则: 一个类或模块只应有一个职责,或者说,只应有一个引起它变化的原因。就像厨师只管炒菜,不负责洗碗,这样当洗碗流程改变时,炒菜的逻辑不会受到影响。

源码实例: Java的核心类库很好地体现了这一点。例如,java.lang.String类的唯一职责就是处理字符串的不可变序列,它提供了 substring()replace() 等方法,但不会包含 saveToFile()sendEmail() 这样无关的功能。这使得 String 类高度内聚、易于理解和复用。

违反SRP的后果: 如果一个 OrderService 类同时处理订单创建、支付和邮件通知,那么当支付或邮件通知的业务逻辑变化时,都需要修改 OrderService。这会使代码变得臃肿、难以维护,并且测试时需要模拟多个不相关的依赖。因此,正确的做法是将这些职责拆分到 OrderServicePaymentServiceNotificationService 等不同类中。

2. 开闭原则(OCP):面向未来的扩展

原则: 一个软件实体(如类、模块、函数)应该对扩展开放,对修改关闭。就像一个老铁锅能炒新菜式而无需更换,这意味着在增加新功能时,我们不应该去修改旧代码,而是通过扩展的方式来实现。

源码实例: Java I/O库中的装饰者模式是OCP的经典实践。InputStream接口定义了基本的输入流功能,而 BufferedInputStreamDataInputStream 等子类通过包装 (Wrap) 现有流来添加新功能(如缓冲、读取基本类型),它们都继承自FilterInputStream,保持了同一接口。这使得我们可以灵活地组合不同功能,而无需修改 FileInputStream 等原有类的代码。


二、抽象与依赖:里氏替换(LSP)与依赖倒置(DIP)

为了实现开闭原则,我们必须依赖于抽象里氏替换原则依赖倒置原则正是指导我们如何正确使用抽象的。

1. 里氏替换原则(LSP):行为的一致性

原则: 所有使用父类的地方,都可以用其子类进行替换,而程序不会出现错误或异常。就像儿子继承了老爸的餐馆,顾客可以无感知地继续就餐

源码实例: Java集合框架严格遵循LSP。List接口定义了列表的基本行为,而 ArrayListLinkedList 等实现类都完整地实现了这些行为。因此,我们可以在任何使用 List 接口的地方安全地用 ArrayListLinkedList 替换,而无需担心程序行为发生改变。

违反LSP的后果: 假设你有一个 Square 类继承了 Rectangle,并重写了 setWidth()setHeight() 方法,使它们总是将宽度和高度设置为相同的值。这违反了LSP,因为在期望 Rectangle 的地方使用 Square 会改变原有的行为(即widthheight可以独立设置),从而导致不可预知的错误。

2. 依赖倒置原则(DIP):依赖于抽象而非实现

原则: 高层模块不应依赖于低层模块,两者都应依赖于抽象。抽象不应依赖于细节,细节应依赖于抽象。就像炒菜时只认调料瓶(抽象接口),而不管里面具体是哪家品牌的酱油(具体实现)

源码实例: **Spring框架的依赖注入(DI)**机制是DIP的最佳实践。在Spring中,OrderService类会依赖于一个抽象的 PaymentGateway 接口,而不是具体的 AlipayGatewayWeChatPayGateway。具体的实现类由Spring容器在运行时注入,这使得 OrderService 不再耦合于任何具体的支付实现,大大增强了模块的解耦和灵活性。


三、接口与交互:接口隔离(ISP)与迪米特法则(LoD)

接口隔离原则迪米特法则则进一步细化了我们与模块之间的交互方式。

1. 接口隔离原则(ISP):接口的精简与细粒度

原则: 不应该强迫客户端依赖于它们不使用的接口。一个接口应该尽可能地小而精,就像吃面条用筷子,喝汤用勺子,工具应专为特定用途设计。

源码实例: Java的Runnable接口只包含一个run()方法,清晰地表达了其“可执行”的单一职责。相比之下,如果一个接口如IWorker包含了work()eat()sleep()等所有方法,而某个类只需要实现work(),那么它就被迫实现或空实现其他不相关的方法,这违背了ISP。更好的做法是拆分为IWorkerIEaterISleeper三个小接口。

2. 迪米特法则(LoD):最少知识原则

原则: 一个对象应该对其他对象有最少的了解。只与你的“朋友”交谈,不与陌生人说话。就像顾客在餐馆点菜时,只需与服务员交谈,而无需了解后厨的厨师、厨具等具体细节

源码实例: JavaBean的设计遵循了LoD。通过提供gettersetter方法来访问私有字段,外部类无法直接操作对象的内部状态。这隐藏了实现细节,只暴露了必要的公共接口。

违反LoD的后果: 在一个方法中,如果出现 object.getA().getB().doSomething() 这样的调用链,就违反了迪米特法则。这意味着当前对象与 getA() 返回的对象(一个“朋友”)的“朋友”B(一个“陌生人”)发生了交互。这种紧密的耦合使得代码难以维护,一旦 B 的内部结构发生变化,所有调用它的地方都可能需要修改。


四、融会贯通:设计原则的平衡与取舍

六大原则并非孤立存在,它们是相辅相成的。单一职责原则是基础,它让类职责清晰;里氏替换原则接口隔离原则通过限定子类行为和接口粒度,为开闭原则的实现创造了条件;而依赖倒置原则迪米特法则则提供了具体的技术路径,通过抽象和解耦来实现灵活扩展。

在实际项目中,我们应将这些原则视为指导方针而非硬性规定。在小型项目或性能敏感的场景下,可以适当妥协。关键在于权衡,理解每项原则背后的设计思想,并根据项目的具体需求做出明智的选择。