软件设计模式是前辈们代码设计经验的总结,可以反复使用。设计模式共分为3大类,创建者模式(6种)、结构型模式(7种)、行为型模式(11种),一共24种设计模式,软件设计一般需要满足7大基本原则。下面通过5章的学习一起来看看设计模式的魅力吧。
行为模式(11种):本质是描述类与对象协助完成单个对象无法完成的任务,以及怎么分配职责。
包括:模板方法、策略、命令、责任链、状态、观察者、中介者模式、迭代器、访问者、备忘录、解释器。
目录
1.模板方法模式
2.策略模式
3.命令模式
4.责任链模式
2.5.状态模式
2.6.观察者模式
定义:定义一个算法骨架,将算法的一些步骤延迟到其子类中,使得子类不改变算法结构的情况下,重新定义该算法的特定步骤。
模板方法主要包含一下角色:
01.抽象类:由一个模板方法和多个基本方法组成,负责给出一个算法的轮廓与骨架。
02.具体子类:用于实现抽象类中的方法。
模板方法模式优点及使用场景:
优点:提高了代码的复用性,子类继承父类并重写方法,实现了反转控制,父类掉子类。
使用场景:整体算法步骤固定,部分的步骤不同,JDK中InputStreaam类就使用了模板方法模式。
下面通过模板方法模拟炒菜的过程:倒油、热油、倒数蔬菜、翻炒等步骤。
1.首先定义一个抽象类,抽象类中定义一个模板方法与基本方法(抽象的与具体的)。
/**
* @author nuist__NJUPT
* @ClassName AbstractClass
* @description: 抽象类-定义模板方法与基本方法
* @date 2024年02月01日
*/
public abstract class AbstractClass {
// 模板方法的定义
public final void cookProcess(){
pourSoil();
heatSoil();
pourVegetable();
pourSauce();
fry();
}
public void pourSoil(){
System.out.println("倒油");
}
public void heatSoil(){
System.out.println("热油");
}
public abstract void pourVegetable() ;
public abstract void pourSauce() ;
public void fry(){
System.out.println("炒菜直至菜熟了...");
}
}
2.定义具体的子类,子类继承抽象类,并重写抽象方法,具体如下:
/**
* @author nuist__NJUPT
* @ClassName ConcreteClass_BaoCai
* @description: 炒包菜子类
* @date 2024年02月01日
*/
public class ConcreteClass_BaoCai extends AbstractClass {
public void pourVegetable() {
System.out.println("下锅的蔬菜是包菜");
}
public void pourSauce() {
System.out.println("加入的调料是辣椒");
}
}
/**
* @author nuist__NJUPT
* @ClassName ConcreteClass_CaiXin
* @description: 炒菜心子类
* @date 2024年02月01日
*/
public class ConcreteClass_CaiXin extends AbstractClass {
public void pourVegetable() {
System.out.println("下锅的蔬菜是菜心");
}
public void pourSauce() {
System.out.println("加入的调料是蒜苗");
}
}
3.定义测试类,测试模板方法模式。
/**
* @author nuist__NJUPT
* @ClassName Client
* @description: 测试类
* @date 2024年02月01日
*/
public class Client {
public static void main(String[] args) {
ConcreteClass_BaoCai concreteClass_baoCai = new ConcreteClass_BaoCai() ;
concreteClass_baoCai.cookProcess();
System.out.println("--------------------------------");
ConcreteClass_CaiXin concreteClass_caiXin = new ConcreteClass_CaiXin() ;
concreteClass_caiXin.cookProcess();
}
}
定义:策略模式定义了一系列算法,并把个每个算法封装起来,并把使用算法的责任与算法的实现进行分割,并委派不同的对象对算法进行管理。
策略模式的主要角色如下:
01.抽象策略:通常是一个接口或者一个抽象类。
02.具体策略:实现了抽象策略中定义的接口,提供了具体的算法实现。
03.环境类:持有策略类的引用,由测试类调用。
策略模式的优缺点及使用场景:
优点:具体的策略类之间可以自由切换,易于扩展。
缺点:客户端必须知道所有的策略类,策略模式会产生很多策略类,可以通过享元模式减少策略类。
使用场景:一个系统动态的在多种算法中选择其中的一种。Arrays.sort()方法就是应用了策略模式,根据传入的策略进行规则排序。
下面通过一个促销活动案例,来学习策略模式,其中,促销活动策略为抽象策略接口,其实现子类为具体的策略,促销员类为环境类,用于和客户端交互。
1.首先定义抽象策略接口。
/**
* @author nuist__NJUPT
* @InterfaceName Strategy
* @description: 抽象策略
* @date 2024年02月01日
*/
public interface Strategy {
void show() ;
}
2.定义两个具体的策略类,实现抽象策略接口。
/**
* @author nuist__NJUPT
* @ClassName StrategyA
* @description: 具体策略类
* @date 2024年02月01日
*/
public class StrategyA implements Strategy {
public void show() {
System.out.println("买一送一");
}
}
/**
* @author nuist__NJUPT
* @ClassName StrategyB
* @description: 具体策略类
* @date 2024年02月01日
*/
public class StrategyB implements Strategy{
public void show() {
System.out.println("满200减30");
}
}
3.定义环境类,用于注入接口,并调用接口方法,用于和客户端进行交互。
/**
* @author nuist__NJUPT
* @ClassName SalesMan
* @description 环境类
* @date 2024年02月01日
*/
public class SalesMan {
// 聚合策略对象
private Strategy strategy ;
public SalesMan(Strategy strategy) {
this.strategy = strategy;
}
public Strategy getStrategy() {
return strategy;
}
public void setStrategy(Strategy strategy) {
this.strategy = strategy;
}
public void show(){
strategy.show();
}
}
4.最后定义测试类,测试策略模式。
/**
* @author nuist__NJUPT
* @ClassName Main
* @description: 测试类
* @date 2024年02月01日
*/
public class Main {
public static void main(String[] args) {
SalesMan salesMan = new SalesMan(new StrategyA()) ;
salesMan.show();
System.out.println("==============================");
salesMan.setStrategy(new StrategyB());
salesMan.show();
}
}
定义:将一个请求封装成一个对象,将发出请求与执行请求分割开,这样可以使得两者通过命令对象进行沟通。
命令模式包含以下主要角色:
01.抽象命令类角色:定义命令的接口,声明执行的方法。
02.具体命令角色:实现命令接口的类。
03.实现者/接收者:真正执行命令的对象。
04.调用者/请求者:要求命令对象执行请求。
命令模式的优缺点及使用场景:
优点:降低系统的耦合度,增加与删除命令也很方便
缺点:系统架构比较复杂,命令类会比较多。
使用场景:请求与执行解耦的场景,JDK中Runnable接口就是命令对象
我们通过一个案例去学习一下策略模式,其中:服务器是请求者用于发送命令,厨师是接收者用于执行命令。
1.首先定义一个订单类,订单类包含餐桌编号,餐品名称和餐品数量集合map。
import java.util.HashMap;
import java.util.Map;
/**
* @author nuist__NJUPT
* @ClassName Order
* @description: 订单类
* @date 2024年02月01日
*/
public class Order {
// 餐桌号码
private int diningTable ;
// 所下的餐品及份数
private Map fooDir = new HashMap() ;
public int getDiningTable() {
return diningTable;
}
public void setDiningTable(int diningTable) {
this.diningTable = diningTable;
}
public Map getFooDir() {
return fooDir;
}
public void setFood(String name, int num) {
fooDir.put(name, num) ;
}
}
2.定义抽象命令类与具体命令类,在具体命令类中持有订单对象与接收者。
/**
* @author nuist__NJUPT
* @InterfaceName Command
* @description: 抽象命令类
* @date 2024年02月01日
*/
public interface Command {
void execute() ;
}
import java.util.Map;
/**
* @author nuist__NJUPT
* @ClassName OrderCommand
* @description: 具体的命令类
* @date 2024年02月01日
*/
public class OrderCommand implements Command {
// 持有接收者对象
private Chief chief ;
// 持有订单对象
private Order order ;
public OrderCommand(Chief chief, Order order) {
this.chief = chief;
this.order = order;
}
public void execute() {
System.out.println(order.getDiningTable() + "桌的订单:");
Map fooDir = order.getFooDir();
for(String foodName : fooDir.keySet()){
chief.makeFood(foodName,fooDir.get(foodName));
}
System.out.println(order.getDiningTable() + "桌的饭准备好了...");
}
}
3.定义请求者发起命令,定义接收者执行具体的命令。
import java.util.ArrayList;
import java.util.List;
/**
* @author nuist__NJUPT
* @ClassName Waiter
* @description: 请求者-服务员类
* @date 2024年02月01日
*/
public class Waiter {
// 持有多个命令对象
private List list = new ArrayList() ;
public void setCommand(Command command){
list.add(command) ;
}
// 发起命令功能
public void orderUp(){
System.out.println("服务员:厨师,订单来了...");
// 遍历集合
for(Command command : list){
if(command != null){
command.execute();
}
}
}
}
/**
* @author nuist__NJUPT
* @ClassName Chief
* @description: 接收者-厨师类
* @date 2024年02月01日
*/
public class Chief {
public void makeFood(String name, int num){
System.out.println(num + "份" + name);
}
}
4.定义测试类,在测试类中创建订单与接收者对象,根据订单与接收者对象创建命令对象,根据命令对象创建发送者,由发送者发送命令。
/**
* @author nuist__NJUPT
* @ClassName Main
* @description: 测试类
* @date 2024年02月01日
*/
public class Main {
public static void main(String[] args) {
// 创建订单,设置餐桌号、设置食物
Order order1 = new Order() ;
order1.setDiningTable(1);
order1.setFood("西红柿鸡蛋面", 1);
order1.setFood("紫菜汤", 2);
// 创建订单,设置餐桌号、设置食物
Order order2 = new Order() ;
order2.setDiningTable(2);
order2.setFood("牛肉盖饭", 2);
order2.setFood("冰可乐", 3);
// 创建接收者对象
Chief chief = new Chief();
// 创建命令对象
OrderCommand orderCommand1 = new OrderCommand(chief, order1);
OrderCommand orderCommand2 = new OrderCommand(chief, order2);
// 创建请求者对象
Waiter waiter = new Waiter();
waiter.setCommand(orderCommand1);
waiter.setCommand(orderCommand2);
// 请求者发起命令
waiter.orderUp();
}
}
定义:责任链模式又名职责链模式,就是将请求的处理者拼接成一条链,当请求发生时候,可以沿着链进行传递,直至找到处理的对象。
责任链的主要角色:
01.抽象处理者角色:定义一个处理请求的接口或者抽象类。
02.具体处理者角色:实现抽线处理者的抽象方法。
03.客户类角色:创建处理链,并向链头的具体处理者对象提交请求。
责任链模式的优缺点及使用场景:
优点:简化了对象之间的连接,使得责任也能正常分担。
缺点:责任链太长可能会影响性能,另外要是出现环形责任链也会导致系统错误。
使用场景:在Javaweb中的过滤器链Filterchain就是责任链模式
我们通过一个请假系统案例来学习责任链模式,请假1天以内需要小组长同意,请假1-3天需要部门经理同意,请假3-7天需要总经理同意。
1.首先定义一个请假条类,包含请假人姓名,请假时间,请假原因。
/**
* @author nuist__NJUPT
* @ClassName LeaveRequest
* @description: 请假条类
* @date 2024年02月02日
*/
public class LeaveRequest {
// 姓名
private String name ;
// 请假天数
private int num ;
// 请假内容
private String content ;
public LeaveRequest(String name, int num, String content) {
this.name = name;
this.num = num;
this.content = content;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getNum() {
return num;
}
public void setNum(int num) {
this.num = num;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
}
2.然后定义抽象处理者角色用于处理请假条,其中定义具体的提交请假条方法和具体处理者待处理的抽象方法。
/**
* @author nuist__NJUPT
* @ClassName Handler
* @description: 抽象处理者类
* @date 2024年02月02日
*/
public abstract class Handler {
protected final static int NUM_ONE = 1 ;
protected final static int NUM_THREE = 3 ;
protected final static int NUM_SEVEN = 7 ;
// 该领导处理的请求天数区间
private int numStart ;
private int numEnd ;
// 声明后续者:上级领导
private Handler nextHandler ;
public Handler(int numStart, int numEnd) {
this.numStart = numStart;
this.numEnd = numEnd;
}
public Handler(int numStart) {
this.numStart = numStart;
}
// 设置上级领导的方法
public void setNextHandler(Handler nextHandler) {
this.nextHandler = nextHandler;
}
// 各级领导处理请求条的方法
protected abstract void handleLeave(LeaveRequest leaveRequest) ;
// 提交请求条
public void submit(LeaveRequest leaveRequest){
this.handleLeave(leaveRequest);
if(this.nextHandler != null && leaveRequest.getNum() > this.numEnd){
this.nextHandler.submit(leaveRequest);
}else {
System.out.println("流程结束...");
}
}
}
3.定义三个具体的处理者类,分别处理相应天数的请假条。
/**
* @author nuist__NJUPT
* @ClassName GroupLeader
* @description: 小组长类-具体的处理者
* @date 2024年02月02日
*/
public class GroupLeader extends Handler {
public GroupLeader() {
super(0, Handler.NUM_ONE);
}
protected void handleLeave(LeaveRequest leaveRequest) {
System.out.println(leaveRequest.getName() + "请假" + leaveRequest.getNum() + "天" + leaveRequest.getContent());
System.out.println("小组长审批:同意");
}
}
/**
* @author nuist__NJUPT
* @ClassName Manager
* @description: 部门经理类
* @date 2024年02月03日
*/
public class Manager extends Handler{
public Manager() {
super(Handler.NUM_ONE, Handler.NUM_THREE);
}
protected void handleLeave(LeaveRequest leaveRequest) {
System.out.println(leaveRequest.getName() + "请假" + leaveRequest.getNum() + "天" + leaveRequest.getContent());
System.out.println("部门经理审批:同意");
}
}
/**
* @author nuist__NJUPT
* @ClassName GeneralManager
* @description: 总经理类
* @date 2024年02月03日
*/
public class GeneralManager extends Handler{
public GeneralManager() {
super(Handler.NUM_THREE, Handler.NUM_SEVEN);
}
protected void handleLeave(LeaveRequest leaveRequest) {
System.out.println(leaveRequest.getName() + "请假" + leaveRequest.getNum() + "天" + leaveRequest.getContent());
System.out.println("总经理审批:同意");
}
}
4.最后客户端测试类,创建请假条,创建处理者角色并设置责任链,最后提交请假条。
/**
* @author nuist__NJUPT
* @ClassName Client
* @description: 客户端测试类
* @date 2024年02月03日
*/
public class Client {
public static void main(String[] args) {
// 创建一个请假条对象
LeaveRequest leaveRequest = new LeaveRequest("小王", 6, "生病了" ) ;
// 创建各级领导对象
GroupLeader groupLeader = new GroupLeader() ;
Manager manager = new Manager() ;
GeneralManager generalManager = new GeneralManager() ;
// 设置处理者链
groupLeader.setNextHandler(manager);
manager.setNextHandler(generalManager);
// 小王提交请假条
groupLeader.submit(leaveRequest);
}
}
定义:对于有状态的对象,把复杂的判断逻辑提取到不同的状态对象中去,允许在对象内部状态发生变化时改变其行为。
状态模式包含如下角色:
01.环境角色:定义客户端程序需要的接口,维护一个当前状态,将相关操作委派给当前状态处理
02.抽象状态角色:定义一个接口,用于封装环境对象中的状态所对应的行为。
03.具体状态角色:实现抽象状态所对应的行为。
状态模式的优缺点:
优点:状态与行为合为一体,改变状态即可改变行为,减少了条件语句的判断。
缺点:增加了系统类与对象的个数,实现起来相对复杂。
使用场景:当对象的行为取决于它的状态,需要根据状态改变其行为。
下面通过电梯的使用案例来学习一下状态模式,在状态对象中包含具体的行为。
1.首先定义抽象状态类,同时定义相应的抽象方法。
/**
* @author nuist__NJUPT
* @ClassName LiftState
* @description: 抽象状态类
* @date 2024年02月03日
*/
public abstract class LiftState {
// 声明环境角色类变量
protected Context context ;
public void setContext(Context context) {
this.context = context;
}
// 电梯开启、关闭、运行、停止操作
public abstract void open() ;
public abstract void close() ;
public abstract void run() ;
public abstract void stop() ;
}
2.创建一个环境类,在环境类中设置相应的状态与环境,并通过相应的状态调用相应的方法。
/**
* @author nuist__NJUPT
* @ClassName Context
* @description: 环境类
* @date 2024年02月03日
*/
public class Context {
// 定义对应的状态对象常量
public final static OpeningState OPENING_STATE = new OpeningState() ;
public final static ClosingState CLOSING_STATE = new ClosingState() ;
public final static RunningState RUNNING_STATE = new RunningState() ;
public final static StoppingState STOPPING_STATE = new StoppingState() ;
// 定义一个当前电梯的状态变量
private LiftState liftState ;
// 设置当前状态对象
public void setLiftState(LiftState liftState) {
this.liftState = liftState;
this.liftState.setContext(this);
}
public void open(){
this.liftState.open();
}
public void stop(){
this.liftState.stop() ;
}
public void close(){
this.liftState.close();
}
public void run(){
this.liftState.run() ;
}
}
3.定义四个具体的状态类,分别实现抽象状态类,并抽象状态类中的方法。
/**
* @author nuist__NJUPT
* @ClassName ClosingState
* @description: 电梯关闭状态
* @date 2024年02月03日
*/
public class ClosingState extends LiftState{
public void open() {
super.context.setLiftState(Context.OPENING_STATE);
super.context.open();
}
public void close() {
System.out.println("电梯门关闭了...");
}
public void run() {
super.context.setLiftState(Context.RUNNING_STATE);
super.context.run() ;
}
public void stop() {
super.context.setLiftState(Context.STOPPING_STATE);
super.context.stop();
}
}
/**
* @author nuist__NJUPT
* @ClassName RunningState
* @description: 运行状态
* @date 2024年02月03日
*/
public class RunningState extends LiftState{
public void open() {
}
public void close() {
}
public void run() {
System.out.println("电梯正在运行...");
}
public void stop() {
super.context.setLiftState(Context.CLOSING_STATE);
super.context.stop();
}
}
/**
* @author nuist__NJUPT
* @ClassName StoppingState
* @description: 停止状态
* @date 2024年02月03日
*/
public class StoppingState extends LiftState{
public void open() {
super.context.setLiftState(Context.OPENING_STATE);
super.context.open() ;
}
public void close() {
super.context.setLiftState(Context.CLOSING_STATE);
super.context.close();
}
public void run() {
super.context.setLiftState(Context.RUNNING_STATE);
super.context.run();
}
public void stop() {
System.out.println("电梯停止了...");
}
}
/**
* @author nuist__NJUPT
* @ClassName OpeningState
* @description: 电梯开启状态
* @date 2024年02月03日
*/
public class OpeningState extends LiftState {
public void open() {
System.out.println("电梯开启了...");
}
public void close() {
// 修改状态,关闭电梯
super.context.setLiftState(Context.CLOSING_STATE);
super.context.close();
}
public void run() {
// 什么都不做
}
public void stop() {
// 不做
}
}
4.定义测试类,实例化环境,并添加相应的状态,最后调用相应的方法。
/**
* @author nuist__NJUPT
* @ClassName Client
* @description: 客户端测试类
* @date 2024年02月03日
*/
public class Client {
public static void main(String[] args) {
Context context = new Context() ;
context.setLiftState(new RunningState()) ;
context.stop() ;
context.run() ;
context.close() ;
context.open() ;
}
}
定义:观察者模式又称为发布订阅模式,定义一对多的依赖关系,让多个观察者同时监听一个主题对象,主题对象变化,观察者可以更好地更新自己。
在观察者模式中有如下角色:
01.抽象被观察者:把所有的观察者对象保存在一个集合里,提供接口可以增删观察者。
02.具体被观察者:将状态存入具体观察者对象,状态发生变化时,给具体观察者发送通知。
03.抽象观察者:定义更新接口,主题更新时候通知自己。
04.具体观察者:实现相应的接口,在主题更新时候,实现相应的状态更新。
观察者模式优缺点与使用场景:
优点:可以实现广播机制,降低了观察者与被观察者之间的耦合。
缺点:观察者收到消息可能会有延时。
使用场景:对象间存在一对多的关系,并且一个对象的状态改变会影响多个对象。
下面通过一个微信公众号推送的案例来学习一下观察者模式,首先微信公众号是被观察者,用户是观察者。
1.定义一个抽象被观察者角色和一个抽线观察者角色,在抽象被观察者中添加观察者并通知观察者,在观察者中根据通知更新相应得状态。
/**
* @author nuist__NJUPT
* @InterfaceName Subject
* @description: 抽象被观察者角色
* @date 2024年02月03日
*/
public interface Subject {
// 添加观察者对象
void attach(Observe observe) ;
// 删除观察者
void detach(Observe observe) ;
// 通知观察者更新消息
void notify(String message) ;
}
/**
* @author nuist__NJUPT
* @InterfaceName Observe
* @description: 抽象观察者角色
* @date 2024年02月03日
*/
public interface Observe {
void update(String message) ;
}
2.定义具体的被观察者对象与具体的观察者对象,在被观察者对象中添加观察者并通知观察者。
import java.util.ArrayList;
import java.util.List;
/**
* @author nuist__NJUPT
* @ClassName SubscriptionSubject
* @description: 具体被观察者对象
* @date 2024年02月03日
*/
public class SubscriptionSubject implements Subject{
// 定义一个集合存储观察者对象
private List userList = new ArrayList() ;
public void attach(Observe observe) {
userList.add(observe) ;
}
public void detach(Observe observe) {
userList.remove(observe) ;
}
public void notify(String message) {
for(Observe observe : userList){
observe.update(message) ;
}
}
}
/**
* @author nuist__NJUPT
* @ClassName User
* @description: 具体的观察者对象
* @date 2024年02月03日
*/
public class User implements Observe {
private String name ;
public User(String name) {
this.name = name;
}
public void update(String message) {
System.out.println(name + "-" + message);
}
}
3.定义客户端测试类,测试观察者模式。
/**
* @author nuist__NJUPT
* @ClassName Client
* @description: 客户端测试类
* @date 2024年02月03日
*/
public class Client {
public static void main(String[] args) {
// 创建被观察者对象
SubscriptionSubject subject = new SubscriptionSubject() ;
// 添加观察者对象
subject.attach(new User("用户张三"));
subject.attach(new User("用户李四"));
subject.attach(new User("用户王五"));
// 被观察者通知观察者
subject.notify("被观察者状态更新了,各位观察者望知悉...");
}
}