前言
# 设计模式的目的
编写软件过程中,程序员面临着来自耦合性,内聚性以及可维护性,可扩展性,重用性,灵活性等多方面的 挑战,设计模式是为了让程序(软件),具有更好
- 代码重用性 (即:相同功能的代码,不用多次编写)
- 可读性 (即:编程规范性, 便于其他程序员的阅读和理解)
- 可扩展性 (即:当需要增加新的功能时,非常的方便,称为可维护)
- 可靠性 (即:当我们增加新的功能后,对原来的功能没有影响)
- 使程序呈现高内聚,低耦合的特性 分享金句:
- 设计模式包含了面向对象的精髓,“懂了设计模式,你就懂了面向对象分析和设计(OOA/D)的精要”
- Scott Mayers 在其巨著《Effective C++》就曾经说过:C++ 老手和 C++ 新手的区别就是前者是手背上有很多伤疤
# 设计模式七大原则
设计模式原则,其实就是程序员在编程时,应当遵守的原则,也是各种设计模式的基础(即:设计模式为什么这样设计的依据)
设计模式常用的七大原则:
- 单一职责原则(Single Responsibility Principle,SRP)
- 接口隔离原则(Interface Segregation Principle,ISP)
- 依赖倒转(倒置)原则(Dependency Inversion Principle, DIP)
- 里氏替换原则(Liskov Substitution Principle, LSP)
- 开闭原则(Open/Closed Principle, OCP)
- 迪米特法则(Law of Demeter, LoD)
- 合成复用原则(Composite Reuse Principle, CRP)
# 单一职责原则(Single Responsibility Principle,SRP)
# 定义
单一职责原则(Single Responsibility Principle, SRP)是面向对象设计原则之一,由Robert C. Martin提出。该原则指出一个类应该只有一个引起它变化的原因。
# 核心思想
- 一个类应该只有一个职责。
- 每个类应该只有一个功能,并且该功能应该完全封装在该类中。
# 目的
- 降低类的复杂性。
- 提高类的可读性和可维护性。
- 降低变更引起的风险。
# 实现方法
- 识别类中的不同职责。
- 将不同的职责分离到不同的类中。
- 确保每个类只负责一个功能。
# 示例代码
// 不好的示例:类承担了多个职责
public class User {
private String name;
private String email;
public User(String name, String email) {
this.name = name;
this.email = email;
}
public void saveToDatabase() {
// 保存用户到数据库的逻辑
}
public void sendWelcomeEmail() {
// 发送欢迎邮件的逻辑
}
}
// 好的示例:职责分离
public class User {
private String name;
private String email;
public User(String name, String email) {
this.name = name;
this.email = email;
}
}
public class UserRepository {
public void save(User user) {
// 保存用户到数据库的逻辑
}
}
public class EmailService {
public void sendWelcomeEmail(User user) {
// 发送欢迎邮件的逻辑
}
}
// 使用示例
public class Main {
public static void main(String[] args) {
User user = new User("John Doe", "john@example.com");
UserRepository userRepository = new UserRepository();
EmailService emailService = new EmailService();
userRepository.save(user);
emailService.sendWelcomeEmail(user);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
# 优点
- 代码更加清晰和易于理解。
- 降低类之间的耦合度。
- 提高代码的可测试性和可维护性。
# 缺点
- 过度分离职责可能导致类的数量增多,增加管理难度。
通过遵循单一职责原则,可以确保每个类只负责一个功能,从而提高代码的可读性和可维护性,减少因职责不明确导致的错误。
# 接口隔离原则(Interface Segregation Principle,ISP)
# 定义
接口隔离原则(Interface Segregation Principle, ISP)是面向对象设计原则之一,由Robert C. Martin提出。该原则指出客户端不应该依赖它不需要的接口。
# 核心思想
- 将大的接口拆分为更小、更具体的接口。
- 客户端只应了解它们感兴趣的方法。
# 目的
- 减少接口的依赖性。
- 提高系统的灵活性和可维护性。
# 实现方法
- 识别接口中的方法,确定哪些方法是一组相关的功能。
- 将这些相关的方法组合成一个独立的接口。
- 让客户端只实现或依赖于它们真正需要的接口。
# 示例代码
// 不好的示例:大而全的接口
public interface Machine {
void print();
void scan();
void fax();
}
public class MultiFunctionMachine implements Machine {
@Override
public void print() {
// 打印逻辑
}
@Override
public void scan() {
// 扫描逻辑
}
@Override
public void fax() {
// 传真逻辑
}
}
public class OldPrinter implements Machine {
@Override
public void print() {
// 打印逻辑
}
@Override
public void scan() {
throw new UnsupportedOperationException("Not supported");
}
@Override
public void fax() {
throw new UnsupportedOperationException("Not supported");
}
}
// 好的示例:拆分为多个小接口
public interface Printer {
void print();
}
public interface Scanner {
void scan();
}
public interface Fax {
void fax();
}
public class MultiFunctionMachine implements Printer, Scanner, Fax {
@Override
public void print() {
// 打印逻辑
}
@Override
public void scan() {
// 扫描逻辑
}
@Override
public void fax() {
// 传真逻辑
}
}
public class OldPrinter implements Printer {
@Override
public void print() {
// 打印逻辑
}
}
// 使用示例
public class Main {
public static void main(String[] args) {
Printer printer = new OldPrinter();
printer.print();
MultiFunctionMachine multiFunctionMachine = new MultiFunctionMachine();
multiFunctionMachine.print();
multiFunctionMachine.scan();
multiFunctionMachine.fax();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
# 优点
- 降低模块间的耦合度。
- 提高代码的可复用性和可测试性。
# 缺点
- 过度拆分接口可能导致接口数量过多,增加管理难度。
通过遵循接口隔离原则,可以确保每个接口只包含必要的方法,从而提高系统的可维护性和扩展性。
# 依赖倒转(倒置)原则(Dependency Inversion Principle, DIP)
# 定义
依赖倒转原则(Dependency Inversion Principle, DIP)是面向对象设计原则之一,由Robert C. Martin提出。该原则指出高层模块不应该依赖低层模块,两者都应该依赖抽象;抽象不应该依赖细节,细节应该依赖抽象。
# 核心思想
- 高层模块(如业务逻辑层)不应该依赖低层模块(如数据访问层)的具体实现。
- 高层模块和低层模块都应该依赖于抽象(如接口或抽象类)。
- 抽象不应该依赖于具体实现,具体实现应该依赖于抽象。
# 目的
- 降低模块间的耦合度。
- 提高代码的可复用性和可测试性。
- 使系统更加灵活,更容易扩展和维护。
# 实现方法
- 定义抽象(接口或抽象类)来描述高层模块和低层模块之间的交互。
- 高层模块通过抽象与低层模块进行通信。
- 具体实现类实现抽象,并由低层模块提供。
# 示例代码
// 不好的示例:直接依赖具体实现
public class FileManager {
public void saveToFile(String content) {
// 保存文件的具体实现
}
}
public class ReportGenerator {
private FileManager fileManager;
public ReportGenerator() {
this.fileManager = new FileManager();
}
public void generateReport(String report) {
fileManager.saveToFile(report);
}
}
// 好的示例:依赖抽象
public interface FileStorage {
void save(String content);
}
public class FileManager implements FileStorage {
@Override
public void save(String content) {
// 保存文件的具体实现
}
}
public class ReportGenerator {
private FileStorage fileStorage;
public ReportGenerator(FileStorage fileStorage) {
this.fileStorage = fileStorage;
}
public void generateReport(String report) {
fileStorage.save(report);
}
}
// 使用示例
public class Main {
public static void main(String[] args) {
FileStorage fileManager = new FileManager();
ReportGenerator reportGenerator = new ReportGenerator(fileManager);
reportGenerator.generateReport("This is a report.");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# 优点
- 降低模块间的耦合度,提高代码的可复用性和可测试性。
- 使系统更加灵活,更容易扩展和维护。
- 便于引入新的实现,而不需要修改高层模块的代码。
# 缺点
- 增加了抽象层,可能会使代码结构变得复杂。
- 需要更多的设计和规划,以确保抽象的合理性和有效性。
通过遵循依赖倒转原则,可以确保高层模块和低层模块都依赖于抽象,从而提高系统的灵活性和可维护性。
# 里氏替换原则(Liskov Substitution Principle, LSP)
# 定义
里氏替换原则(Liskov Substitution Principle, LSP)是面向对象设计原则之一,由Barbara Liskov提出。该原则指出子类型必须能够替换掉它们的基类型,即子类对象能够替换父类对象,而程序的行为不会改变。
# 核心思想
- 子类可以扩展父类的功能,但不能改变父类原有的行为。
- 子类对象在任何父类对象出现的地方都能正确地工作。
# 目的
- 保证继承关系的合理性。
- 提高代码的可复用性和可扩展性。
- 降低模块间的耦合度。
# 实现方法
- 确保子类的所有方法都符合父类的约定。
- 子类可以添加新的方法,但不能改变父类方法的行为。
- 如果子类不能完全实现父类的行为,考虑重新设计类的继承关系。
# 示例代码
// 不好的示例:违反里氏替换原则
public class Bird {
public void fly() {
System.out.println("Bird is flying.");
}
}
public class Ostrich extends Bird {
@Override
public void fly() {
System.out.println("Ostrich cannot fly.");
}
}
// 使用示例
public class Main {
public static void main(String[] args) {
Bird bird = new Ostrich();
bird.fly(); // 输出 "Ostrich cannot fly."
}
}
// 好的示例:遵守里氏替换原则
public interface Flyable {
void fly();
}
public class Bird {
public void fly() {
System.out.println("Bird is flying.");
}
}
public class FlyingBird extends Bird implements Flyable {
@Override
public void fly() {
super.fly();
}
}
public class Ostrich extends Bird {
@Override
public void fly() {
// Ostrich does not implement the fly method
}
}
// 使用示例
public class Main {
public static void main(String[] args) {
Bird flyingBird = new FlyingBird();
flyingBird.fly(); // 输出 "Bird is flying."
Bird ostrich = new Ostrich();
// ostrich.fly(); // 不调用 fly 方法,因为鸵鸟不会飞
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
# 优点
- 保证继承关系的合理性,避免子类对父类行为的破坏。
- 提高代码的可复用性和可扩展性。
- 降低模块间的耦合度,提高系统的灵活性。
# 缺点
- 需要仔细设计类的继承关系,确保子类的行为符合父类的约定。
- 可能需要引入更多的接口或抽象类来实现合理的继承关系。
通过遵循里氏替换原则,可以确保子类对象在任何父类对象出现的地方都能正确地工作,从而提高代码的可复用性和可维护性。
# 开闭原则(Open/Closed Principle, OCP)
# 定义
开闭原则(Open/Closed Principle, OCP)是面向对象设计原则之一,由Bertrand Meyer提出。该原则指出软件实体(类、模块、函数等)应该对扩展开放,对修改关闭。
# 核心思想
- 对扩展开放:软件实体应该能够通过扩展来增加新的功能。
- 对修改关闭:在增加新功能时,不应该修改现有的代码。
# 目的
- 提高代码的可复用性和可维护性。
- 降低因修改现有代码而引入错误的风险。
- 使系统更加灵活,更容易扩展和维护。
# 实现方法
- 使用抽象类或接口来定义行为。
- 通过继承或组合来扩展功能。
- 使用多态来实现行为的动态切换。
# 示例代码
// 不好的示例:违反开闭原则
public class Shape {
public void draw() {
System.out.println("Drawing a shape.");
}
}
public class Circle extends Shape {
@Override
public void draw() {
System.out.println("Drawing a circle.");
}
}
public class Square extends Shape {
@Override
public void draw() {
System.out.println("Drawing a square.");
}
}
public class DrawingApp {
public void drawShapes(Shape[] shapes) {
for (Shape shape : shapes) {
shape.draw();
}
}
}
// 使用示例
public class Main {
public static void main(String[] args) {
Shape[] shapes = {new Circle(), new Square()};
DrawingApp app = new DrawingApp();
app.drawShapes(shapes);
}
}
// 好的示例:遵守开闭原则
public interface Shape {
void draw();
}
public class Circle implements Shape {
@Override
public void draw() {
System.out.println("Drawing a circle.");
}
}
public class Square implements Shape {
@Override
public void draw() {
System.out.println("Drawing a square.");
}
}
public class Triangle implements Shape {
@Override
public void draw() {
System.out.println("Drawing a triangle.");
}
}
public class DrawingApp {
public void drawShapes(Shape[] shapes) {
for (Shape shape : shapes) {
shape.draw();
}
}
}
// 使用示例
public class Main {
public static void main(String[] args) {
Shape[] shapes = {new Circle(), new Square(), new Triangle()};
DrawingApp app = new DrawingApp();
app.drawShapes(shapes);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
# 优点
- 提高代码的可复用性和可维护性。
- 降低因修改现有代码而引入错误的风险。
- 使系统更加灵活,更容易扩展和维护。
# 缺点
- 需要更多的设计和规划,以确保扩展点的合理性和有效性。
- 可能会增加系统的复杂性,特别是在初期设计阶段。
通过遵循开闭原则,可以在不修改现有代码的情况下增加新的功能,从而提高代码的可复用性和可维护性。
# 迪米特法则(Law of Demeter, LoD)
# 定义
迪米特法则(Law of Demeter, LoD),也称为最少知识原则(Least Knowledge Principle),是由Ian Holland提出的一种面向对象设计原则。该原则指出一个对象应该尽可能少地与其他对象发生交互,即一个对象应当对其它对象有最少的了解。
# 核心思想
- 一个对象应该尽量减少与其它对象的直接交互。
- 一个对象应该只与其直接的朋友(即其自身、其参数、其成员变量、其方法返回的对象)发生交互。
# 目的
- 降低模块间的耦合度。
- 提高代码的可复用性和可维护性。
- 使系统更加灵活,更容易扩展和维护。
# 实现方法
- 通过封装来隐藏内部实现细节。
- 通过中间类来协调不同对象之间的交互。
- 避免对象之间直接调用其他对象的方法。
# 示例代码
// 不好的示例:违反迪米特法则
public class Person {
private Address address;
public Person(Address address) {
this.address = address;
}
public Address getAddress() {
return address;
}
}
public class Address {
private String city;
private String street;
public Address(String city, String street) {
this.city = city;
this.street = street;
}
public String getCity() {
return city;
}
public String getStreet() {
return street;
}
}
public class ReportGenerator {
public void generateReport(Person person) {
String city = person.getAddress().getCity();
String street = person.getAddress().getStreet();
System.out.println("Report: " + city + ", " + street);
}
}
// 使用示例
public class Main {
public static void main(String[] args) {
Address address = new Address("New York", "5th Avenue");
Person person = new Person(address);
ReportGenerator reportGenerator = new ReportGenerator();
reportGenerator.generateReport(person);
}
}
// 好的示例:遵守迪米特法则
public class Person {
private Address address;
public Person(Address address) {
this.address = address;
}
public String getCity() {
return address.getCity();
}
public String getStreet() {
return address.getStreet();
}
}
public class Address {
private String city;
private String street;
public Address(String city, String street) {
this.city = city;
this.street = street;
}
public String getCity() {
return city;
}
public String getStreet() {
return street;
}
}
public class ReportGenerator {
public void generateReport(Person person) {
String city = person.getCity();
String street = person.getStreet();
System.out.println("Report: " + city + ", " + street);
}
}
// 使用示例
public class Main {
public static void main(String[] args) {
Address address = new Address("New York", "5th Avenue");
Person person = new Person(address);
ReportGenerator reportGenerator = new ReportGenerator();
reportGenerator.generateReport(person);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
# 优点
- 降低模块间的耦合度,提高代码的可复用性和可维护性。
- 使系统更加灵活,更容易扩展和维护。
- 减少对象之间的直接依赖,提高代码的健壮性。
# 缺点
- 可能会增加类的数量,使系统结构变得复杂。
- 需要更多的设计和规划,以确保对象之间的交互合理。
通过遵循迪米特法则,可以减少对象之间的直接交互,降低模块间的耦合度,从而提高代码的可复用性和可维护性。
# 合成复用原则(Composite Reuse Principle, CRP)
# 定义
合成复用原则(Composite Reuse Principle, CRP)是一种面向对象设计原则,强调在软件设计中应尽量使用对象组合(Composition)而不是继承(Inheritance)来实现复用。
# 核心思想
- 优先使用对象组合而非继承来实现复用。
- 组合关系表示“拥有”关系,而继承关系表示“是一种”关系。
# 目的
- 提高代码的灵活性和可维护性。
- 降低类之间的耦合度。
- 避免继承带来的紧耦合和复杂性。
# 实现方法
- 通过将一个类的实例作为另一个类的成员变量来实现组合。
- 通过委托(Delegation)将任务交给组合的对象来完成。
# 示例代码
// 不好的示例:使用继承
public class Engine {
public void start() {
System.out.println("Engine started.");
}
}
public class Car extends Engine {
@Override
public void start() {
super.start();
System.out.println("Car started.");
}
}
// 使用示例
public class Main {
public static void main(String[] args) {
Car car = new Car();
car.start();
}
}
// 好的示例:使用组合
public class Engine {
public void start() {
System.out.println("Engine started.");
}
}
public class Car {
private Engine engine;
public Car(Engine engine) {
this.engine = engine;
}
public void start() {
engine.start();
System.out.println("Car started.");
}
}
// 使用示例
public class Main {
public static void main(String[] args) {
Engine engine = new Engine();
Car car = new Car(engine);
car.start();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# 优点
- 提高代码的灵活性和可维护性。
- 降低类之间的耦合度,使系统更加模块化。
- 避免继承带来的紧耦合和复杂性,减少代码的脆弱性。
# 缺点
- 可能会增加类的数量,使系统结构变得复杂。
- 需要更多的设计和规划,以确保组合关系的合理性和有效性。
通过遵循合成复用原则,可以优先使用对象组合而非继承来实现复用,从而提高代码的灵活性和可维护性,降低类之间的耦合度。