说到设计模式,大家一定并不陌生,不就是23个名词吗,不啦不啦可以列举一堆。实际上,在真正的实际开发中,我们常用的不过七八种,今天就让我们一起来深入了解一下常用的几种设计模式。
首先我们必须要知道,什么是设计模式,我们为什么要使用设计模式 。
设计模式(Design Pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。
-
可以重用设计,减少代码的重复,提高代码的可维护性。
-
可以为设计提供共同的词汇,方便程序员间的交流和理解。
-
可以实现开闭原则,增加新的功能或者修改旧的功能不影响原有的结构。
-
可以让重构系统变得容易,确保开发正确的代码,并降低出错的可能。
-
可以支持变化,为重写其他应用程序提供很好的系统架构。
-
后期可以节省大量时间,提高开发效率。
一、三大分类
设计模式分为三大类:
- 创建型模式(Creational Patterns):这些模式关注对象的创建过程,以便以一种灵活的方式来创建对象,同时隐藏对象的创建细节。有五种创建型模式,分别是工厂方法模式、抽象工厂模式、单例模式、建造者模式和原型模式。
- 结构型模式(Structural Patterns):这些模式涉及对象和类之间的组合,以形成更大的结构,以便更好地满足系统的需求,就像搭积木,可以通过简单积木的组合形成复杂的、功能更为强大的结构。有七种结构型模式,分别是适配器模式、装饰者模式、代理模式、外观模式、桥接模式、组合模式和享元模式。
- 行为型模式(Behavioral Patterns):这些模式关注对象之间的通信和协作,以便在系统中更好地组织和管理类的行为。有十一种行为型模式,分别是策略模式、模板方法模式、观察者模式、迭代器模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式和解释器模式。
二、六大原则
设计模式遵循了六大原则,也称为SOLID原则:
-
单一职责原则(Single Responsibility Principle,SRP):一个类应该只有一个引起它变化的原因,即一个类只负责一项功能。这个原则提倡将一个类拆分成多个独立的类,每个类只关注一个单一职责,从而降低类的复杂性和耦合性。
-
开放封闭原则(Open-Closed Principle,OCP):软件实体(类、模块、函数等)应该对扩展开放,对修改关闭。这意味着当需要添加新功能时,不应该修改现有代码,而是通过扩展现有代码来实现新功能,以避免影响原有功能的稳定性。
-
里氏替换原则(Liskov Substitution Principle,LSP):子类应该能够替换父类并且不影响程序的正确性。这个原则强调子类应该保持父类的行为和约定,不应该修改父类的基本行为。
-
接口隔离原则(Interface Segregation Principle,ISP):客户端不应该依赖它不需要的接口。这个原则推崇将大接口拆分成多个小接口,从而只提供客户端需要的方法,减少不必要的依赖和耦合。
-
依赖倒置原则(Dependency Inversion Principle,DIP):高层模块不应该依赖低层模块,而是应该依赖于抽象。抽象不应该依赖于具体实现,具体实现应该依赖于抽象。这个原则鼓励使用接口或抽象类作为依赖,而不是直接依赖于具体实现类。
-
迪米特法则(Law of Demeter,LoD):也称为最少知识原则(Least Knowledge Principle,LKP)。一个对象应该对其他对象有尽可能少的了解,只与它直接的朋友进行交流。直接的朋友是指当前对象的成员变量、方法的输入参数和方法中创建的对象。这个原则有助于减少对象之间的耦合,提高代码的可维护性和灵活性。
遵循这些设计原则可以帮助我们设计出更加灵活、可扩展、易维护的软件系统,同时,设计模式和设计原则相互配合,可以帮助我们更好地解决各种设计问题。
三、1.责任链模式
责任链模式(Chain of Responsibility Pattern)是一种行为型设计模式,它允许你将请求沿着处理链进行传递,直到有一个处理者能够处理这个请求为止。这种模式通过将多个处理者组成一个链条,每个处理者都尝试处理请求,如果无法处理,则将请求传递给链中的下一个处理者,直到请求被处理为止。
责任链模式在许多不同的应用场景中都有广泛的应用。下面列举了一些常见的应用场景:
-
请求处理链:当一个请求需要经过多个处理步骤或处理者进行处理时,可以使用责任链模式。每个处理者负责一部分逻辑,处理完后可以选择将请求传递给下一个处理者,从而形成一个处理链。
-
日志记录:在日志系统中,可以使用责任链模式来记录日志。不同的处理者可以负责不同级别的日志记录,例如,一个处理者负责记录错误日志,另一个处理者负责记录调试日志,然后按照链式结构传递日志。
-
身份验证和权限检查:在身份验证和权限检查系统中,可以使用责任链模式来验证用户的身份和权限。每个处理者可以检查特定的条件,例如用户名和密码的正确性、账户是否锁定等。如果一个处理者无法通过验证,可以将请求传递给下一个处理者。
-
数据过滤和转换:在数据处理过程中,可以使用责任链模式来进行数据过滤和转换。每个处理者可以根据特定的条件过滤数据或对数据进行转换,然后将处理后的数据传递给下一个处理者。
-
错误处理和异常处理:在错误处理和异常处理系统中,可以使用责任链模式来处理错误和异常。不同的处理者可以处理不同类型的错误或异常,并根据需要将错误或异常传递给下一个处理者进行进一步处理或记录。
组成责任链模式的主要角色有:
-
抽象处理者(Handler):定义一个处理请求的接口,并持有下一个处理者的引用。通常包含一个处理方法(handleRequest),用于处理请求。在实际场景中,可以是一个接口或抽象类。
-
具体处理者(Concrete Handler):实现抽象处理者的接口,具体处理请求的逻辑。如果它能处理该请求,则直接处理;否则,将请求传递给下一个处理者。
-
客户端(Client):创建责任链并向其发送请求。客户端通常并不知道链中具体的处理者是谁,只需要知道链的第一个处理者即可。
使用责任链模式的步骤:
-
定义抽象处理者接口,声明处理请求的方法以及设置下一个处理者的方法。
-
创建具体处理者类,实现抽象处理者接口。在具体处理责任链模式(Chain of Responsibility Pattern)是一种行为型设计模式,它允许多个对象在一个请求的发送者和接收者之间依次处理该请求,形成一个链条。请求在链条上传递,直到有一个处理者能够处理它为止。该模式的主要目的是解耦发送者和接收者之间的关系,使得多个处理者都有机会处理请求,从而增强了系统的灵活性和可扩展性。
现在让我们实现一个简单的demo。假设有一个采购系统,不同的经理有不同的批准额度限制。
- 创建一个抽象处理程序接口:
public interface PurchaseApprover {
void setNextApprover(PurchaseApprover nextApprover);
void approvePurchase(double amount);
}
- 实现具体的处理程序:
public class DepartmentManager implements PurchaseApprover {
private double approvalLimit = 1000.0;
private PurchaseApprover nextApprover;
@Override
public void setNextApprover(PurchaseApprover nextApprover) {
this.nextApprover = nextApprover;
}
@Override
public void approvePurchase(double amount) {
if (amount <= approvalLimit) {
System.out.println("部门经理批准了 $" + amount + " 的采购申请");
} else if (nextApprover != null) {
nextApprover.approvePurchase(amount);
} else {
System.out.println("采购未批准。责任链中没有更多的处理者。");
}
}
}
public class FinanceManager implements PurchaseApprover {
private double approvalLimit = 5000.0;
private PurchaseApprover nextApprover;
@Override
public void setNextApprover(PurchaseApprover nextApprover) {
this.nextApprover = nextApprover;
}
@Override
public void approvePurchase(double amount) {
if (amount <= approvalLimit) {
System.out.println("财务经理批准了 $" + amount + " 的采购申请");
} else if (nextApprover != null) {
nextApprover.approvePurchase(amount);
} else {
System.out.println("采购未批准。责任链中没有更多的处理者。");
}
}
}
public class CEO implements PurchaseApprover {
private PurchaseApprover nextApprover;
@Override
public void setNextApprover(PurchaseApprover nextApprover) {
this.nextApprover = nextApprover;
}
@Override
public void approvePurchase(double amount) {
System.out.println("CEO批准了 $" + amount + " 的采购申请");
}
}
- 客户端代码使用责任链:
public class PurchaseClient {
public static void main(String[] args) {
PurchaseApprover departmentManager = new DepartmentManager();
PurchaseApprover financeManager = new FinanceManager();
PurchaseApprover ceo = new CEO();
// 构建责任链
departmentManager.setNextApprover(financeManager);
financeManager.setNextApprover(ceo);
// 提交采购申请
departmentManager.approvePurchase(500.0);
departmentManager.approvePurchase(2500.0);
departmentManager.approvePurchase(7000.0);
}
}
责任链模式的优点是灵活性高,可以动态地添加或修改处理者,降低了系统的耦合度。但同时也有可能导致请求遍历整个责任链,如果责任链过长,性能可能会受到影响。因此,在使用责任链模式时需要权衡这些因素。
2.模板方法模式
模板方法模式是一种行为设计模式,它定义了一个算法的骨架,并将某些步骤的实现延迟到子类。这样做可以确保算法的结构在不同子类中保持一致,同时允许子类根据需要重写特定步骤的实现。
在模板方法模式中,通常有一个抽象类作为模板,其中包含一个模板方法(Template Method),它定义了算法的结构,以及一些抽象方法和钩子方法。抽象方法由子类实现,用于特定步骤的具体实现,而钩子方法是可选的,子类可以选择性地覆盖或不覆盖这些方法。
常见的应用场景:
-
开发框架,通常框架会定义一些通用的模板,子类可以根据自身的特定需求来细化模板的实现细节,比如Spring中的JdbcTemplate,RestTemplate,RabbitTemplate等。
-
业务逻辑,我们可以针对业务流程做一些拆解,将特定步骤改为子类实现。比如发送验证码的流程,在发送验证码时需要选择不同厂商来发送验证码,但是我们发送的验证码前的检查、验证码生成、保存验证码逻辑都是一样的。
模板方法模式包含以下:
-
抽象类:负责定义模板方法、基本方法、抽象方法。
-
模板方法:在抽象类中定义的流程操作集合,里面有一系列流程操作和条件控制,包含基本方法和抽象方法。
-
基本方法:在抽象类中已经实现了的方法。
-
抽象方法:在抽象类中还没有实现的方法。
-
具体子类:实现抽象类中所定义的抽象方法,也就是实现特定步骤。
还是来一个简单的demo来实现一下模板方法模式。
假设我们要创建一个用于制作饮料的模板,其中有两种饮料:咖啡和茶。饮料的制作过程分为煮水、冲泡、加入调料和倒入杯中等步骤,其中冲泡和加入调料的过程因饮料不同而有所不同。
- 创建一个抽象类作为模板:
abstract class BeverageTemplate {
// 模板方法,定义饮料的制作过程
public final void prepareBeverage() {
boilWater();
brew();
addCondiments();
pourInCup();
}
// 煮水的具体实现
public void boilWater() {
System.out.println("煮水");
}
// 冲泡的抽象方法,由子类实现
public abstract void brew();
// 加入调料的抽象方法,由子类实现
public abstract void addCondiments();
// 倒入杯中的通用实现
public void pourInCup() {
System.out.println("倒入杯中");
}
}
- 创建具体的饮料类,继承模板类并实现特定步骤:
class Coffee extends BeverageTemplate {
@Override
public void brew() {
System.out.println("冲泡咖啡");
}
@Override
public void addCondiments() {
System.out.println("加入糖和牛奶");
}
}
class Tea extends BeverageTemplate {
@Override
public void brew() {
System.out.println("冲泡茶叶");
}
@Override
public void addCondiments() {
System.out.println("加入柠檬");
}
}
- 使用模板方法创建并制作饮料:
public class BeverageClient {
public static void main(String[] args) {
BeverageTemplate coffee = new Coffee();
BeverageTemplate tea = new Tea();
System.out.println("制作咖啡:");
coffee.prepareBeverage();
System.out.println("\n制作茶:");
tea.prepareBeverage();
}
}
输出结果:
制作咖啡:
煮水
冲泡咖啡
加入糖和牛奶
倒入杯中
制作茶:
煮水
冲泡茶叶
加入柠檬
倒入杯中
模板方法模式的优缺点:
优点:
- 代码复用:模板方法模式将算法的结构封装在模板方法中,这样相同的算法结构可以在不同的子类中共享和复用,避免了重复代码。
- 算法固定:模板方法将算法的骨架固定下来,确保算法的结构在不同子类中保持一致,从而减少了代码逻辑的混乱,提高了代码的可读性和维护性。
- 可扩展性:通过将特定步骤的实现延迟到子类中,模板方法模式允许在不修改算法结构的情况下,轻松地在子类中扩展或修改特定步骤的实现。
- 控制流程:模板方法定义了算法的执行流程,使得子类只需要关注特定步骤的实现,而无需关心整个算法的执行过程。
缺点:
- 固化算法结构:模板方法将算法结构固定在模板中,如果算法结构需要经常变化,可能会导致模板方法的修改,增加了维护成本。
- 增加了抽象类的数量:为了实现模板方法模式,通常需要引入抽象类或接口,可能会增加类的数量,增加了代码复杂性。
- 粒度较大:模板方法模式往往将算法细节封装在具体步骤中,可能导致方法的粒度较大,不够灵活。