行为型模式用于描述程序在运行时复杂的流程控制,即描述多个类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,
它涉及算法与对象间职责的分配。
行为型模式分为类行为模式和对象行为模式,前者采用继承机制来在类间分派行为,后者采用组合或者聚合在对象间分配行为,由于组合或聚合关系比继承关系耦合度低,满足合成复用原则,所以对象行为模式比类行为模式具有更大的灵活性。
行为型模式分为:
- 模版方法模式
- 策略模式
- 命令模式
- 责任链模式
- 状态模式
- 观察者模式
- 中介者模式
- 迭代器模式
- 访问者模式
- 备忘录模式
- 解释器模式
除了模版方法模式和解释器模式是类行为模式,其他全部属于对象行为模式。
一、中介者模式
定义:
- 又叫调停模式,定义一个中介角色来封装一系列对象之间的交互,使原有对象之间的耦合松散,且可以独立的改变他们之间的交互。
结构:
- 抽象中介者:他是中介者的接口,提供了同事对象注册与转发同事对象信息的抽象方法
- 具体中介者:实现中介者接口,定义一个list来管理同事对象,协调各个同事角色之间的交互关系
- 抽象同事类:定义同事类的接口,保存中介者对象,提供同事对象交互的抽象方法,实现所有相互影响同事类的公共功能
- 具体同事类:是抽象同事类的实现者,当需要与其他同事对象交互时,由中介对象负责后续的交互
优点:
- 松散耦合:
中介者模式通过把多个同事对象之间的交互封装在中介者对象里面,从而使同事对象之间松散耦合,基本上可以做到互补依赖,这样一来,同事对象就可以独立的变化和复用,而不再像之前那样牵一处而动全身, - 集中控制交互:
多个同事对象的交互,被封装在中介者对象里面集中管理,使得这些交互行为发生变化时,只需要修改中介者对象就可以,当然如果是已经做好的系统,那么就扩展中介者对象,而各个同事类不需要修改。 - 一对多关联变成一对一关联:
没有使用中介者模式时,同事对象之间的关系通常是一对多的,引入中介者对象之后,中介者对象和同事对象之间的关系通常变成双向的一对一,这会让对象的关系更容易理解和实现。
缺点:
- 当同事类太多,中介者的职责将很大,他会变得复杂而庞大,以至于系统难以维护
使用场景:
- 系统中对象之间存在复杂的引用关系,系统结构混乱且难以理解
- 当想创建一个运行于多个类之间的对象,又不想生成新的子类时。
/*
* 抽象中介者类
* */
public abstract class Mediator {
public abstract void contact(String message, Person person);
}
/*
* 具体的中介者类
* */
@Data
public class MediatorStructure extends Mediator{
private Tenant tenant;
private HouseOwner houseOwner;
@Override
public void contact(String message, Person person) {
if (person == tenant) {
tenant.getMessage(message);
} else {
houseOwner.getMessage(message);
}
}
}
/*
*抽象同事类
* */
@AllArgsConstructor
public abstract class Person {
protected String name;
protected Mediator mediator;
}
/*
* 具体同事类
* */
public class HouseOwner extends Person{
public HouseOwner(String name, Mediator mediator) {
super(name, mediator);
}
/*和中介联系*/
public void contact(String message) {
mediator.contact(message, this);
}
public void getMessage(String message){
System.out.println("房主" + name + "获取的租房信息是:" + message);
}
}
/*
* 具体同事类
* */
public class Tenant extends Person{
public Tenant(String name, Mediator mediator) {
super(name, mediator);
}
/*和中介联系*/
public void contact(String message) {
mediator.contact(message, this);
}
public void getMessage(String message){
System.out.println("租房者" + name + "获取的租房信息是:" + message);
}
}
public class test {
public static void main(String[] args) {
MediatorStructure mediatorStructure = new MediatorStructure();
Tenant tenant = new Tenant("张三", mediatorStructure);
HouseOwner houseOwner = new HouseOwner("李四", mediatorStructure);
mediatorStructure.setHouseOwner(houseOwner);
mediatorStructure.setTenant(tenant);
tenant.contact("我要租李四的房子!!");
houseOwner.contact("我这里有房子,你要吗?");
}
}
二、 命令模式
定义:
- 将一个请求封装成一个对象,使发出请求的责任和执行请求的责任分隔开,这样两者之间通过命令对象进行沟通,这样方便将命令对象进行存储,传递,调用,增加,管理
结构:
- 抽象命令类:定义命令的接口,声明执行的方法。
- 具体命令角色:具体的命令,实现命令接口,通常会持有接收者,并调用接收者的功能来完成命令执行的具体操作。
- 实现者/接收者角色:接收者,真正执行命令的对象,任何类都可能成为一个接收者,只要它能够实现命令要求实现的相应功能。
- 调用者/请求者角色:要求命令对象执行请求,通常会持有命令对象,可以持有很多命令对象,这个是客户端真正触发命令并要求命令执行相应操作的地方,也就是说相当于使用命令对象的入口。
优点:
- 降低系统的耦合度,命令模式能将调用操作对象与实现该操作对象的对象解耦
- 增加或删除命令非常方便。采用命令模式增加与删除命令不会影响其他类,它满足开闭原则,对扩展比较灵活
- 可以实现宏命令,命令模式可以与组合模式结合,将多个命令装配成一个组合命令,即宏命令
- 方便实现undo和redo操作,命令模式可以与后面介绍的备忘录模式结合,实现命令的撤销与恢复
缺点:
- 使用命令模式可能会导致某些系统有过多的具体命令类
- 系统结构更加复杂
使用场景:
- 系统需要将请求调用者和请求接收者解耦,使得调用者和接收者不直接交互
- 系统需要在不同的时间制定请求,将请求排队和执行请求
- 系统需要支持命令的撤销(undo)操作和恢复(redo)操作
请求者类中包含具体命令类
具体命令类中包含接收者和订单类
/*
* 抽象命令类
* */
public interface Command {
void execute();
}
@AllArgsConstructor
public class OrderCommand implements Command{
//持有接收者对象
private SeniorChef receiver;
private Order order;
@Override
public void execute() {
System.out.println(order.getDiningTable() + "桌的订单:");
Map<String, Integer> foodDir = order.getFoodDir();
Set<String> keySet = foodDir.keySet();
for (String foodName : keySet) {
receiver.makeFood(foodName, foodDir.get(foodName));
}
System.out.println(order.getDiningTable() + "桌的饭已经做好!!");
}
}
/*
* 接收者
* */
public class SeniorChef {
public void makeFood(String name, int num) {
System.out.println(num + "份" + name);
}
}
/*
* 请求者
* */
public class Waiter {
private List<Command> commands = new ArrayList<>();
public void setCommands(Command command) {
commands.add(command);
}
public void orderUp(){
System.out.println("大厨,新订单来了!!");
for (Command command : commands) {
if (command != null) {
command.execute();
}
}
}
}
@Data
public class Order {
private int diningTable;
private Map<String, Integer> foodDir = new HashMap<>();
public void setFood(String name, int num){
foodDir.put(name, num);
}
}
public class test {
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("尖椒肉丝", 1);
order2.setFood("小杯雪碧", 2);
SeniorChef seniorChef = new SeniorChef();
OrderCommand orderCommand1 = new OrderCommand(seniorChef, order1);
OrderCommand orderCommand2 = new OrderCommand(seniorChef, order2);
Waiter waiter = new Waiter();
waiter.setCommands(orderCommand1);
waiter.setCommands(orderCommand2);
waiter.orderUp();
}
}
三、备忘录模式
定义:
- 又叫快照模式,在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,以便以后当需要时能将该对象恢复到原先保存的状态。
结构:
- 发起人角色:记录当前时刻的内部状态信息,提供创建备忘录和恢复备忘录数据的功能,实现其他业务功能,他可以访问备忘录中的所有信息
- 备忘录角色:负责存储发起人的内部状态,在需要的时候提供这些内部状态给发起人。
- 管理者角色:对备忘录进行管理,提供保存与获取备忘录的功能,但其不能对备忘录的内容进行访问与修改。
备忘录有两个等效的接口: - 窄接口:管理者对象看到的是备忘录的窄接口,这个窄接口只允许他把备忘录对象传给其他的对象
- 宽接口:与管理者看到的窄接口相反,发起人对象可以看到一个宽接口,这个宽接口允许他读取所有的数据,以便根据这些数据恢复发起人对象的内部状态
分类:
- 白箱备忘录:备忘录角色对任何一个对象都提供一个宽接口,备忘录角色的内部所存储的状态就对所有对象公开。
- 黑箱备忘录:备忘录角色对发起人对象提供一个宽接口,而对其他对象提供一个窄接口,在Java语言中,实现双重接口的方法就是将备忘录类设计成发起人类的内部成员类。
优点:
- 提供了一种可以恢复状态的机制,当用户需要时能够比较方便的将数据恢复到某个历史状态
- 实现了内部状态的封装,初创建它的发起人对象外,其他对象都不能访问这些状态信息(针对黑箱)
- 简化了发起人类,发起人不需要管理和保存其内部状态的各个备份,所有状态信息都保存在备忘录中,并由管理者进行管理,这符合单一职责原则
缺点:
资源消耗大,如果要保存的内部状态信息过多或特别频繁,将会占用比较大的内存资源
使用场景:
- 需要保存与恢复数据的场景,如玩游戏时中间结果的保存
- 需要提供一个回滚操作的场景,还有数据库中的事务操作。
(1)白箱备忘录
/*
* 备忘录角色
* */
@AllArgsConstructor
@NoArgsConstructor
@Data
public class RoleStateMemento {
private int vit;
private int atk;
private int def;
}
/*
* 发起人角色
* */
@Data
public class GameRole {
private int vit;
private int atk;
private int def;
public void initState(){
this.vit = 100;
this.atk = 100;
this.def = 100;
}
public void fight(){
this.vit = 0;
this.atk = 0;
this.def = 0;
}
public RoleStateMemento saveState(){
return new RoleStateMemento(vit, atk, def);
}
public void recoverState(RoleStateMemento roleStateMemento){
this.vit = roleStateMemento.getVit();
this.atk = roleStateMemento.getAtk();
this.def = roleStateMemento.getDef();
}
public void stateDisplay(){
System.out.println("角色生命力:" + vit);
System.out.println("角色攻击力:" + atk);
System.out.println("角色防御力:" + def);
}
}
/*
* 备忘录对象管理对象
* */
@Data
public class RoleStateCareTaker {
private RoleStateMemento roleStateMemento;
}
public class test {
public static void main(String[] args) {
System.out.println("---------大战boss前---------");
GameRole gameRole = new GameRole();
gameRole.initState();
gameRole.stateDisplay();
RoleStateCareTaker roleStateCareTaker = new RoleStateCareTaker();
roleStateCareTaker.setRoleStateMemento(gameRole.saveState());
System.out.println("---------大战boss后---------");
gameRole.fight();
gameRole.stateDisplay();
System.out.println("---------恢复之前的状态---------");
gameRole.recoverState(roleStateCareTaker.getRoleStateMemento());
gameRole.stateDisplay();
}
}
(2)黑箱备忘录
/*
* 发起人角色
* */
@Data
public class GameRole {
private int vit;
private int atk;
private int def;
public void initState(){
this.vit = 100;
this.atk = 100;
this.def = 100;
}
public void fight(){
this.vit = 0;
this.atk = 0;
this.def = 0;
}
public Memento saveState(){
return new RoleStateMemento(vit, atk, def);
}
public void recoverState(Memento memento){
RoleStateMemento roleStateMemento = (RoleStateMemento) memento;
this.vit = roleStateMemento.getVit();
this.atk = roleStateMemento.getAtk();
this.def = roleStateMemento.getDef();
}
public void stateDisplay(){
System.out.println("角色生命力:" + vit);
System.out.println("角色攻击力:" + atk);
System.out.println("角色防御力:" + def);
}
@AllArgsConstructor
@NoArgsConstructor
@Data
public class RoleStateMemento implements Memento{
private int vit;
private int atk;
private int def;
}
}
/*
* 备忘录接口,对外提供窄接口
* */
public interface Memento {
}
/*
* 备忘录对象管理对象
* */
@Data
public class RoleStateCareTaker {
private Memento memento;
}
public class test {
public static void main(String[] args) {
System.out.println("---------大战boss前---------");
GameRole gameRole = new GameRole();
gameRole.initState();
gameRole.stateDisplay();
RoleStateCareTaker roleStateCareTaker = new RoleStateCareTaker();
roleStateCareTaker.setMemento(gameRole.saveState());
System.out.println("---------大战boss后---------");
gameRole.fight();
gameRole.stateDisplay();
System.out.println("---------恢复之前的状态---------");
gameRole.recoverState(roleStateCareTaker.getMemento());
gameRole.stateDisplay();
}
}
三、 模版方法模式
定义:
- 定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤。
结构:
- 抽象类:负责给出一个算法的轮廓和骨架,它由一个模版方法和若干个基本方法构成。
- 模版方法:定义了算法骨架,按某种顺序调用其包含的基本方法
- 基本方法:是实现算法各个步骤的方法,是模版方法的组成部分,基本方法又可以分为三种。
- 抽象方法:一个抽象方法由抽象类声明,由具体子类实现。
- 具体方法:一个具体方法由一个抽象类或具体类声明并实现,其子类可以进行覆盖也可以直接继承
- 钩子方法:在抽象类中已经实现,包括用于判断的逻辑方法和需要子类重写的空方法两种。一般钩子方法是用于判断的逻辑方法,这类方法名一般为isXxx,返回值类型为Boolean类型
- 具体子类:实现抽象类中所定义的抽象方法和钩子方法,他们是一个顶级逻辑的组成部分。
优点:
- 提高代码的复用性:
将相同部分的代码放在抽象的父类中,而将不同的代码放入不同的子类中 - 实现了反向控制:
通过一个父类调用其子类的操作,通过对子类的具体实现扩展不同的行为,实现了反向的控制,并符合开闭原则
缺点:
- 对每个不同的实现都需要定一个子类,这会导致类的个数增加,系统更爱庞大,设计也更加抽象
- 需要通过子类来决定父类算法中某个步骤是否执行,实现子类对父类的反向控制
适用场景:
- 算法的整体步骤很固定,但其中个别部分易变时,这时候可以使用模版方法模式,将容易变的部分抽象出来,供子类实现
- 需要通过子类来决定父类算法中某个步骤是否执行,实现子类对父类的反向控制
public abstract class AbstractClass {
// 模版方法
public void cookProcess(){
pourOil();
heatOil();
pourVegetable();
pourSauce();
fry();
}
public void pourOil(){
System.out.println("倒油");
}
public void heatOil(){
System.out.println("热油");
}
public abstract void pourVegetable();
public abstract void pourSauce();
public void fry(){
System.out.println("炒熟");
}
}
public class concreteClass_BaoCai extends AbstractClass{
@Override
public void pourVegetable() {
System.out.println("下锅的蔬菜是包菜");
}
@Override
public void pourSauce() {
System.out.println("下锅的酱料是辣椒");
}
}
public class concreteClass_CaiXin extends AbstractClass{
@Override
public void pourVegetable() {
System.out.println("下锅的蔬菜是菜心");
}
@Override
public void pourSauce() {
System.out.println("下锅的酱料是蒜蓉");
}
}
public class test {
public static void main(String[] args) {
concreteClass_BaoCai concreteClass_baoCai = new concreteClass_BaoCai();
concreteClass_baoCai.cookProcess();
}
}
五、状态模式
定义:
- 状态模式(State Pattern)也叫作状态机模式(StateMachine Pattern),允许对象在内部状态发生改变时改变它的行为,对象看起来好像修改了它的类,属于行为型设计模式。状态模式中类的行为是由状态决定的,在不同的状态下有不同的行为。其意图是让一个对象在其内部改变的时候,行为也随之改变。状态模式的核心是状态与行为绑定,不同的状态对应不同的行为。
结构:
- 环境类角色(Context):定义客户端需要的接口,内部维护一个当前状态实例,并负责具体状态的切换。
- 抽象状态角色(IState):定义该状态下的行为,可以有一个或多个行为。
- 具体状态角色(ConcreteState):具体实现该状态对应的行为,并且在需要的情况下进行状态切换。
优点:
- 将所有与某个状态有关的行为放到一个类中,并且可以方便地增加新的状态,只需要改变对象状态即可改变对象的行为
- 允许状态转换逻辑与状态对象合成一体,而不是某个巨大条件语句块。
缺点:
- 状态模式的使用必然会增加系统和对象的个数
- 状态模式的结构和实现都较为复杂,如果使用不当将导致程序结构和代码的混乱
- 状态模式对开闭原则的支持并不太好
使用场景:
- 当一个对象的行为取决于他的状态,并且它必须在运行时根据状态改变它的行为时,就可以考虑使用状态模式
- 一个操作中含有庞大的分支结构,并且这些分支决定于对象的状态时。
/*
* 环境角色类
* */
public class Context {
public final static OpingState OPENING_STATE = new OpingState();
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 LifeState lifeState;
public LifeState getLifeState() {
return lifeState;
}
public void setLifeState(LifeState lifeState) {
this.lifeState = lifeState;
this.lifeState.setContext(this);
}
public void open(){
this.lifeState.open();
}
public void close(){
this.lifeState.close();
}
public void run(){
this.lifeState.run();
}
public void stop(){
this.lifeState.stop();
}
}
public class OpingState extends LifeState{
@Override
public void open() {
System.out.println("电梯开启。。。。");
}
@Override
public void close() {
super.context.setLifeState(Context.CLOSING_STATE);
super.context.close();
}
@Override
public void run() {
}
@Override
public void stop() {
}
}
public class RunningState extends LifeState{
@Override
public void open() {
}
@Override
public void close() {
}
@Override
public void run() {
System.out.println("电梯正在运行");
}
@Override
public void stop() {
super.context.setLifeState(Context.STOPPING_STATE);
super.context.stop();
}
}
public class StoppingState extends LifeState{
@Override
public void open() {
super.context.setLifeState(Context.OPENING_STATE);
super.context.open();
}
@Override
public void close() {
super.context.setLifeState(Context.CLOSING_STATE);
super.context.close();
}
@Override
public void run() {
super.context.setLifeState(Context.RUNNING_STATE);
super.context.run();
}
@Override
public void stop() {
System.out.println("电梯停止了");
}
}
public class ClosingState extends LifeState{
@Override
public void open() {
super.context.setLifeState(Context.OPENING_STATE);
super.context.open();
}
@Override
public void close() {
System.out.println("电梯门关闭");
}
@Override
public void run() {
super.context.setLifeState(Context.RUNNING_STATE);
super.context.run();
}
@Override
public void stop() {
super.context.setLifeState(Context.STOPPING_STATE);
super.context.stop();
}
}
/*
* 抽象状态类
* */
public abstract class LifeState {
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();
}
public class test {
public static void main(String[] args) {
Context context = new Context();
context.setLifeState(new RunningState());
// context.open();
// context.close();
// context.run();
context.stop();
}
}
六、策略模式
定义:
- 该模式定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的变化不会影响使用算法的客户,策略模式属于对象行为模式,它通过对算法进行封装,把使用算法的责任和算法的实现部分割开来,并委托给不同的对象,对这些算法进行管理。
结构:
- 抽象策略类:这是一个抽象角色,通常由一个接口或抽象类实现,此角色给出所有的具体策略类所需的接口。
- 具体策略类:实现了抽象策略类定义的接口,提供具体的算法实现或行为
- 环境类:持有一个策略类的引用,最终给客户端使用。
优点:
- 策略类之间可以自由切换,由于策略类都实现同一个接口,所以他们之间可以自由切换。
- 易于扩展,增加一个新的策略类只需要添加一个具体的策略类即可,基本不需要改变原有的代码,符合开闭原则
- 避免使用多重条件选择语句,充分体现面向对象的设计思想
缺点:
- 客户端必须知道所有的策略类,并自行决定使用哪一个策略类
- 策略模式将造成产生很多策略类,可以通过使用享元模式在一定程度上减少对象的数量
使用场景:
- 一个系统需要动态地在几个算法中选择一种时,可以将每个算法封装到策略类中
- 一个类定义了很多行为,并且这些行为在这个类的操作中以多个条件语句的形式出现,可以将每个条件分支移入到他们各自的策略类中以那个代替这些条件语句
- 系统中个算法彼此完全独立,且要求对客户隐藏具体算法的实现细节时
- 系统要求使用算法的客户不应该知道其操作的数据时,可使用策略模式来隐藏与算法相关的数据结构
- 多个类只区别在表现行为不同,可以使用策略模式,在运行时动态选择具体要执行的行为。
public interface Strategy {
void show();
}
/*
* 具体策略类
* */
public class StrategyA implements Strategy{
@Override
public void show() {
System.out.println("买一送一");
}
}
/*
* 具体策略类
* */
public class StrategyB implements Strategy{
@Override
public void show() {
System.out.println("买200减50");
}
}
/*
* 具体策略类
* */
public class StrategyC implements Strategy{
@Override
public void show() {
System.out.println("满1000加1换购任意200元以下的商品");
}
}
/*
* 环境类
* */
@Data
public class SalesMan {
private Strategy strategy;
public SalesMan(Strategy strategy) {
this.strategy = strategy;
}
public void salesManShow(){
strategy.show();
}
}
public class test {
public static void main(String[] args) {
SalesMan salesMan = new SalesMan(new StrategyA());
salesMan.salesManShow();
}
}
七、观察者模式
定义:
- 又被称为发布-订阅模式,它定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象,这个主题对象在状态变化时会通知所有的观察者对象,使他们能够自动的更新自己。
结构:
- 抽象主题(抽象被观察者),抽象主题角色把所有的观察者对象保存在一个集合中,每个主题都可以有任意数量的观察者,抽象主题提供一个接口,可以增加和删除观察者对象。
- 具体主题(具体被观察者),该角色将有关状态存入具体观察者对象,在具体主题内部状态发生改变时,给所有注册过的观察者发送通知
- 抽象观察者,是观察者的抽象类,它定义了一个更新接口,使得在得到主题更改通知时更新自己
- 具体观察者,实现抽象观察者定义的更新接口,以便在得到主题更改通知时更新自身的状态。
优点:
- 降低目标与观察者之间的耦合关系,两者之间是抽象耦合
- 被观察者发送通知,所有注册的观察者都能收到信息,可以实现广播机制
缺点:
- 如果被观察者非常的多,那么所有的观察者都会收到被观察者发送的通知会耗时。
- 如果被观察者有循环依赖的话,那么被观察者发送通知会使观察者循环调用,会导致系统崩溃
使用场景:
- 对象间存在一对多关系,一个对象的状态改变会影响其他对象
- 当一个抽象模型有两个方面,其中一个方面依赖于另一个方面时
/*
* 抽象观察者
* */
public interface Observer {
void update(String message);
}
/*
* 具体的观察者角色
* */
@AllArgsConstructor
public class WeiXinUser implements Observer{
private String name;
@Override
public void update(String message) {
System.out.println(name + "-" + message);
}
}
/*
* 抽象主题角色类
* */
public interface Subject {
void attach(Observer observer);
void detach(Observer observer);
void notify(String message);
}
/*
* 具体主题类
* */
public class SubscriptionSubject implements Subject{
private List<Observer> observerList = new ArrayList<>();
@Override
public void attach(Observer observer) {
observerList.add(observer);
}
@Override
public void detach(Observer observer) {
observerList.remove(observer);
}
@Override
public void notify(String message) {
for (Observer observer : observerList) {
observer.update(message);
}
}
}
public class test {
public static void main(String[] args) {
SubscriptionSubject subscriptionSubject = new SubscriptionSubject();
subscriptionSubject.attach(new WeiXinUser("王二"));
subscriptionSubject.attach(new WeiXinUser("李四"));
subscriptionSubject.attach(new WeiXinUser("麻子"));
subscriptionSubject.notify("吃饭啦");
}
}
八、 解释器模式
定义:
- 给定一个语言,定义它的文法表示,并定义一个解释器,这个解释器使用该标识来解释语言中的句子。在解释器模式中,我们需要将待解决的问题,提取出规则,抽象为一种语言,比如加减法运算,规则为:由数值和±符号组成的合法序列,"1+3-2"就是这种语言的句子。
结构:
- 抽象表达式角色:定义解释器的接口,约定解释器的解释操作,主要包含解释方法interpret()
- 终结符表达式角色:是抽象表达式的子类,用来实现文法中与终结符相关的操作,文法中每一个终结符都有一个具体中介表达式与之对应
- 非终结符表达式角色:也是抽象表达式的子类,用来实现文法中与非终结符相关的操作,文法中的每条规则都对应于一个非终结符表达式
- 环境角色:通常包含各个解释器需要的数据或者公共的功能,一般用来传递被所有解释器共享的数据,后面的解释器可以从这里获取这些值
- 客户端角色:主要任务是将需要分析的句子或表达式转换成使用解释器对象描述的抽象语法树,然后调用解释器的解释方法,当然也可以通过环境角色间接访问解释器的解释方法。
优点:
- 易于改变和扩展文法:
由于在解释器模式中使用类来表示语言的文法规则,因此可以通过继承机制来改变和扩展文法,每一条文法规则都可以表示为一个类,因此可以方便的实现一个简单的语言。 - 实现文法比较容易:
在抽象语法树中每个表达式节点类的实现方式都是相似的,这些类的代码编写都不会特别复杂。 - 增加新的解释表达式比较方便:
如果用户需要增加新的解释表达式,只需要对应增加一个新的表达式或非终结符表达式类,原有表达式类代码无需修改,符合开闭原则
缺点:
- 对于复杂文法难以维护:
在解释器模式中,每一条规则至少需要定义一个类,因此如果一个语言包含太多文法规则,类的个数将会急剧增加,导致系统难以管理和维护 - 执行效率较低:
由于解释器模式中使用了大量的循环和递归调用,因此解释较为复杂的句子时其速度很慢,而且代码的调试过程也比较麻烦。
使用场景:
- 当语法的文法比较简单,且执行效率不是关键问题时
- 当问题重复出现,且可以用一种简单的语言来进行表达时
- 当一个语言需要解释执行,并且语言中的句子可以表示为一个抽象语法树的时候
/*
* 抽象表达式类
* */
public abstract class AbstractExpression {
public abstract int interpret(Context context);
}
import lombok.AllArgsConstructor;
@AllArgsConstructor
public class Minus extends AbstractExpression {
private AbstractExpression left;
private AbstractExpression right;
@Override
public int interpret(Context context) {
return left.interpret(context) - right.interpret(context);
}
@Override
public String toString() {
return "(" + left.toString() + "-" + right.toString() + ")";
}
}
import lombok.AllArgsConstructor;
@AllArgsConstructor
public class Plus extends AbstractExpression {
private AbstractExpression left;
private AbstractExpression right;
@Override
public int interpret(Context context) {
return left.interpret(context) + right.interpret(context);
}
@Override
public String toString() {
return "(" + left.toString() + "+" + right.toString() + ")";
}
}
/*
* 变量类
* */
@AllArgsConstructor
@Data
public class Variable extends AbstractExpression{
private String name;
@Override
public int interpret(Context context) {
return context.getValue(this);
}
@Override
public String toString() {
return name;
}
}
/*
* 环境角色类
* */
public class Context {
private Map<Variable, Integer> map = new HashMap<>();
public void assign(Variable var, Integer value) {
map.put(var, value);
}
public int getValue(Variable var){
return map.get(var);
}
}
public class test {
public static void main(String[] args) {
Context context = new Context();
Variable a = new Variable("a");
Variable b = new Variable("b");
Variable c = new Variable("c");
Variable d = new Variable("d");
context.assign(a, 1);
context.assign(b, 2);
context.assign(c, 3);
context.assign(d, 4);
// a+b-c+d
AbstractExpression abstractExpression = new Minus(a, new Plus(new Minus(b,c), d));
int result = abstractExpression.interpret(context);
System.out.println(abstractExpression + "=" + result);
}
}
九、访问者模式
定义:
- 封装一些作用于某种数据结构中的各元素的操作,他可以在不改变这个数据结构的前提下定义作用于这些元素的新的操作。
结构:
- 抽象访问者角色:定义了对每个元素访问的行为,它的参数就是可以访问的元素,它的方法个数理论上来讲与元素个数是一样的,从这点不难看出,访问者模式要求元素的个数不能改变。
- 具体访问者角色:给出对每个元素类访问时所产生的具体行为
- 抽象元素角色:定义了一个接收访问者的方法,其意义是指,每个元素都要可以被访问者访问
- 具体元素角色:提供受访问者的具体实现,而这个具体实现,通常情况下是使用访问者提供的访问该元素类的方法
- 对象结构角色:定义当中所提到的对象结构,对象结构是一个抽象表述,具体点可以理解为一个具有容器性质或者复合对象特性的类,他会含有一组元素,并且可以迭代这些元素,供访问者访问。
优点:
- 扩展性好:在不修改对象结构中元素的情况下,为对象结构中的元素添加新的功能
- 复用性好:通过访问者来定义整个对象结构通用的功能,从而提高复用程度
- 分离无关行为:通过访问者来分离无关的行为,把相关的行为封装在一起,构成一个访问者,这样每个访问者的功能都比较单一
缺点:
- 对象结构变化很困难:
在访问者模式中,每增加一个新的元素类,都要在每个具体访问者类中增加相应的具体操作,这违背了开闭原则 - 违反了依赖倒转原则:
访问者模式依赖了具体类,而没有依赖抽象类。
使用场景:
- 对象结构相对稳定,但其操作算法经常变化的程序
- 对象结构中的对象需要提供多种不同且不相关的操作,而且要避免让这些操作的变化影响到对象的结构
/*
* 抽象元素角色类
* */
public interface Animal {
void accept(Person person);
}
/*
* 具体元素角色类
* */
public class Cat implements Animal{
@Override
public void accept(Person person) {
person.feed(this);
System.out.println("好好吃,喵喵");
}
}
/*
* 具体元素角色类
* */
public class Dog implements Animal{
@Override
public void accept(Person person) {
person.feed(this);
System.out.println("好好吃,汪汪");
}
}
/*
* 抽象访问者角色类
* */
public interface Person {
void feed(Cat cat);
void feed(Dog dog);
}
/*
* 具体访问角色类
* */
public class Owner implements Person {
@Override
public void feed(Cat cat) {
System.out.println("主人喂食猫");
}
@Override
public void feed(Dog dog) {
System.out.println("主人喂食狗");
}
}
/*
* 具体访问角色类
* */
public class SomeOne implements Person {
@Override
public void feed(Cat cat) {
System.out.println("其他人喂食猫");
}
@Override
public void feed(Dog dog) {
System.out.println("其他人喂食狗");
}
}
/*
* 对象结构类
* */
public class Home {
private List<Animal> nodeList = new ArrayList<>();
public void add(Animal animal){
nodeList.add(animal);
}
public void action(Person person){
for (Animal animal : nodeList) {
animal.accept(person);
}
}
}
public class test {
public static void main(String[] args) {
Home home = new Home();
home.add(new Dog());
home.add(new Cat());
Owner owner = new Owner();
home.action(owner);
SomeOne someOne = new SomeOne();
home.action(someOne);
}
}
十、责任链模式
定义:
- 又名职责责链模式,为了避免请求发送者与多个请求处理者耦合在一起,将所有请求的处理者通过前一个对象记住下一个对象的引用,而连成一条链,当请求发生时,可将请求沿着这条链传递,直到有对象处理它为止。
结构:
- 抽象处理者角色:定义一个处理请求的接口,包含抽象处理方法和一个后继连接
- 具体处理者角色:实现抽象处理者的处理方法,判断是否能处理本次请求,如果可以处理请求则处理,否则将该请求转给他的后继者
- 客户类角色:创建处理链,并向链头的具体处理者对象提交请求,它不关心处理细节和请求的传递过程
优点:
- 降低了对象之间的耦合,该模式降低了请求发送者和接收者的耦合度
- 增强了系统的可扩展性,可以根据需要增加新的请求处理类,满足开闭原则
- 增强了给对象指派职责的灵活性, 当工作流程发生变化,可以动态地改变链内成员或者修改他们的次序,也可以动态新增或删除责任
- 责任链简化了对象之间的连接,一个对象只需要保持一个指向其后继者的引用,不需保持其他所有后继者的引用,这避免了众多的if或者if。。。else语句
- 责任分担,每个类只需要处理自己该处理的工作,不能处理的传递给下个对象完成,明确各类的责任范围,符合类的单一职责原则
缺点:
- 不能保证每个请求一定被处理,由于一个请求没有明确的接收者,所以不能保证它一定会被处理,该请求可能一直传到链的末端都得不到处理。
- 对比较长的职责链,请求的处理可能涉及多个处理对象,系统性能将受到一定的影响
- 职责链建立的合理性要靠客户端来保证,增加了客户端的复杂性,可能会由于职责连的错误设置而导致系统出错,如果可能会造成循环调用。
/*
* 抽象处理者类
* */
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 nexHandler;
public Handler(int numStart) {
this.numStart = numStart;
}
public Handler(int numStart, int numEnd) {
this.numStart = numStart;
this.numEnd = numEnd;
}
public void setNexHandler(Handler nexHandler) {
this.nexHandler = nexHandler;
}
protected abstract void handleLeave(LeaveRequest leaveRequest);
public final void submit(LeaveRequest leaveRequest){
this.handleLeave(leaveRequest);
if (this.nexHandler != null && leaveRequest.getNum() > this.numEnd) {
this.nexHandler.submit(leaveRequest);
} else {
System.out.println("流程结束!");
}
}
}
/*
* 具体的处理者
* */
public class GroupLeader extends Handler{
public GroupLeader(){
super(Handler.NUM_ONE, Handler.NUM_THREE);
}
@Override
protected void handleLeave(LeaveRequest leaveRequest) {
System.out.println(leaveRequest.getName() + "请假" + leaveRequest.getNum() + "天," + leaveRequest.getContent());
System.out.println("部门经理审批:同意");
}
}
/*
* 具体的处理者
* */
public class GeneralManager extends Handler{
public GeneralManager(){
super(Handler.NUM_THREE, Handler.NUM_SEVEN);
}
@Override
protected void handleLeave(LeaveRequest leaveRequest) {
System.out.println(leaveRequest.getName() + "请假" + leaveRequest.getNum() + "天," + leaveRequest.getContent());
System.out.println("总经理审批:同意");
}
}
/*
* 具体的处理者
* */
public class Manager extends Handler{
public Manager(){
super(0, Handler.NUM_ONE);
}
@Override
protected void handleLeave(LeaveRequest leaveRequest) {
System.out.println(leaveRequest.getName() + "请假" + leaveRequest.getNum() + "天," + leaveRequest.getContent());
System.out.println("小组长审批:同意");
}
}
@Data
@AllArgsConstructor
public class LeaveRequest {
private String name;
private int num;
private String content;
}
public class Test {
public static void main(String[] args) {
LeaveRequest leaveRequest = new LeaveRequest("小明", 1, "身体不适");
GroupLeader groupLeader = new GroupLeader();
Manager manager = new Manager();
GeneralManager generalManager = new GeneralManager();
// 设置处理者链
generalManager.setNexHandler(manager);
manager.setNexHandler(generalManager);
groupLeader.submit(leaveRequest);
}
}
十一、迭代器模式
定义:
- 提供一个对象来顺序访问聚合对象中一系列数据,而暴露聚合对象的内部表示。
结构:
- 抽象聚合角色:定义存储、添加、删除聚合元素以及创建迭代器对象的接口
- 具体聚合角色:实现抽象聚合类,返回一个具体迭代器的实例
- 抽象迭代器角色:定义访问和遍历聚合元素的接口,通常包含hasNext,next等方法
- 具体迭代器角色:实现抽象迭代器接口中所定义的方法,完成对聚合对象的遍历,记录遍历的当前位置
优点:
- 它支持以不同的方式遍历一个聚合对象,在同一个聚合对象上可以定义多种遍历方式,在迭代器模式中只需要用一个不同的迭代器来替换原有迭代器即可改变遍历算法,我们也可以定义迭代器的子类以支持新的遍历方式
- 迭代器简化了聚合类,由于引进了迭代器,在原有聚合对象中不需要再自行提供数据遍历等方法,这样可以简化聚合类的设计
- 在迭代器模式中,由于引入了抽象层,增加新的聚合类和迭代器类都很方便,无须修改原有代码,满足开闭原则
缺点:
增加了类的个数,在一定程度上增加了系统的复杂性
使用场景:
- 当需要为聚合对象提供多种遍历方式时
- 当需要为遍历不同的聚合结构提供一个统一的接口时
- 当访问一个聚合对象的内容而无须暴露其内部细节的表示时
@Data
@AllArgsConstructor
public class Student {
private String name;
private String number;
@Override
public String toString() {
return "Student{" +
"name='" + name + ''' +
", number='" + number + ''' +
'}';
}
}
/*
* 抽象聚合角色接口
* */
public interface StudentAggregate {
void addStudent(Student student);
void removeStudent(Student student);
StudentIterator getStudentIterator();
}
/*
* 具体的聚合角色
* */
public class StudentAggregateImpl implements StudentAggregate {
private List<Student> students = new ArrayList<>();
@Override
public void addStudent(Student student) {
students.add(student);
}
@Override
public void removeStudent(Student student) {
students.remove(student);
}
@Override
public StudentIterator getStudentIterator() {
return new StudentIteratorImpl(students);
}
}
/*
* 抽象迭代角色
* */
public interface StudentIterator {
boolean hasNext();
Student next();
}
/*
* 具体的迭代器角色类
* */
public class StudentIteratorImpl implements StudentIterator {
private List<Student> list;
public StudentIteratorImpl(List<Student> list) {
this.list = list;
}
private int position = 0;
@Override
public boolean hasNext() {
return position < list.size();
}
@Override
public Student next() {
Student student = list.get(position);
position++;
return student;
}
}
public class test {
public static void main(String[] args) {
StudentAggregateImpl studentAggregate = new StudentAggregateImpl();
studentAggregate.addStudent(new Student("A", "001"));
studentAggregate.addStudent(new Student("B", "002"));
studentAggregate.addStudent(new Student("C", "003"));
studentAggregate.addStudent(new Student("D", "004"));
studentAggregate.addStudent(new Student("E", "005"));
StudentIterator studentIterator = studentAggregate.getStudentIterator();
while (studentIterator.hasNext()) {
Student next = studentIterator.next();
System.out.println(next.toString());
}
}
}