设计模式学习(八)——《大话设计模式》
单一职责原则
单一职责原则(Single Responsibility Principle, SRP)是软件开发中 SOLID 原则之一,由罗伯特·C·马丁(Robert C. Martin)提出。它指的是一个类应该仅有一个引起它变化的原因,或者更简单地说,一个类应该只负责一项职责。
原则解释
在软件工程中,职责被定义为“变化的原因”。如果一个类有多于一个引起它变化的原因,那么这个类就有多于一个的职责。这违反了单一职责原则。遵循这个原则可以帮助开发者降低系统的复杂性,提高代码的可读性和可维护性,同时也使得系统更加灵活,容易适应变化。
应用实例
假设我们有一个名为 User 的类,它负责用户信息的管理和数据的持久化(例如,保存到数据库)。这个类违反了单一职责原则,因为它有两个引起变化的原因:用户信息的管理变化和数据持久化方式的变化。
为了遵循单一职责原则,我们可以将 User 类分解为两个类:
UserInfo:负责用户信息的管理。
UserPersistence:负责用户数据的持久化。
这样,每个类都只负责一个职责,如果未来用户信息的管理方式改变,只需修改 UserInfo 类;如果持久化方式改变,只需修改 UserPersistence 类。
优点
提高可维护性:当一个类只负责一项任务时,它的复杂性降低,更容易理解和维护。
提高可扩展性:遵循 SRP 的系统更容易扩展,因为新增功能或修改现有功能时影响范围更小。
降低修改带来的风险:修改一个具有单一职责的类时,引入错误的可能性会降低,因为你不需要在同一个类中处理多个任务。
开放-封闭原则
开放封闭原则(Open/Closed Principle, OCP)是面向对象设计原则之一,也是 SOLID 原则中的第二个原则。它由 Bertrand Meyer 提出,主要意思是软件实体(类、模块、函数等)应该对扩展开放,对修改封闭。这意味着一个实体允许其行为被扩展,而无需修改其源代码。
原则解释
对扩展开放:应该能够在不改变现有代码的情况下,为软件实体添加新的功能。
对修改封闭:一旦一个软件实体被发布,它的源代码就不应该被修改,除非修复bug。
应用示例
假设我们正在开发一个绘图程序,需要绘制不同形状。一开始,我们只有圆形和正方形。随着需求的变化,我们可能需要添加更多形状,如三角形、五边形等。
按照开放封闭原则,我们可以设计一个抽象的基类或接口 Shape,定义一个 draw() 方法。然后,为每种形状创建一个子类(如 Circle、Square、Triangle 等),并实现 draw() 方法。当需要添加新形状时,我们只需添加一个新的 Shape 子类并实现 draw() 方法,而无需修改现有代码。
interface Shape {
void draw();
}
class Circle implements Shape {
public void draw() {
// 绘制圆形
}
}
class Square implements Shape {
public void draw() {
// 绘制正方形
}
}
class Triangle implements Shape {
public void draw() {
// 绘制三角形
}
}
优点
增强系统的可维护性:遵循开放封闭原则可以减少因新功能引入而对现有代码的修改,从而降低维护成本。
增强系统的可扩展性:系统更容易扩展,因为新增功能时不需要修改现有代码,只需要添加新代码。
促进解耦:通过抽象和多态性可以减少类之间的依赖关系,使系统更加灵活和可维护。
依赖倒转原则
依赖倒转原则(Dependency Inversion Principle, DIP)是 SOLID 设计原则之一,旨在减少类之间的耦合度,提高系统的灵活性和可维护性。
依赖倒转原则有两个核心要点:
-
高层模块不应该依赖于低层模块。两者都应该依赖于抽象。
-
抽象不应该依赖于具体实现。具体实现应该依赖于抽象。
解释
依赖倒转原则的主要目的是通过依赖抽象而不是具体实现,来降低类之间的耦合度。这意味着代码的高层策略不应该由底层的实现细节所影响,从而使得修改底层实现时不需要修改依赖于它的高层模块。
应用实例
假设我们有一个应用程序,其中包含一个按钮类(Button)和一个灯类(Lamp),按钮被按下时灯会切换状态(开/关)。如果按钮直接依赖于灯的具体实现,那么每当我们想要按钮控制其他设备时,都需要修改按钮的代码。
为了遵循依赖倒转原则,我们可以引入一个抽象的设备接口(Device),让灯类实现这个接口。按钮类依赖于设备接口,而不是具体的灯类。这样,按钮类就可以控制任何实现了设备接口的对象,而无需修改按钮类的代码。
interface Device {
void toggle();
}
class Lamp implements Device {
public void toggle() {
// 实现开关灯
}
}
class Button {
private Device device;
public Button(Device device) {
this.device = device;
}
public void press() {
device.toggle();
}
}
优点
降低耦合度:依赖倒转原则通过促使模块间依赖抽象而非具体实现,降低了模块间的耦合度。
提高可维护性:系统中的高层模块不再需要知道底层模块的具体实现细节,使得系统更易于扩展和维护。
增强灵活性:更换或增加新的实现时,不需要修改依赖于抽象的高层模块,提高了系统的灵活性。
里氏代换原则
里氏代换原则(Liskov Substitution Principle, LSP)是 SOLID 设计原则之一,由芭芭拉·里氏(Barbara Liskov)在1987年提出。这个原则的核心思想是:在软件中,如果类 S 是类 T 的子类,那么类型 T 的对象可以被类型 S 的对象替换(即类型 S 的对象可以作为类型 T 的对象使用),而不改变程序的期望行为(正确性、任务执行等)。
原则解释
里氏代换原则要求子类能够替换掉它们的父类并且出现在父类能够出现的任何地方,而不破坏程序的正确性。这意味着子类在扩展父类的行为时,不能改变父类原有的行为。
应用示例
假设我们有一个矩形类和一个正方形类,正方形是矩形的一个特例。根据里氏代换原则,正方形应该能够替换矩形。但是,如果矩形类有设置宽度和高度的方法,而正方形类继承了这些方法,这将违反里氏代换原则,因为改变正方形的宽度会同时改变它的高度,这与矩形的行为不一致。
class Rectangle {
protected int width, height;
public void setWidth(int width) {
this.width = width;
}
public void setHeight(int height) {
this.height = height;
}
public int getArea() {
return width * height;
}
}
class Square extends Rectangle {
public void setWidth(int width) {
this.width = this.height = width;
}
public void setHeight(int height) {
this.width = this.height = height;
}
}
在这个例子中,Square 类违反了里氏代换原则,因为它改变了 Rectangle 类的行为。正确的做法是重构这些类,以确保它们遵循里氏代换原则。
优点
遵循里氏代换原则可以增强程序的健壮性,提高代码的可读性和可维护性,并且能够确保继承体系的正确性。此外,它还促进了代码的复用。
装饰模式
装饰模式(Decorator Pattern)是一种结构型设计模式,它允许用户通过将对象放入包含行为的特殊封装对象中来为单个对象动态地添加新的行为,而不改变其结构。这种模式创建了一个装饰类,用来包装原有的类,并在保持原类方法签名完整性的前提下,提供了额外的功能。
原理
装饰模式主要利用组合和继承的技术来实现。它包含以下几个角色:
- 抽象组件(Component):定义了一个对象接口,可以给这些对象动态地添加职责。
- 具体组件(ConcreteComponent):定义了抽象组件的具体实现,即被装饰的具体对象。
- 装饰角色(Decorator):持有一个组件(Component)对象的引用,并定义了与抽象组件接口一致的接口。
- 具体装饰(ConcreteDecorator):负责给组件添加新的职责。
应用示例
假设我们有一个简单的文本消息类,我们想要不修改其代码的情况下,增加一些额外功能,比如加密和压缩。这时,我们可以使用装饰模式来实现:
// 抽象组件
interface Message {
String getContent();
}
// 具体组件
class TextMessage implements Message {
private String content;
public TextMessage(String content) {
this.content = content;
}
@Override
public String getContent() {
return content;
}
}
// 装饰角色
abstract class MessageDecorator implements Message {
protected Message message;
public MessageDecorator(Message message) {
this.message = message;
}
}
// 具体装饰 - 加密装饰
class EncryptMessageDecorator extends MessageDecorator {
public EncryptMessageDecorator(Message message) {
super(message);
}
@Override
public String getContent() {
// 实现加密功能
return encrypt(message.getContent());
}
private String encrypt(String content) {
return "encrypted(" + content + ")";
}
}
// 具体装饰 - 压缩装饰
class CompressMessageDecorator extends MessageDecorator {
public CompressMessageDecorator(Message message) {
super(message);
}
@Override
public String getContent() {
// 实现压缩功能
return compress(message.getContent());
}
private String compress(String content) {
return "compressed(" + content + ")";
}
}
在示例中,TextMessage 是一个具体组件,EncryptMessageDecorator 和 CompressMessageDecorator 是具体装饰。通过这种方式,我们可以在不修改 TextMessage 类的情况下,为其动态添加加密和压缩等功能。
优点
- 提高类的扩展性:在不修改原有对象的基础上,通过使用不同的装饰类以及这些装饰类的排列组合,可以实现不同效果。
- 动态添加功能:装饰模式提供了一种灵活的替代扩展系统功能的方法,可以在运行时添加或删除功能。
- 符合开闭原则:系统可以在不修改原有代码的情况下引入新的功能,满足开闭原则。