软件设计模式是前辈们代码设计经验的总结,可以反复使用。设计模式共分为3大类,创建者模式(6种)、结构型模式(7种)、行为型模式(11种),一共24种设计模式,软件设计一般需要满足7大基本原则。下面通过5章的学习一起来看看设计模式的魅力吧。
行为模式(11种):本质是描述类与对象协助完成单个对象无法完成的任务,以及怎么分配职责。
包括:模板方法、策略、命令、责任链、状态、观察者、中介者模式、迭代器、访问者、备忘录、解释器。
目录
1.中介者模式
2.迭代器模式
3.访问者模式
4.备忘录模式
5.解释器模式
1.中介者模式
定义:中介者模式又称为调停模式,定义一个中介角色来封装一系列对象的交互操作,使得原有对象之间耦合度减小。
中介者模式包含以下角色:
01.抽象中介者:中介者接口,提供同事对象注册与转发同事对象的抽象方法。
02.具体中介者:协调各个同事之间的关系。
03.抽象同事类:定义同事类的接口,保存中介者对象,提供同事对象交互的抽象方法。
04.具体同事类:抽象同事类的实现者。
中介者模式的优缺点及使用场景:
优点:松散耦合、集中控制。
缺点:同事类比较多的情况下,中介者的职责将变得复杂而庞大。
使用场景:系统中对象存在复杂的引用关系,系统结构混乱且难以理解。
下面通过一个租房的案例学习一下中介者模式,房主和租客是同事类,中介就是中介类。
1.首先定义一个抽象同事类,包含房主与租客的名字,以及中介对象。
/*** @author nuist__NJUPT* @ClassName Person* @description: 抽象同事类* @date 2024年02月03日*/
public abstract class Person {protected String name ;protected Mediator mediator ;public Person(String name, Mediator mediator) {this.name = name;this.mediator = mediator;}
}
2.然后定义一个抽象的中介对象,用于和房主以及租客进行沟通。
/*** @author nuist__NJUPT* @ClassName Mediator* @description: 抽象中介者类* @date 2024年02月03日*/
public abstract class Mediator {public abstract void contact(String message, Person person) ;
}
3.定义具体的同事角色类,即租客类与房主类,需要获取信息并与中介进行沟通。
/*** @author nuist__NJUPT* @ClassName HouseOwner* @description: 具体的同事角色类* @date 2024年02月03日*/
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);}
}
/*** @author nuist__NJUPT* @ClassName Tenant* @description: 具体同事角色类* @date 2024年02月03日*/
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);}}
4.定义具体的中介类,用于和房主以及租客进行沟通。
/*** @author nuist__NJUPT* @ClassName MediatorStructure* @description: 具体的中介者类* @date 2024年02月03日*/
public class MediatorStructure extends Mediator {// 聚合同事角色类:房主和租客private HouseOwner houseOwner ;private Tenant tenant ;public HouseOwner getHouseOwner() {return houseOwner;}public void setHouseOwner(HouseOwner houseOwner) {this.houseOwner = houseOwner;}public Tenant getTenant() {return tenant;}public void setTenant(Tenant tenant) {this.tenant = tenant;}// 和中介沟通public void contact(String message, Person person){if(person == houseOwner){tenant.getMessage(message);}else if(person == tenant){houseOwner.getMessage(message);}}}
5.最后创建测试类:创建中介,创建房主与租客,使中介知道该房主与租客,最后通过中介进行沟通。
/*** @author nuist__NJUPT* @ClassName Client* @description: 客户端测试类* @date 2024年02月03日*/
public class Client {public static void main(String[] args) {// 创建中介对象MediatorStructure mediatorStructure = new MediatorStructure() ;// 创建房主与租客对象HouseOwner houseOwner = new HouseOwner("肥婆", mediatorStructure) ;Tenant tenant = new Tenant("小王", mediatorStructure) ;// 中介需要知道具体的房主与租客mediatorStructure.setHouseOwner(houseOwner);mediatorStructure.setTenant(tenant);// 通过中介完成房主与租客进行沟通tenant.contact("请问有单间可以出租吗?");houseOwner.contact("有的...");}
}
2.迭代器模式
定义:提供一个对象来访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示。
迭代器模式主要包含如下角色:
01.抽象聚合角色:定义元素操作以及迭代器对象的接口。
02.具体聚合角色:实现抽象聚合角色,返回一个具体的迭代器对象。
03.抽象迭代器角色:定义访问和遍历聚合元素的接口,包含next()等方法。
04.具体迭代器角色:实现抽象迭代器类并重写相应的抽象方法遍历与访问聚合元素。
迭代器模式的优缺点及使用场景:
优点:支持以不同方式遍历一个聚合对象,简化了聚合类的设计,满足开闭原则。
缺点:增加了类的数目,在一定程度上增加了系统的复杂度。
使用场景:遍历聚合对象,提供统一接口,不暴露内部实现细节。在java的集合中使用了迭代器模式,使用迭代器可以遍历集合元素。
下面定义一个存储学生对象的容器对象,将遍历该容器的功能交给迭代器实现。
1.定义一个学生对象,包含姓名合编号。
/*** @author nuist__NJUPT* @ClassName Student* @description: 学生对象* @date 2024年02月03日*/
public class Student {private String name ;private String number ;public String getName() {return name;}public void setName(String name) {this.name = name;}public String getNumber() {return number;}public void setNumber(String number) {this.number = number;}@Overridepublic String toString() {return "Student{" +"name='" + name + '\'' +", number='" + number + '\'' +'}';}public Student(String name, String number) {this.name = name;this.number = number;}public Student() {}
}
2.定义聚合接口及其实现类,用于用于添加/删除学生对象以及获取迭代器对象。
/*** @author nuist__NJUPT* @InterfaceName StudentAggregate* @description: 抽象聚合角色* @date 2024年02月03日*/
public interface StudentAggregate {// 添加学生void addStudent(Student student) ;// 删除学生void removeStudent(Student student) ;// 获取迭代器对象StudentIterator getStudentIterator() ;
}
import java.util.ArrayList;
import java.util.List;/*** @author nuist__NJUPT* @ClassName StudentAggregateImpl* @description: 具体聚合角色* @date 2024年02月03日*/
public class StudentAggregateImpl implements StudentAggregate {private List<Student> list = new ArrayList<Student>() ;public void addStudent(Student student) {list.add(student) ;}public void removeStudent(Student student) {list.remove(student) ;}public StudentIterator getStudentIterator() {return new StudentIteratorImpl(list);}
}
3.定义迭代器接口及其实现类,用于遍历迭代器元素。
/*** @author nuist__NJUPT* @InterfaceName StudentIterator* @description: 抽象迭代器角色接口* @date 2024年02月03日*/
public interface StudentIterator {// 判断是否还有元素boolean hasNext() ;// 获取下一个元素Student next() ;}
import java.util.List;/*** @author nuist__NJUPT* @ClassName StudentIteratorImpl* @description: 具体迭代器角色* @date 2024年02月03日*/
public class StudentIteratorImpl implements StudentIterator {List<Student> list ;private int position = 0 ;public StudentIteratorImpl(List<Student> list) {this.list = list;}public boolean hasNext() {return position < list.size() ;}public Student next() {return list.get(position++);}
}
4.定义客户端测试类,通过向聚合对象添加学生对象,通过获取迭代器对象,遍历元素。
/*** @author nuist__NJUPT* @ClassName Client* @description: 客户端测试类* @date 2024年02月03日*/
public class Client {public static void main(String[] args) {// 创建聚合对象StudentAggregate studentAggregate = new StudentAggregateImpl() ;// 添加学生对象studentAggregate.addStudent(new Student("张三", "1"));studentAggregate.addStudent(new Student("王五", "2"));studentAggregate.addStudent(new Student("李四", "3"));// 获取迭代器对象StudentIterator iterator = studentAggregate.getStudentIterator();// 通过迭代器对象遍历while (iterator.hasNext()){System.out.println(iterator.next().toString());}}}
3.访问者模式
定义:封装一些作用于某种数据结构的各种操作,在不改变数据结构的前提下定义作用于这些元素的新操作。
访问者模式包含以下角色:
01.抽象访问者角色:定义了对每个元素的访问行为。
02.具体访问者角色:定义了对每个元素类访问时所产生的具体行为。
03.抽象元素角色:定义一个接收访问者的方法。
04.具体元素角色:定义一个接收方法的具体实现。
05.对象结构:定义对象结构,包含一组元素及其迭代方式,供访问者访问。
访问者模式的优缺点及使用场景:
优点:扩展性好,复用性好,通过访问者来分离无关行为。
缺点:违背依赖倒转原则,访问者是依赖具体而不是依赖抽象。
使用场景:对象结构相对稳定,但是操作算法经常变化。
下面通过给宠物喂食的案例学习一下访问者模式。其中各个类对应的角色如下:
抽象访问者角色:给宠物喂食的人
具体访问者角色:主人、其它人
抽象元素角色:动物角色
具体元素角色:宠物狗、宠物猫
结构对象角色:主人家
1.首先定义一个抽象元素角色,定义由访问者访问的方法。
/*** @author nuist__NJUPT* @InterfaceName Animal* @description: 抽象元素角色类* @date 2024年02月04日*/
public interface Animal {// 接受访问者访问void accept(Person person) ;}
2.定义两个具体元素角色,实现抽象元素角色接口,并重写相应的接口方法。
/*** @author nuist__NJUPT* @ClassName Dog* @description: 具体元素角色* @date 2024年02月04日*/
public class Dog implements Animal{public void accept(Person person) {person.feedDog(this);System.out.println("好吃,汪汪汪...");}
}
/*** @author nuist__NJUPT* @ClassName Cat* @description: 具体元素角色类* @date 2024年02月04日*/
public class Cat implements Animal{public void accept(Person person) {person.feedCat(this);System.out.println("好吃,喵喵喵...");}
}
3.定义抽象访问者类,类中定义访问方法。
/*** @author nuist__NJUPT* @InterfaceName Person* @description: 抽象访问者角色* @date 2024年02月04日*/
public interface Person {// 给猫喂食void feedCat(Cat cat) ;// 给狗喂食void feedDog(Dog dog) ;}
4.定义具体访问者类,并实现抽象访问者接口,重写接口方法。
/*** @author nuist__NJUPT* @ClassName Owner* @description: 具体访问者* @date 2024年02月04日*/
public class Owner implements Person {public void feedCat(Cat cat) {System.out.println("主人喂宠物猫");}public void feedDog(Dog dog) {System.out.println("主人喂宠物狗");}
}
/*** @author nuist__NJUPT* @ClassName Someone* @description: 具体访问者* @date 2024年02月04日*/
public class Someone implements Person {public void feedCat(Cat cat) {System.out.println("其它人喂宠物猫");}public void feedDog(Dog dog) {System.out.println("其它人喂宠物狗");}}
5.定义对象结构类,用于添加元素,并为访问者类提供访问对象的方法。
import java.util.ArrayList;
import java.util.List;/*** @author nuist__NJUPT* @ClassName Home* @description: 对象结构类* @date 2024年02月04日*/
public class Home {// 定义一个集合对象,用来存储元素对象List<Animal> animals = new ArrayList<Animal>() ;// 添加元素public void addAnimal(Animal animal){animals.add(animal) ;}// 提供访问者访问元素对象的方法public void action(Person person){for(Animal animal : animals){animal.accept(person);}}}
6.定义客户端测试类,验证访问者模式。
/*** @author nuist__NJUPT* @ClassName Client* @description: 客户端测试类* @date 2024年02月04日*/
public class Client {public static void main(String[] args) {// 创建结构对象Home home = new Home() ;// 添加元素对象home.addAnimal(new Dog());home.addAnimal(new Cat());// 创建访问者对象Owner owner = new Owner() ;// 结构对象提供访问者对象访问相应的元素home.action(owner);}
}
4.备忘录模式
定义:备忘录模式又称为快照模式,在不破坏封装性的前提下,捕获一个对象的内部状态,并在外部保存这个对象的内部状态,当需要使用到的时候可以快速恢复到保存的状态。
备忘录模式的主要角色如下:
01.发起人角色:记录当前时刻的内部状态信息,提供创建备忘录记录和恢复备忘录记录的功能。
02.备忘录角色:存储发起人的内部状态信息,在需要的时候将这个状态传递给发起人。
03.管理者角色:对备忘录进行管理,提供保存与获取备忘录的功能,不能更改备忘录内部细节。
备忘录模式的优缺点及使用场景:
优点:提供了状态恢复机制,实现了内部状态的封装,也符合单一职责原则。
缺点:资源多的情况下,资源消耗大。
使用场景:需要保存与恢复数据,提供一个可以回滚的场景。
备忘录模式有黑箱备忘录和白箱备忘录模式,白箱备忘录模式是破坏封装性的,即备忘录管理者能构修改备忘录内部的细节。
下面通过一个游戏案例学习备忘录模式,一个游戏角色有生命力、攻击力、防御力等数据,在打boss前和打boss后一定会不一样,玩家在跟boss决斗不理想的情况下允许恢复到之前的状态。
1.首先先一下白箱备忘录模式,定义一个游戏角色,即发起人对象。
/*** @author nuist__NJUPT* @ClassName GameRole* @description: 游戏角色类-发起人对象* @date 2024年02月04日*/
public class GameRole {// 攻击力private int vit ;// 防御力private int def ;// 生命力private int atk ;// 初始化内部状态public void initState(){this.vit = 100 ;this.def = 100 ;this.atk = 100 ;}// 战斗public void fight(){this.vit = 0 ;this.atk = 0 ;this.def = 0 ;}// 保存角色状态功能public RoleStateMemento saveState(){return new RoleStateMemento(vit, def, atk) ;}// 恢复角色状态public void recoveryState(RoleStateMemento roleStateMemento){// 将备忘录中存储的对象状态赋值到当前对象this.vit = roleStateMemento.getVit() ;this.def = roleStateMemento.getDef() ;this.atk = roleStateMemento.getAtk() ;}// 展示状态功能public void stateDisplay(){System.out.println("角色生命力:" + vit);System.out.println("角色攻击力" + atk);System.out.println("角色防御力" + def);}public int getVit() {return vit;}public void setVit(int vit) {this.vit = vit;}public int getDef() {return def;}public void setDef(int def) {this.def = def;}public int getAtk() {return atk;}public void setAtk(int atk) {this.atk = atk;}
}
2.定义备忘录角色,用于保存发起人角色的状态。
/*** @author nuist__NJUPT* @ClassName RoleStateMemento* @description: 备忘录角色类* @date 2024年02月04日*/
public class RoleStateMemento {// 攻击力private int vit ;// 防御力private int def ;// 生命力private int atk ;public RoleStateMemento(int vit, int def, int atk) {this.vit = vit;this.def = def;this.atk = atk;}public RoleStateMemento() {}public int getVit() {return vit;}public void setVit(int vit) {this.vit = vit;}public int getDef() {return def;}public void setDef(int def) {this.def = def;}public int getAtk() {return atk;}public void setAtk(int atk) {this.atk = atk;}
}
3.定义管理者角色,用于管理备忘录,备忘录提供宽接口,即提供具体备忘录实现类给管理者管理
/*** @author nuist__NJUPT* @ClassName RoleStateCaretaker* @description: 管理者类* @date 2024年02月04日*/
public class RoleStateCaretaker {// 声明备忘录类对象private RoleStateMemento roleStateMemento ;public RoleStateMemento getRoleStateMemento() {return roleStateMemento;}public void setRoleStateMemento(RoleStateMemento roleStateMemento) {this.roleStateMemento = roleStateMemento;}}
4.最后,定义测试类,测试白箱备忘录模式。
/*** @author nuist__NJUPT* @ClassName Client* @description: 客户端测试类-白箱备忘录模式* @date 2024年02月04日*/
public class Client {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.recoveryState(roleStateCaretaker.getRoleStateMemento());gameRole.stateDisplay();}
}
还是这个案例,下面我们看一下黑箱备忘录模式,该模式是将备忘录实现类放到发起人角色类的内部,通过提供一个抽象接口给管理者管理。
1.定义发起人类对象,在发起人对象内部定义备忘录类对象。
/*** @author nuist__NJUPT* @ClassName GameRole* @description: 将备忘录角色类定义在发起者角色类的内部* @date 2024年02月04日*/
public class GameRole {// 攻击力private int vit ;// 防御力private int def ;// 生命力private int atk ;// 初始化内部状态public void initState(){this.vit = 100 ;this.def = 100 ;this.atk = 100 ;}// 战斗public void fight(){this.vit = 0 ;this.atk = 0 ;this.def = 0 ;}// 保存角色状态功能public Memento saveState(){return new RoleStateMemento(vit, def, atk) ;}// 恢复角色状态public void recoveryState(Memento memento){RoleStateMemento roleStateMemento = (RoleStateMemento) memento;// 将备忘录中存储的对象状态赋值到当前对象this.vit = roleStateMemento.getVit() ;this.def = roleStateMemento.getDef() ;this.atk = roleStateMemento.getAtk() ;}// 展示状态功能public void stateDisplay(){System.out.println("角色生命力:" + vit);System.out.println("角色攻击力" + atk);System.out.println("角色防御力" + def);}public int getVit() {return vit;}public void setVit(int vit) {this.vit = vit;}public int getDef() {return def;}public void setDef(int def) {this.def = def;}public int getAtk() {return atk;}public void setAtk(int atk) {this.atk = atk;}public class RoleStateMemento implements Memento{// 攻击力private int vit ;// 防御力private int def ;// 生命力private int atk ;public RoleStateMemento(int vit, int def, int atk) {this.vit = vit;this.def = def;this.atk = atk;}public RoleStateMemento() {}public int getVit() {return vit;}public void setVit(int vit) {this.vit = vit;}public int getDef() {return def;}public void setDef(int def) {this.def = def;}public int getAtk() {return atk;}public void setAtk(int atk) {this.atk = atk;}}
}
2.对外提供一个备忘录抽象接口由管理者管理。
/*** @author nuist__NJUPT* @InterfaceName Memento* @description: 备忘录接口-对外提供窄接口* @date 2024年02月04日*/
public interface Memento {}
3.声明管理者类管理备忘录对象。
/*** @author nuist__NJUPT* @ClassName RoleStateCaretaker* @description: 管理者类-只管理备忘录的接口抽象,不管理具体内部实现类细节* @date 2024年02月04日*/
public class RoleStateCaretaker {// 声明备忘录类对象private Memento memento ;public Memento getMemento() {return memento;}public void setMemento(Memento memento) {this.memento = memento;}
}
4.定义客户端测试类,测试黑盒备忘录模式。
/*** @author nuist__NJUPT* @ClassName Client* @description: 客户端测试类-黑箱备忘录模式* @date 2024年02月04日*/public class Client {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.recoveryState(roleStateCaretaker.getMemento());gameRole.stateDisplay();}
}
5.解释器模式
定义:给定一个语言,定义它的文法表示,并定义一个解释器,这个解释器使用该标识来解释语言中的句子。
解释器模式包含以下角色:
01.抽象表达式角色:定义解释器接口,约定解释器的解释操作。
02.终结符表达式角色:抽象表达式的子类,用来实现文法中与终结符相关的操作。
03.非终结符表达式角色:抽象表达式的子类,用来实现文法中与非终结符相关的操作。
04.环境角色:通常包含各个解释器需要的数据或是公共的功能。
05.客户端:将需要分析的句子或者表达式转换成使用解释器对象描述的抽象语法树。
解释器模式的优缺点及使用场景:
优点:易于改变与扩展文。
缺点:对于复杂的文法难以维护。
使用场景:当语言的文法较为简单,且执行效率不是关键问题的时候。
通过实现用于加减法的案例来学习解释器模式,具体如下:
1.首先定义抽象表达式类,定义解释器接口。
/*** @author nuist__NJUPT* @ClassName AbstractExpression* @description: 抽象表达式角色* @date 2024年02月04日*/
public abstract class AbstractExpression {public abstract int intercept(Context context) ;}
2.定义三个具体表达式角色,是抽象表达式的子类。
/*** @author nuist__NJUPT* @ClassName Plus* @description: 加法表达式角色* @date 2024年02月04日*/
public class Plus extends AbstractExpression {// +号左边的表达式private AbstractExpression left ;// +号右边的表达式private AbstractExpression right ;public Plus(AbstractExpression left, AbstractExpression right) {this.left = left;this.right = right;}public int intercept(Context context) {// 将左边表达式的结果与右边表达式的结果进行相加return left.intercept(context) + right.intercept(context);}@Overridepublic String toString() {return "Plus{" +"left=" + left +", right=" + right +'}';}
}
/*** @author nuist__NJUPT* @ClassName Minus* @description: 减法表达式类* @date 2024年02月04日*/
public class Minus extends AbstractExpression {// -号左边的表达式private AbstractExpression left ;// -号右边的表达式private AbstractExpression right ;public Minus(AbstractExpression left, AbstractExpression right) {this.left = left;this.right = right;}public int intercept(Context context) {// 将左边表达式的结果与右边表达式的结果进行相减return left.intercept(context) - right.intercept(context);}@Overridepublic String toString() {return "Plus{" +"left=" + left +", right=" + right +'}';}
}
/*** @author nuist__NJUPT* @ClassName Variable* @description: TODO* @date 2024年02月04日*/
public class Variable extends AbstractExpression {// 声明存储变量名的成员变量private String name ;public Variable(String name){this.name = name ;}public int intercept(Context context) {// 直接返回变量的值return context.getValue(this);}@Overridepublic String toString() {return "Variable{" +"name='" + name + '\'' +'}';}
}
3.定义环境角色,用于对变量进行操作。
import java.util.HashMap;
import java.util.Map;/*** @author nuist__NJUPT* @ClassName Context* @description: 环境角色类* @date 2024年02月04日*/
public class Context {// 定义一个Map集合用来存储变量及其对应的值private Map<Variable,Integer> map = new HashMap<Variable, Integer>() ;// 添加变量的功能public void assign(Variable variable, Integer value){map.put(variable, value) ;}// 根据变量获取相应的值public int getValue(Variable variable){return map.get(variable) ;}}
4.最后定义测试类,测试解释器模式。
/*** @author nuist__NJUPT* @ClassName Client* @description: 客户端测试类* @date 2024年02月04日*/
public class Client {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);// 获取抽象语法树AbstractExpression abstractExpression = new Minus(a, new Plus(new Minus(b, c), d));// 解释int intercept = abstractExpression.intercept(context);System.out.println(intercept);}
}