admin 管理员组文章数量: 887021
上篇链接:设计模式学习笔记(上)
目录
(三)结构型模式
代理模式
适配器模式
装饰者模式
桥接模式
外观模式
组合模式
享元模式
(四)行为型模式
模板方法模式
策略模式
命令模式
责任链模式
状态模式
观察者模式
中介者模式
迭代器模式
访问者模式
备忘录模式
解释器模式
(三)结构型模式
结构型模式描述如何将类或对象按某种布局组成更大的结构。它分为类结构型模式和对象结构型模式,前者采用继承机制来组织接口和类,后者采用组合或聚合来组合对象
代理模式
概述:由于某些原因需要对某对象提供一个代理以控制该对象的访问,这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标之间的中介
java中代理按照代理类生成时机不同又分为静态代理和动态代理。动态代理又分JDK代理和CGLIB代理
结构:
抽象主题类:通过接口或抽象类声明真实主题和代理对象实现的业务方法
真实主题类:实现了抽象主题类中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象
代理类:提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能
例子:
//抽象主题类,声明了真实主题类要实现的业务方法
public interface SellTickets {
public void sell();
}
//真实主题类,实现了具体的业务方法
public class TrainStation implements SellTickets{
public void sell() {
System.out.println("火车站卖票");
}
}
//代理类 ,提供与真实主题类相同的接口
public class ProxyPoint implements SellTickets{
//声明火车站类
private TrainStation trainStation = new TrainStation();
public void sell() {
//在代理类中调用火车站类的方法
trainStation.sell();
//在代理类中可以对方法进行增强
System.out.println("增强");
}
}
//测试
public class Client {
public static void main(String[] args) {
//创建代理类对象
TrainStation trainStation = new TrainStation();
trainStation.sell();
}
}
--JDK动态代理 --
在程序运行期间动态生成代理类。Java中提供了一个动态代理类Proxy,Proxy提供了一个创建代理对象的静态方法(newProxyInstance方法)来获取代理对象
//提供方法获取代理类
public class ProxyFactory {
//声明目标对象
private TrainStation station = new TrainStation();
public SellTickets getProxyObject() {
//创建一个代理对象
SellTickets proxyObject = (SellTickets) Proxy.newProxyInstance(
station.getClass().getClassLoader(),
station.getClass().getInterfaces(),
new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//执行目标对象的方法,返回值用Object接收
Object obj = method.invoke(station,args);
System.out.println("aaa");
return obj;
}
}
);
//返回代理对象
return proxyObject;
}
}
--CGLIB代理--
动态生成目标类的子类,JDK代理效率高于CGLIB代理,如果有接口使用JDK代理,没接口使用CGLIB代理
public class ProxyFactory implements MethodInterceptor {
//声明火车站对象
private TrainStation station = new TrainStation();
public TrainStation getProxyObject() {
//创建Enhancer对象,类似于jdk代理中的Proxy类
Enhancer enhancer = new Enhancer();
//设置父类的字节码对象
enhancer.setSuperclass(TrainStation.class);
//设置回调函数
enhancer.setCallback(this);
//创建代理对象
TrainStation proxyObject = (TrainStation) enhancer.create();
return proxyObject;
}
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
//要这里调用目标对象的方法
Object obj = method.invoke(station, objects);
return obj;
}
}
//测试
public class Client {
public static void main(String[] args) {
ProxyFactory proxyFactory = new ProxyFactory();
//获取代理对象
TrainStation proxyObject = proxyFactory.getProxyObject();
proxyObject.sell();
}
}
cglib代理不能对声明为final的类或方法进行代理
优点:
--代理模式在客户端与目标对象之间起到了一个中介作用和保护目标对象的作用
--代理对象可以扩展目标对象的功能
--代理模式能将客户端与目标对象分离,在一定程度上降低了程序耦合
缺点:
--增加了系统复杂度
使用场景:
--远程代理
--防火墙代理
--保护代理
适配器模式
定义:
将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作
适配器模式分为类适配器模式和对象适配器模式,前者类之间的耦合度比后者高,且要求程序员了解现有组件库中相关组件的内部结构,所以应用相对较少。
结构:
--目标接口:当前系统业务所期待的接口,可以是抽象类或接口
--适配者类:是被访问和适配的现存组件库中的组件接口
--适配器类:是一个转换器,通过继承或引用适配者的对象,把适配者接口转换成目标接口,让客户按目标接口的格式访问适配者
(1)类适配器模式:
定义一个适配器来实现当前系统的业务接口,同时又要继承现有组件库的已经存在的组件
//现有一台电脑,有读取SDCard功能
//想让这台电脑读取TFCard,
// 可以将一个实现了SDCard接口的适配器作为参数传入方法
//在电脑中调用readSDCard()方法,实际调用的是适配器的readSD()方法
//而在适配器的readSD()方法中由调用了TFCard的read()
public class Computer {
public String readSDCard(SDCard sdCard){
return sdCard.readSD();
}
}
//目标接口
public interface SDCard {
//从sd卡中读取数据
String readSD();
}
//目标接口的实现类 电脑可以直接使用这里的方法,不需要适配器
public class SDCardImpl implements SDCard{
public String readSD() {
String msg = "我是SD卡";
return msg;
}
}
*适配者类 ,电脑没有这个类的接口,需要适配器才能使用
实现了TFCard接口(里面有一个readTF()方法)*/
public class TFCardImpl implements TFCard {
public String readTF() {
String message = "TFCard内容。。。";
return message;
}
}
//适配器,继承了TFCardImpl
public class SDAdapter extends TFCardImpl implements SDCard {
public String readSD() {
//调用父类的方法
return readTF();
}
}
//测试
public class Client {
public static void main(String[] args) {
Computer computer = new Computer();
System.out.println(computer.readSDCard(new SDAdapter()));
}
}
类适配器模式违背了合成复用原则。类适配器是客户类有一个接口规范的情况下可用,反之不可用
(2)对象适配器模式
//适配器,内部调用TFCard的方法
//实现了目标者类,可以向上转型为SDCard
public class SDAdapter implements SDCard{
private TFCard tfCard;
public SDAdapter(TFCard tfCard) {
this.tfCard = tfCard;
}
public String readSD() {
return tfCard.readTF();
}
}
//测试
public class Client {
public static void main(String[] args) {
Computer computer = new Computer();
System.out.println(computer.readSDCard(new SDAdapter(new TFCardImpl())));
}
}
如果不希望实现接口中的所有方法,可以写一个抽象类实现接口的方法,再让适配器继承
应用场景:
--以前开发的系统存在满足新系统功能的类,但其接口同新系统接口不一致
--使用第三方提供的组件,但组件接口定义和自己要求的接口定义不同
装饰者模式
定义:指在不改变现有对象结构的情况下,动态地给对象增加一些职责的模式
结构:
--抽象构件角色:定义一个抽象接口以规范准备接收附加责任的对象
--具体构件角色:实现抽象构件,通过修饰角色为其添加一些职责
--抽象装饰角色:继承或实现抽象接口,并包含具体构件的实例,可以通过其子类扩展具体构件的功能
--具体装饰角色:实现抽象装饰的相关方法,并给具体构件对象添加附加的责任
案例:
//抽象构件角色
public abstract class AbstractRice {
private String desc;
public abstract double cost();
public AbstractRice( String desc) {
this.desc = desc;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
}
//具体构件角色(被修饰者)
public class Rice extends AbstractRice{
public Rice() {
//将desc设置为白米饭
super( "白米饭");
}
//重写具体业务
@Override
public double cost() {
return 10.0;
}
}
//抽象装饰角色
public abstract class Decorator extends AbstractRice {
//内部持有一个抽象构件角色
AbstractRice abstractRice;
public Decorator(AbstractRice abstractRice, String desc) {
super(desc);
this.abstractRice = abstractRice;
}
}
/具体装饰者,可以给米饭加一个鸡蛋,对米饭进行修饰
public class Egg extends Decorator{
//构造器传入被修饰者,经过修饰返回一个被修饰好的对象
public Egg(AbstractRice abstractRice) {
super(abstractRice, abstractRice.getDesc() + "+鸡蛋");
}
@Override
public double cost() {
//鸡蛋2块钱 加上被修饰者的价格
return 2.0 + abstractRice.cost();
}
}
//另一个具体修饰者
public class Watermelon extends Decorator{
public Watermelon(AbstractRice abstractRice) {
super(abstractRice, abstractRice.getDesc() + "+西瓜");
}
@Override
public double cost() {
return 3.0 + abstractRice.cost();
}
}
//测试
public class Client {
public static void main(String[] args) {
//创建一个被修饰者
Rice rice = new Rice();
//加鸡蛋
Egg egg = new Egg(rice);
//再加上西瓜
Watermelon watermelon = new Watermelon(egg);
//输出结果
System.out.println(watermelon.getDesc());
System.out.println(watermelon.cost());
}
}
结果:
可以根据自己的需求选择要加什么
好处:
(1)装饰者模式可以带来比继承更加灵活的扩展功能,使用更加方便,可以通过组合不同的装饰者对象来获取具有不同行为状态的多样化的结果。装饰者模式比继承更具良好的扩展性,完美的遵循开闭原则,继承是静态的附加责任,装饰者则是动态的附加责任
(2)装饰类和被装饰类可以独立发展,不会相互耦合,装饰模式是继承的一个代替模式,装饰模式可以动态扩展一个实现类的功能
使用场景:
- 当不能采用继承的方式对系统进行扩充后者采用继承不利于系统扩展和维护时 --不能采用继承的主要有两种情况:(1)是系统中存在大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长 (2)是因为类定义不能继承(如final类)
- 在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责
- 当对象的功能要求可以动态地添加,也可以再动态地撤销时
桥接模式
定义:
将抽象与实现分离,使它们可以独立变化。它们用组合关系代替继承关系来实现,从而降低了抽象的实现这两个可变维度的耦合度
结构:
- 抽象化角色:定义抽象类,并包含一个对实现化对象的引用
- 扩展抽象化角色:是抽象化角色的子类,实现父类中的业务方法,并通过组合关系调用实现化角色中的业务方法
- 实现化角色:定义实现化角色的接口,供扩展抽象化角色使用
- 具体实现化角色:给出实现化角色接口的具体实现
案例:
//抽象的操作系统类(抽象化角色)
public abstract class OperatingSystem {
//声明videoFile变量 对实现化角色的引用
protected VideoFile videoFile;
//
public OperatingSystem(VideoFile videoFile) {
this.videoFile = videoFile;
}
public abstract void play(String fileName);
}
//扩展抽象化角色Windows
public class Windows extends OperatingSystem{
public Windows(VideoFile videoFile) {
super(videoFile);
}
@Override
public void play(String fileName) {
videoFile.decode(fileName);
}
}
//扩展化抽象角色Mac
public class Mac extends OperatingSystem{
public Mac(VideoFile videoFile) {
super(videoFile);
}
@Override
public void play(String fileName) {
videoFile.decode(fileName);
}
}
//实现化角色,供扩展抽象化角色使用
public interface VideoFile {
//解码功能
void decode(String fileName);
}
//rmvb视频文件 具体实现化角色
public class RmvbFile implements VideoFile{
public void decode(String fileName) {
System.out.println("rmvb视频文件:" + fileName);
}
}
//具体的实现化角色AviFile
public class AviFile implements VideoFile{
public void decode(String fileName) {
System.out.println("avi视频文件:" + fileName);
}
}
//测试
public class Client {
public static void main(String[] args) {
OperatingSystem mac = new Mac(new AviFile());
//
mac.play("喜羊羊与灰太狼");
}
}
好处:
- 提高了系统的可扩充性,在两个变化维度中任意扩展一个维度,都不需要修改原有系统
- 实现细节对用户透明
使用场景:
- 当一个类存在两个独立变化的维度,且这两个维度都需要进行扩展时
- 当一个系统不希望使用继承或因为多层次继承导致系统类的个数急剧增加时
- 当一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性时。避免在两个层次之间建立静态的继承关系,通过桥接模式可以使它们在抽象层建立一个关联关系
外观模式
定义:是一种通过为多个复杂的子系统提供一个一致的接口,而使这些子系统更加容易被访问的模式。该模式对外有一个统一接口,外部应用程序不用关心内部子系统的具体实现细节,这样会大大降低应用程序的复杂度,提高程序的可维护性
结构:
- 外观角色:为多个子系统对外提供一个共同的接口
- 子系统角色:实现系统的部分功能,客户端可以通过外观角色访问它
案例:
//电灯类 (子系统角色)
public class Light {
public void on(){
System.out.println("打开电灯");
}
public void off(){
System.out.println("关闭电灯");
}
}
//(子系统角色)
public class TV {
public void on(){
System.out.println("打开电视机");
}
public void off(){
System.out.println("关闭电视机");
}
}
//(子系统角色)
public class AirCondition {
public void on(){
System.out.println("打开空调");
}
public void off(){
System.out.println("关闭空调");
}
}
//智能音响 (外观角色)
public class SmartAppliancesFacade {
//聚合电灯,电视机,空调对象
private Light light;
private TV tv;
private AirCondition airCondition;
public SmartAppliancesFacade(){
light = new Light();
tv = new TV();
airCondition = new AirCondition();
}
//
public void say(String message){
if(message.equals("打开")){
on();
}else if(message.equals("关闭")){
off();
}else{
System.out.println("我听不懂");
}
}
//一键打开方法
private void on(){
light.on();
tv.on();
airCondition.on();
}
//一键关闭
private void off(){
light.off();
tv.off();
airCondition.off();
}
}
好处:
- 降低了子系统与客户端之间的耦合度,使得子系统的变化不会影响调用它的客户类
- 对客户屏蔽了子系统组件,减少了客户处理的对象数目,并使得子系统使用起来更加容易
缺点:不符合开闭原则,修改很麻烦
使用场景:
- 对分层结构系统构件时,使用外观模式定义子系统中每层的入口点可以简化子系统之间的依赖关系
- 当一个复杂系统的子系统很多时,外观模式可以为系统设计一个简单的接口供外界访问
- 当客户端与多个子系统之间存在很大的联系时,引入外观模式可将它们分离,从而提高子系统的独立性和可移植性
组合模式
定义:
又名部分整体模式,是用于把一组相似的对象当作一个单一的对象。组合模式依据树形结构来组合对象,用来表示部分以及整体层次,这种类型的设计模式属于结构型模式,它创建了对象族的树形结构
结构:
- 抽象根节点:定义系统各层次对象的共有方法和属性,可以预先定义一些默认行为和属性
- 树枝节点:定义树枝节点的行为,存储子节点,组合树枝节点和叶子节点形成一个树形结构
- 叶子节点:叶子节点对象,其下再无分支,是系统层次遍历的最小单位
案例:
/*不管是菜单还是菜单项,都应该继承自统一的接口,
这里姑且将这个统一的接口成为菜单组件*/
//抽象根节点
public abstract class MenuComponent {
protected String name; //菜单组件的名称
protected int level; //菜单组件的层级
//添加子菜单 (如果传入的是菜单项,则无法使用该功能,抛出异常)
public void add(MenuComponent menuComponent) {
throw new UnsupportedOperationException();
}
//移除子菜单
public void remove(MenuComponent menuComponent) {
throw new UnsupportedOperationException();
}
//获取指定子菜单
public MenuComponent getChild(int index) {
throw new UnsupportedOperationException();
}
//获取菜单或者菜单项的名称
public String getName() {
return name;
}
//打印菜单名称的方法
public abstract void print();
}
//菜单类(树枝节点)
public class Menu extends MenuComponent{
//菜单可以有多个子菜单或子菜单项
private List<MenuComponent> menuComponentList = new ArrayList<MenuComponent>();
//构造方法
public Menu(String name, int level) {
this.name = name;
this.level = level;
}
@Override
public void print() {
//打印菜单名称
System.out.println(name);
//打印子菜单或子菜单项名称
for (MenuComponent component : menuComponentList) {
component.print();
}
}
@Override
public void add(MenuComponent menuComponent) {
menuComponent.add(menuComponent);
}
@Override
public void remove(MenuComponent menuComponent) {
menuComponent.remove(menuComponent);
}
@Override
public MenuComponent getChild(int index) {
return menuComponentList.get(index);
}
}
//菜单项类(叶子节点)
public class MenuItem extends MenuComponent{
public MenuItem(String name, int level) {
this.name = name;
this.level = level;
}
@Override
public void print() {
//打印菜单项的名称
System.out.println(name);
}
}
组合模式的分类:
(1)透明组合模式:抽象根节点角色中声明了所有用于管理成员对象的方法。这样做确保所有的构件类都有相同的接口。
缺点是不够安全,因为叶子和容器对象在本质上是有区别的,叶子对象不可能有下一层级对象,即不可能包含成员对象,因此为其提供add(),remove()等方法是没有意义的,这在编译阶段不会出错,但在运行阶段如果调用这些方法可能会出错(如果没有提供相应的错误处理代码)
(2)安全组合模式
在安全组合模式中,抽象构件角色没有声明任何用于管理成员对象的方法,而是在树枝节点类中声明并实现这些方法。
缺点是不够透明,因为叶子节点和容器构件具有不同的方法,且容器构件中那些用于管理成员对象的方法没有在抽象构件类中定义,因此客户端不能完全针对抽象编程,必须有区别地对待叶子构件和容器构件
组合模式优点:
- 组合模式可以清晰的定义分层次的复杂对象,表示对象的全部或部分层次,它让客户端忽略了层次的差异,方便对整个层次结构进行控制
- 客户端可以一致地使用一个组合结构或其中单个对象,不必关心处理的是单个对象还是整个组合对象,简化了客户端代码
- 在组合模式中增加新的树枝节点和叶子节点都很方便,无须对现有类库进行任何修改,符合开闭原则
- 组合模式为树形结构的面向对象实现提供了一种灵活的解决方案,通过叶子节点和树枝节点递归组合,可以形成复杂的树形结构,但对树形结构的控制却非常简单
使用场景:
组合模式正是应树形结构而生,所以组合模式的使用场景就是出现树形结构的地方。比如:文件目录显示,多级目录呈现等树形结构数据的操作
享元模式
定义:
运用共享技术来有效地支持大量细粒度对象的复用。它通过共享已经存在的对象来大幅度减少需要创建的对象数量、避免大量相似对象的开销,从而提高系统资源的利用率
结构:
享元模式存在以下两种状态:
(1)内部状态,即不会随着环境的改变而改变的可共享部分
(2)外部状态,即随环境改变而改变的不可以共享的部分。享元模式的实现要领就是区分应用中的这两种状态,并将外部状态外部化
主要角色:
- 抽象享元角色:通常是一个接口或抽象类,在抽象享元类中声明了具体享元类公共的方法,这些方法可以向外界提供享元对象的内部数据(内部状态),同时也可以通过这些方法类设置外部数据(外部状态)
- 具体享元角色:实现了抽象享元角色,称为享元角色;在具体享元类中为内部状态提供了存储空间。通常我们可以结合单例模式来设计具体享元类,为每一个具体享元类提供唯一的享元对象
- 非享元角色:并不是所有的抽象享元类的子类都需要被共享,不能被共享的子类可设计为非共享具体享元类;当需要一个非共享具体享元类的对象是可以直接通过实例化创建
- 享元工厂角色:负责创建和管理享元角色。当客户对象请求一个享元对象时,享元工厂检查系统中是否存在符合要求的享元对象,如果存在则提供给客户;如果不存在,则创建一个新的享元对象
案例:
【例】俄罗斯方块
俄罗斯方块中有大量相同形状的方块,同一种形状的方块又有不同的颜色等状态,颜色对方块的形状不会产生影响,可以将颜色作为外部状态抽取出来,而形状则作为内部状态被共享
/*俄罗斯方块有不同的形状,可以对这些形状向上抽取出AbstractBox
用来定义共同的属性和行为
*/
//抽象享元角色
public abstract class AbstractBox {
//获取图像的方法
public abstract String getShape();
//显示图形及颜色
public void display(String color) {
System.out.println("方块形状" + getShape() + ",颜色:" + color);
}
}
//I、L、O三种形状的方块用于共享
//具体享元角色
public class IBox extends AbstractBox{
@Override
public String getShape() {
return "I";
}
}
public class LBox extends AbstractBox {
@Override
public String getShape() {
return "L";
}
}
public class OBox extends AbstractBox {
@Override
public String getShape() {
return "O";
}
}
//工厂类
public class BoxFactory {
private HashMap<String,AbstractBox> map;
//初始化
private BoxFactory() {
map = new HashMap<String, AbstractBox>();
map.put("I",new IBox());
map.put("L",new LBox());
map.put("O",new OBox());
}
//这个工厂类设计为单例模式
public static BoxFactory getInstance() {
return factory;
}
private static BoxFactory factory = new BoxFactory();
//根据名称获取图像对象
public AbstractBox getShape(String name) {
return map.get(name);
}
}
//测试
public class Client {
public static void main(String[] args) {
BoxFactory boxFactory = BoxFactory.getInstance();
AbstractBox boxI = boxFactory.getShape("I");
boxI.display("绿色");
AbstractBox boxL = boxFactory.getShape("L");
boxL.display("红色");
AbstractBox boxI2 = boxFactory.getShape("I");
boxI2.display("红色");
System.out.println(boxI == boxI2);//返回true,是同一个对象
}
}
结果:
优点 :
- 极大减少内存中相似或相同对象数量,节约系统资源,提供系统性能
- 享元模式中的外部状态相对独立,且不影响内部状态
缺点:
为了使对象可以共享,需要将享元对象的部分状态外部化,分离内部状态和外部状态,使程序逻辑复杂
使用场景:
- 一个系统有大量相同或者相似的对象,造成内部的大量耗费
- 对象的大部分状态都可以外部化,可以将这些外部状态传入对象中
- 在使用享元模式时需要维护一个存储想源对象的享元池,而者需要耗费一定的系统资源,因此,应当在需要多次重复使用享元对象时才值得使用享元模式
(四)行为型模式
行为型模式用于描述程序在运行时复杂的流程控制,及描述多个类或对象之间怎样相互协作共同完成单个对象都无法完成的任务,它涉及算法与对象见职责的匹配
行为型模式分为类行为模式和对象行为模式,前者采用继承机制来在类之间分派行为,后者采用组合或者聚合在对象间分配行为。由于组合关系或聚合关系耦合度地,满足“合成复用原则”,所以对象行为模式比类行为模式具有更大的灵活性
模板方法模式
定义:
定义一个操作系统的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特点步骤
结构:
(1)抽象类:负责给出一个算法的轮廓和具体骨架。由一个模板方法和若干个基本方法组成。
模板方法:定义了算法的骨架,按某种顺序调用其包含的基本方法
基本方法:是实现算法各个步骤的方法,是模板方法的组成部分,可以分为以下三种:
①抽象方法:一个抽象方法由抽象类声明、其具体子类实现
②具体方法:一个具体方法由一个抽象类或具体类声明并实现,其子类可以进行覆盖也可以直接继承
③钩子方法:在抽象类中已经实现,包括用于判断的逻辑方法和需要子类重写的空方法
一般钩子方法适用于判断的逻辑方法,这类方法名一般为isXxx,返回值类型为boolean类型
(2)具体子类:实现抽象类中所定义的抽象方法和钩子方法,它们是一个顶级逻辑的组成步骤
案例:
炒菜的步骤是固定的,分别为倒油、热油、倒蔬菜、倒调料瓶、翻炒等步骤
//抽象类
public abstract class AbstractClass {
//模板方法 在父类中已经定义好了结构,不能被子类重写
public final 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 extends AbstractClass{
public void pourVegetable() {
System.out.println("倒入大白菜");
}
public void pourSauce() {
System.out.println("加盐,加醋");
}
}
//测试
public class Client {
public static void main(String[] args) {
ConcreteClass concreteClass = new ConcreteClass();
concreteClass.cookProcess();
}
}
优点:
提高代码的复用性:将相同部分的代码放在抽象的父类中,而将不同的代码放入不同的子类中
实现了反向控制:通过一个父类调用其子类的操作,通过对子类的具体实现扩展不同的行为,实现了反向的控制,符合开闭原则
缺点:
- 对每个不同的实现都需要定义一个子类,这会导致类的个数的增加,系统更加庞大,设计也更加抽象
- 父类中的抽象方法由子类实现,子类执行的结果会影响父类的结果,这回导致一种反向的控制结构,提高了代码阅读的难度
适用场景:
- 代码的整体步骤很固定,但其中个别部分易变时,这时候可以使用模板方法模式,将易变的部分抽象出来,供子类实现
- 需要通过子类来决定父类算法中某个步骤是否执行,实现子类对父类的反向控制
策略模式
定义:
该模式定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的替换不会影响使用算法的用户。策略模式属于对象行为模式,它通过对算法进行封装,把使用算法的责任与算法的实现分割开来,并委派不同对象对这些算法进行管理
结构:
- 抽象策略类:这是一个接口,通常由一个接口或抽象类实现,此角色给出所有具体策略类所需的接口
- 具体策略类:实现了抽象策略类定义的接口,提供具体的算法实现或行为
- 环境类:持有一个策略类的引用,最终给客户端调用
案例:
//抽象策略类
public interface Strategy {
void show();
}
//具体策略类,封装算法
public class Strategy1 implements Strategy{
public void show() {
System.out.println("买一送一");
}
}
//具体策略类,封装算法
public class Strategy2 implements Strategy{
public void show() {
System.out.println("买一送二");
}
}
//环境类
public class SaleMan {
//聚合策略者类
private Strategy strategy;
public SaleMan(Strategy strategy) {
this.strategy = strategy;
}
public void salesManShow(){
strategy.show();
}
public Strategy getStrategy() {
return strategy;
}
public void setStrategy(Strategy strategy) {
this.strategy = strategy;
}
}
//测试
public class Client {
public static void main(String[] args) {
SaleMan saleMan = new SaleMan(new Strategy1());
saleMan.salesManShow();
saleMan.setStrategy(new Strategy2());
saleMan.salesManShow();
}
}
优点:
- 策略类之间可以自由切换 由于策略类都实现同一个接口,所有它们之间可以自由切换
- 易于扩展 增加一个新的策略只需要增加一个具体的策略类即可,基本不需要改变原有的代码,符合开闭原则
- 避免使用多重条件选择语句(if else),充分体现了面向对象设计思想
缺点:
- 客户端必须知道所有的策略类,并自行决定使用哪个策略类
- 策略模式将造成产生很多策略类,可以通过享元模式在一定程度上减少对象的数量
适用环境:
- 一个系统需要动态地在几种算法中选一种时,可将每个算法都封装到策略类中
- 一个类定义了多种行为,并且这些行为在这个类的操作中以多个条件语句的形式出现,可将每个条件分支移入它们各自的策略类中代替这些语句
- 系统中算法彼此完全独立,且要求对客户隐藏算法的具体实现细节时
- 系统要求使用算法的客户不应该知道其具体的操作时,可使用策略模式来隐藏与算法相关的数据结构
- 多个类只区别在表现行为不同,可以使用策略模式,在运行时动态选择要执行的行为
命令模式
定义:
将一个请求进行封装,使发出请求的责任与执行请求的责任分离开。这样两者之间通过命令对象进行沟通,这样方便将命令对象进行存储、传递、调用、增加和管理
结构:
- 抽象命令角色:定义命令的接口,声明执行的方法
- 具体命令角色:具体的命令,实现命令接口;通常会持有接收者,并调用接收者的功能来完成命令要执行的操作
- 实现者/接收者:接收者,真正要执行命令的对象。任何类都可能成为接收者,只要它能够实现命令要求实现的功能
- 调用者/请求者:要求命令对象执行请求,通常会持有命令对象,可以持有很多命令对象。这个使客户端真正触发命令并要求命令执行相应操作的地方,相当于使用命令对象的入口
案例:
//抽象命令角色 定义命令的接口
public interface Command {
void execute();
}
//具体命令角色 实现接口 聚合接收者
public class OrderCommand implements Command{
//持有接收者对象
private SeniorChef receiver;
//构造器
public OrderCommand(SeniorChef receiver) {
this.receiver = receiver;
}
//具体命令
public void execute() {
//让接收者执行命令
receiver.makeFood();
}
}
//厨师(接收者)
public class SeniorChef {
//方法中写具体业务
public void makeFood(){
System.out.println("做饭");
}
}
//服务员(请求者)一般持有命令对象
public class Waiter {
//可以持有多个命令
private List<Command> commands = new ArrayList<Command>();
//将命令放到集合中
public void setCommands(Command cmd){
commands.add(cmd);
}
//执行命令
public void orderUp(){
for (Command command : commands) {
if(command != null){
command.execute();
}
}
}
}
//测试
public class Client {
public static void main(String[] args) {
//创建接收者对象
SeniorChef receiver = new SeniorChef();
//创建命令对象
OrderCommand orderCommand = new OrderCommand(receiver);
//创建调用者
Waiter waiter = new Waiter();
//将具体命令传入调用者(可以持有多个)
waiter.setCommands(orderCommand);
waiter.setCommands(orderCommand);
//由调用者要求命令对象执行请求
waiter.orderUp();
}
}
优点:
- 降低系统耦合度。命令模式能将调用操作的对象和执行操作的对象解耦
- 增加或删除操作非常方便。采用命令模式增加和删除命令不会影响其他类,满足开闭原则,对扩展比较灵活
- 可以实现宏命令。命令模式可以和组合模式结合,将多个目录装配成一个组合命令,即宏命令
- 方便实现Undo和Redo操作。命令模式可以与后面的备忘录模式结合,实现命令的撤销和恢复
缺点:
- 使用命令模式可能导致系统有过多的具体命令类
- 系统结构更加复杂
适用场景:
- 系统需要将请求调用者和请求执行者解耦,使二者不直接交互
- 系统需要在不同时间指定请求、将请求排队、执行请求
- 系统需要支持命令的撤销和恢复操作
责任链模式
定义:
为了避免请求发送者与多个请求处理者耦合在一起,将所有的请求的处理者通过其前一个对象记住其下一个对象的引用而连成一条链;当有请求发生时,请求可以沿着这条链传递,直到有对象处理它为止。
结构:
- 抽象处理者角色:定义了一个处理请求的接口(抽象类),包含抽象处理方法和一个后续连接
- 具体处理者角色:实现抽象处理者角色的处理方法,判断能否处理本次请求,能出来就处理,不能就交给它的后继者
- 客户类角色:创建处理链,并向链头的具体处理者对象提交请求,它不关心处理细节和请求的传递过程
案例:
//抽象处理者
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 void setNextHandler(Handler nextHandler) {
this.nextHandler = nextHandler;
}
//各级领导处理请假条的方法
//传入请假的要求,对请求进行处理
protected abstract void handleLeave(LeaveRequest leave);
//提交请假条 判断能否处理 不能处理的话往上传
public final void submit(LeaveRequest leave) {
if(this.nextHandler != null && leave.getNumber() > this.numEnd){
//这一级无法处理 提交给上级进行处理
this.nextHandler.submit(leave);
}else if (this.nextHandler != null){
//该领导进行审批
this.handleLeave(leave);
System.out.println("流程结束");
}else {
System.out.println("流程结束,请假失败");
}
}
//构造器
public Handler(int numStart, int numEnd) {
this.numStart = numStart;
this.numEnd = numEnd;
}
}
//请假条类
public class LeaveRequest {
private String name;
private int number;
private String content;
public LeaveRequest(String name, int number, String content) {
this.name = name;
this.number = number;
this.content = content;
}
public String getName() {
return name;
}
public int getNumber() {
return number;
}
public String getContent() {
return content;
}
}
//小组长
public class GroupLeader extends Handler{
@Override
protected void handleLeave(LeaveRequest leave) {
System.out.println("...请假了...天");
System.out.println("小组长同意");
}
public GroupLeader( ) {
super(0, NUM_ONE);
}
}
//经理
public class Manager extends Handler{
@Override
protected void handleLeave(LeaveRequest leave) {
System.out.println("...请假了...天");
System.out.println("经理同意");
}
public Manager( ) {
super(NUM_ONE, NUM_THREE);
}
}
//总经理 最高一级
public class GeneralManager extends Handler{
@Override
protected void handleLeave(LeaveRequest leave) {
System.out.println("...请假了...天");
System.out.println("总经理同意");
}
public GeneralManager( ) {
super(NUM_THREE, NUM_SEVEN);
}
}
//测试
public class Client {
public static void main(String[] args) {
//创建请假条
LeaveRequest leaveRequest = new LeaveRequest("jack", 5, "无");
//创建各级领导
GroupLeader groupLeader = new GroupLeader();
Manager manager = new Manager();
GeneralManager generalManager = new GeneralManager();
//设置处理者链
groupLeader.setNextHandler(manager);
manager.setNextHandler(generalManager);
groupLeader.submit(leaveRequest);
}
}
优点:
- 降低了对象之间的耦合度:该模式降低了请求发送者和接收者之间的耦合度
- 增强了系统的可扩展性:可以根据需要增加新的请求处理类,满足开闭原则
- 增强了给对象指派职责的灵活性:当工作流程发生变化,可以动态地改变链内成员或修改它们的次序,也可以动态地新增或者删除职责
- 责任链简化了对象之间的连接:一个对象只需保持一个指向其后续者的引用,不需要保持其他处理者的引用,者避免了使用众多的if 或者 if else 语句
- 责任分担:每个类只需要处理自己该处理的工作,不能处理的传递给下一个对象完成,明确各类的责任范围,符合类的单一职责原则
缺点:
- 不能保证每个请求一定被处理。由于一个请求没有明确的接收者,所以不能保证它一定会被处理,该请求可能一直传到链的末端都得不到处理
- 对比较长的职责链,请求的处理可能涉及多个处理对象,系统性能可能会受到影响
- 职责链建立的合理性要靠客户端来保证,增加了客户端的复杂性,可能会由于职责链的错误设置而导致系统出错,如可能造成循环调用
状态模式
定义:
对有状态的对象,把复杂的判断逻辑提取到不同的状态对象中,允许状态对象在其内部状态发生改变时改变其行为
结构:
- 环境角色:也称上下文,它定义了客户端程序需要的接口,维护一个当前状态,并将与状态相关的操作委托给当前状态对象来处理
- 抽象状态角色:定义一个接口,用以封装环境对象中的特定状态所对应的行为
- 具体状态角色:实现抽象状态所对应的行为
案例:
电梯有开启、关闭、运行、停止四个状态
每种状态能做出的操作都不相同,有着大量的分支,如果用switch语句,代码可阅读性差,不易维护,所以下面用状态模式解决问题
//抽象状态类
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();
}
//具体状态角色
public class OpeningState extends LiftState{
@Override
public void open() {
System.out.println("电梯开启");
}
@Override
public void close() {
//修改状态
super.context.setLiftState(Context.CLOSE_STATE);
//调用当前状态中的context的close方法
super.context.close();
}
@Override
public void run() {
//什么都不做
}
@Override
public void stop() {
//什么都不做
}
}
//具体状态角色
public class RunningState extends LiftState{
@Override
public void open() {
//不动
}
@Override
public void close() {
//不动
}
@Override
public void run() {
System.out.println("电梯正在运行");
}
@Override
public void stop() {
super.context.setLiftState(Context.STOPPING_STATE);
super.context.stop();
}
}
//具体状态角色
public class StoppingState extends LiftState{
@Override
public void open() {
super.context.setLiftState(Context.OPENING_STATE);
super.context.getLiftState().open();
}
@Override
public void close() {
super.context.setLiftState(Context.CLOSE_STATE);
super.context.getLiftState().close();
}
@Override
public void run() {
//状态修改
super.context.setLiftState(Context.RUNNING_STATE);
//动作委派为CloseState来执行
super.context.getLiftState().run();
}
@Override
public void stop() {
System.out.println("电梯停止了。。。");
}
}
//具体状态角色
public class CloseState extends LiftState{
@Override
public void open() {
super.context.setLiftState(Context.OPENING_STATE);
super.context.getLiftState().open();
}
@Override
public void close() {
System.out.println("电梯门关闭。。。");
}
@Override
public void run() {
//状态修改
super.context.setLiftState(Context.RUNNING_STATE);
//动作委派为CloseState来执行
super.context.getLiftState().run();
}
@Override
public void stop() {
super.context.setLiftState(Context.STOPPING_STATE);
super.context.getLiftState().stop();
}
}
//环境角色 定义了客户端程序需要的接口,维护一个当前状态,
// 并将与状态相关的操作委托给当前状态对象来处理
public class Context {
//定义对应状态对象的常量
public final static OpeningState OPENING_STATE = new OpeningState();
public final static CloseState CLOSE_STATE = new CloseState();
public final static RunningState RUNNING_STATE = new RunningState();
public final static StoppingState STOPPING_STATE = new StoppingState();
//定义一个当前电梯状态变量
private LiftState liftState;
public LiftState getLiftState() {
return liftState;
}
//设置当前状态对象
public void setLiftState(LiftState liftState) {
this.liftState = liftState;
//设置当前状态的context对象
this.liftState.setContext(this);
}
public void open() {
this.liftState.open();
}
public void close() {
this.liftState.close();
}
public void run() {
this.liftState.run();
}
public void stop() {
this.liftState.stop();
}
}
//测试
public class Client {
public static void main(String[] args) {
//创建环境角色对象
Context context = new Context();
//设置当前电梯状态
context.setLiftState(new RunningState());
context.open();
context.close();
context.run();
context.stop();
}
}
优点:
- 将所有与某个状态有关的行为放到一个类中,并且可以方便地增加新状态,只需要改变对象状态即可改变对象的行为
- 允许状态转换逻辑与状态对象合成一体,而不是某一个巨大的条件语句块
缺点:
- 状态模式的使用必然会增加系统类和对象的数量
- 状态模式的结构和实现都比较复杂,如果使用不当将导致程序结构和代码的混乱
- 状态模式对开闭原则的支持并不太好
使用场景:
- 当一个对象的行为取决于它的状态,并且它必须在运行时根据状态改变它的行为时,就可以考虑使用状态模式
- 一个操作系统含有庞大的分支结构,并且这些分支决定于对象的状态时
观察者模式
定义:
又称发布-订阅模式,它定义了一种一对多的依赖关系,让多个观察者同时监听某一个主题对象。这个主题对象在状态发生改变时,会通知所有观察者对象,使它们能够自动更新自己
结构:
- 抽象主题(抽象被观察者):抽象主题角色把所有观察者对象保存在一个集合里,每个主题都可以有任意数量的观察者,抽象主题提供一个接口,可以增加和删除观察者对象
- 具体主题(具体被观察者):该角色将有关状态存入具体观察者对象,在具体主题内部状态发生改变时,给所有注册过的观察者发送通知
- 抽象观察者:使观察者的抽象类,它定义了一个更新的接口,使得在得到主题更改通知时更新自己
- 具体观察者,实现抽象观察者定义的更新接口,以便在得到主题更改通知时更新自身的状态
案例:
【例】
//抽象观察者
public interface Observer {
void update(String message);
}
//具体观察者
public class User implements Observer{
private String name;
public User(String name) {
this.name = name;
}
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 ConcreteSubject implements Subject{
//定义一个集合,用来存储多个观察者对象
private List<Observer> userList = new ArrayList<Observer>();
public void attach(Observer observer) {
userList.add(observer);
}
public void detach(Observer observer) {
userList.remove(observer);
}
public void notify(String message) {
//遍历,通知所有观察者
for (Observer user : userList) {
user.update(message);
}
}
}
//测试
public class Client {
public static void main(String[] args) {
//创建观察者
ConcreteSubject concreteSubject = new ConcreteSubject();
//将被观察者添加到观察者中
concreteSubject.attach(new User("Jack"));
concreteSubject.attach(new User("Mike"));
concreteSubject.attach(new User("John"));
//
concreteSubject.notify("更新。。。 ");
}
}
优点:
- 降低了目标与观察者之间的耦合关系,两者之间是抽象耦合关系
- 被观察者发送通知,所有注册的观察者都会收到信息
缺点:
- 如果观察者非常多的话,那么所有的观察者收到被观察者发送到通知会耗时
- 如果被观察者有循环依赖的话,那么被观察者发送通知会使观察者循环调用,会导致系统崩溃
使用场景:
- 对象间存在一对多关系,一个对象的状态发生改变会影响其它对象
- 当一个抽象模型有两个方面,其中一个方面依赖于另一个方面时
中介者模式
定义:
又叫调停模式,定义了一个中介角色来封装一系列对象之间的交互,使原有对象之间的耦合松散,且可以独立地改变它们之间的交互
结构:
- 抽象中介者角色:它是中介者的接口,提供了同事对象注册与转发同事对象信息的抽象方法
- 具体中介者角色:实现中介者接口,定义了一个List来管理同事对象,协调各个同事之间的交互关系
- 抽象同事类角色:定义同事类的接口,保存中介者对象,提供同事对象交互的抽象方法,实现所有相互影响的同事类的公共功能
- 具体同事类角色:是抽象同事类的实现者,当需要与其他同事对象交互时,由中介者对象负责后续的交互
案例:
【例】租房 房屋中介充当租房者与房屋所有者之间的中介者
//抽象中介者角色
public abstract class Mediator {
public abstract void contact(String message,Person person);
}
//具体中介者
public class MediatorStructure extends Mediator{
//聚合房主和具体租房者(同事类角色)
private HouseOwner houseOwner;
private Tenant tenant;
//一个同事类角色通过中介者和另一个同事类角色联系
@Override
public void contact(String message, Person person) {
if (person == houseOwner) {
tenant.getMessage(message);
} else {
houseOwner.getMessage(message);
}
}
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 abstract class Person {
protected String name;
protected Mediator mediator;
public Person(String name, Mediator mediator) {
this.name = name;
this.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 Client {
public static void main(String[] args) {
//创建中介
MediatorStructure mediatorStructure = new MediatorStructure();
//创建租客
Tenant jack = new Tenant("Jack", mediatorStructure);
//创建房主
HouseOwner houseOwner = new HouseOwner("Mike", mediatorStructure);
//中介者要知道具体的房主和租房者
mediatorStructure.setTenant(jack);
mediatorStructure.setHouseOwner(houseOwner);
jack.contact("我要房子");
houseOwner.contact("www");
}
}
结果
优点:
- 松散耦合:中介者模式通过把多个同事对象之间的交互封装到中介者对象里面,从而使得同事对象之间松散耦合,基本上可以做到互补依赖。这样一来,同事对象就可以独立变化和复用
- 集中控制交互:多个同事对象的交互,被封装到中介者对象里面集中管理,使得这些交互行为发生变化的时候,只需要修改中介者对象就可以了,当然如果是已经做好的系统,那么就扩展中介者对象,而各个同事类不需要做修改
- 一对多关系变为一对一的关联:没有使用中介者的时候,同事对象之间的关系通常是一对多的,引入中介者对象后,中介者对象和同事对象的关系通常变成双向的一对一,这会让对象的关系更容易理解和实现
缺点:
同事类太多,中介者的职责将更大。它会变得复杂而庞大,以至于系统难以维护
使用场景:
- 系统中对象之间存在复杂的引用关系,系统结构混乱且难以理解
- 当想创建一个运行于多个类之间的对象,又不想生成新的子类时
迭代器模式
定义:
提供一个对象来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示
结构:
- 抽象聚合角色:定义存储、添加、删除聚合元素以及创建迭代器对象的接口
- 具体聚合角色:实现抽象聚合类,返回一个具体迭代器实例
- 抽象迭代器角色:定义访问和遍历聚合元素的接口,通常包含hasNext() 、next()等方法
- 具体迭代器角色:实现抽象迭代器接口中所定义的方法,完成对聚合对象的遍历,记录遍历的当前位置
案例:
定义一个可以存储学生对象的容器对象,将遍历该容器的功能交由迭代器实现
//抽象聚合角色
public interface StudentAggregate {
//添加学生功能
void addStudent(Student stu);
//删除学生功能
void removeStudent(Student stu);
//获取迭代器功能
StudentIterator getStudentIterator();
}
//具体聚合角色
public class StudentAggregateImpl implements StudentAggregate{
private List<Student> list = new ArrayList<Student>();
public void addStudent(Student stu) {
list.add(stu);
}
public void removeStudent(Student stu) {
list.remove(stu);
}
public StudentIterator getStudentIterator() {
return new StudentIteratorImpl(list);
}
}
//抽象迭代器角色
public interface StudentIterator {
//判断是否还有元素hasNext()
boolean hasNext();
//获取下一个元素
Student next();
}
//具体迭代器角色
public class StudentIteratorImpl implements StudentIterator{
private List<Student> studentList;
private int position = 0; //用来记录遍历时的位置
public StudentIteratorImpl(List<Student> studentList) {
this.studentList = studentList;
}
public boolean hasNext() {
return position < studentList.size();
}
public Student next() {
//从集合中获取指定位置的元素
Student currentStudent = studentList.get(position);
position++;
return currentStudent;
}
}
//测试
public class Client {
public static void main(String[] args) {
//创建聚合对象
StudentAggregate aggregate = new StudentAggregateImpl();
aggregate.addStudent(new Student("Jack","202201"));
aggregate.addStudent(new Student("Mike","202202"));
aggregate.addStudent(new Student("Tom","202203"));
StudentIterator studentIterator = aggregate.getStudentIterator();
while (studentIterator.hasNext()){
System.out.println(studentIterator.next());
}
}
}
优点:
- 它支持以不同的方式遍历一个聚合对象,在同一个聚合对象上可以定义多种遍历方式。在迭代器模式中只需要用一个不同的迭代器来替换原有迭代器即可改变遍历算法,我们也可以自己定义迭代器的子类以支持新的遍历方式
- 迭代器简化了聚合类。由于引入了迭代器,在原有的聚合对象中不需要再自行提供数据遍历等方式,这样可以简化聚合类的设计
- 在迭代器模式中,由于引入了抽象层,增加新的聚合类和迭代器类都很方便,无须修改原有代码,满足开闭原则
缺点:
增加了类的个数,在一定程度上增加了系统的复杂性
使用场景:
- 当需要为聚合对象提供多种遍历方式时
- 当需要为遍历不同的聚合结构提供一个统一的接口时
- 当访问一个聚合对象的内容而无须暴露其内部细节的表示时
访问者模式
定义:
封装一些作用于某种数据结构中的各种元素的操作,它可以在不改变这个数据结构的前提下定义作用于这些元素的新的操作
结构:
- 抽象访问者角色:定义了对每一个元素访问的行为,为每个元素类定义了一个访问操作,这个操作的参数就是被访问的元素
- 具体访问者角色:给出对每一个元素类访问时所产生的具体行为,确定访问者访问一个元素是应该做什么
- 抽象元素角色:定义了一个接受访问者的方法accept(),被接受的访问者对象作为accept()的参数
- 具体元素角色:提供接收访问方法的具体实现,而这个具体的实现,通常情况下是使用访问者提供的访问该元素类的方法
- 对象结构角色:是一个包含元素角色的容器,提供让访问者对象遍历容器中所有元素的方法
案例:
现有一个造纸厂和一个家具厂(具体访问者角色),对木头和竹子进行加工(具体元素角色)
//材料 (抽象元素角色)
public interface Materials {
//提供一个accept方法 用来接收访问者
String accept(Visitors visitor);
}
//木头(具体元素角色)
public class Wood implements Materials {
//实现accept()方法,通过这个方法调用访问者的方法,并将
//具体元素角色传进去供访问者使用
public String accept(Visitors visitors) {
return visitors.create(this);
}
}
//竹子(具体元素角色)
public class Bamboo implements Materials{
//在这里调用访问者的方法
public String accept(Visitors visitor) {
return visitor.create(this);
}
}
//抽象访问者角色
public interface Visitors {
String create(Wood wood);
String create(Bamboo bamboo);
}
public class PaperFactory implements Visitors{
//方法中写具体操作
public String create(Wood wood) {
return "木头做的纸";
}
public String create(Bamboo bamboo) {
return "竹子做的纸";
}
}
//具体访问者 家具厂
public class FurnitureFactory implements Visitors{
//方法中写具体操作
public String create(Wood wood) {
return "";
}
public String create(Bamboo bamboo) {
return "";
}
}
//对象结构角色
public class ObjectStructure {
List<Materials>list = new ArrayList<Materials>();
public String accept(Visitors visitor) {
//遍历 加工
Iterator<Materials> iterator = list.iterator();
String result = "";
while(iterator.hasNext()){
Materials materials = iterator.next();
result += materials.accept(visitor);
}
//返回产品
return result;
}
public void add(Materials materials){
list.add(materials);
}
public void remove(Materials materials){
list.remove(materials);
}
}
//测试
public class Client {
public static void main(String[] args) {
//创建对象结构角色
ObjectStructure objectStructure = new ObjectStructure();
//创建具体元素角色
Wood wood = new Wood();
Bamboo bamboo = new Bamboo();
objectStructure.add(wood);
objectStructure.add(bamboo);
String s = objectStructure.accept(new PaperFactory());
System.out.println(s);
}
}
优点:
- 扩展性好:在不改变对象结构中的元素的情况下,为对象结构中的元素添加新的功能
- 复用性好:通过访问者来定义整个对象结构通用的功能,从而提高复用程度
- 分离无关行为:通过访问者来分离无关行为,把相关行为封装在一起,构成一个访问者,这样每一个访问者的功能都比较单一
缺点:
- 对象结构变化很困难:在访问者模式中,每添加一种新的元素类,都要在每一个具体访问者类中增加相应的具体操作,违背了开闭原则
- 违反了依赖倒置原则:访问者模式依赖具体类,而没有依赖抽象类
使用场景:
- 对象结构相对稳定,但是其操作算法经常变化的程序
- 对象结构中的对象需要提供多种不同且不相关的操作,而且要避免这些操作的变化影响对象的结构
备忘录模式
定义:
又叫快照模式,在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外这个状态,以便以后需要时将给对象恢复为原先保存的状态
结构:
- 发起人角色:记录当前时刻的内部状态信息,提供创建备忘录和恢复备忘录数据的功能,实现其他业务功能,它可以访问备忘录中的所有信息
- 备忘录角色:负责存储发起人的内部状态,在需要的时候提供这些状态给发起人
- 管理者角色:对备忘录进行管理,提供保存与获取备忘录的功能,但不能对备忘录的内容进行访问和修改
备忘录有两个等效的接口:
- 窄接口:管理者对象看到的是备忘录的窄接口,这个接口这允许它把备忘录对象传给其他对象
- 宽接口:与管理者看到的窄接口相反,发起人对象可以看到一个宽接口,这个宽接口运行它读取所有的数据,以便更加这个数据发起人对象的内部状态
案例:
实现方式有白箱备忘录模式和黑箱备忘录模式
白箱备忘录模式
备忘录角色对任何对象都提供一个接口,即宽接口,备忘录内部所存储的状态就对所有对象公开
(这违背了备忘录模式的概念)
//游戏角色(发起人角色)
public class GameRole {
private int HP;
private int atk;
private int def;
//保存角色状态功能 生成一个备忘录角色(由管理者进行管理)
public RoleStateMemento saveState() {
return new RoleStateMemento(HP,atk,def);
}
//恢复角色状态 传入一个备忘录角色
public void recoverState(RoleStateMemento memento) {
//将备忘录中的状态赋值给当前对象的成员
this.HP = memento.getHP();
this.atk = memento.getAtk();
this.def = memento.getDef();
}
//初始化状态
public void intiState() {
this.atk = 100;
this.HP = 100;
this.def = 5;
}
//战斗后,属性都变成了0
public void fight() {
this.atk = 0;
this.HP = 0;
this.def = 0;
}
//展示状态
public void display() {
System.out.println("生命:" + HP);
System.out.println("攻击:" + atk);
System.out.println("防御:" + def);
}
public int getHP() {
return HP;
}
public void setHP(int HP) {
this.HP = HP;
}
public int getAtk() {
return atk;
}
public void setAtk(int atk) {
this.atk = atk;
}
public int getDef() {
return def;
}
public void setDef(int def) {
this.def = def;
}
}
//备忘录角色类
public class RoleStateMemento {
private int HP;
private int atk;
private int def;
public RoleStateMemento() {
}
public RoleStateMemento(int HP, int atk, int def) {
this.HP = HP;
this.atk = atk;
this.def = def;
}
public int getHP() {
return HP;
}
public void setHP(int HP) {
this.HP = HP;
}
public int getAtk() {
return atk;
}
public void setAtk(int atk) {
this.atk = atk;
}
public int getDef() {
return def;
}
public void setDef(int def) {
this.def = def;
}
}
//备忘录管理这对象
public class RoleStateCareTaker {
//声明备忘录类型的变量
private RoleStateMemento roleStateMemento;
public RoleStateMemento getRoleStateMemento() {
return roleStateMemento;
}
public void setRoleStateMemento(RoleStateMemento roleStateMemento) {
this.roleStateMemento = roleStateMemento;
}
}
//测试
public class Client {
public static void main(String[] args) {
GameRole gameRole = new GameRole();
gameRole.intiState();
//展示初始状态
gameRole.display();
//创建管理者对象
RoleStateCareTaker roleStateCareTaker = new RoleStateCareTaker();
//存储当前状态
roleStateCareTaker.setRoleStateMemento(gameRole.saveState());
//修改当前状态
gameRole.fight();
gameRole.display();
//恢复原先保存的状态
gameRole.recoverState(roleStateCareTaker.getRoleStateMemento());
gameRole.display();
}
}
黑箱备忘录模式
备忘录角色对发起人对象提供一个宽接口,而为其他对象提供一个窄接口。实现双重接口的方法就是将备忘录类设计成发起人类的内部成员类
//备忘录接口 对外提供窄接口
public interface Memento {
}
//游戏角色(发起人角色)
public class GameRole {
private int HP;
private int atk;
private int def;
//保存角色状态功能 生成一个备忘录角色
/*返回类型为Memento接口,该接口没有提供get和set方法
所以将该对象存入管理者中后,在管理者类中无法访问与修改备忘录
*/
public Memento saveState() {
return new RoleStateMemento(HP,atk,def);
}
//恢复角色状态 传入一个备忘录角色
public void recoverState(Memento memento) {
//向下转型
RoleStateMemento roleStateMemento = (RoleStateMemento)memento;
//将备忘录中的状态赋值给当前对象的成员
this.HP = roleStateMemento.getHP();
this.atk = roleStateMemento.getAtk();
this.def = roleStateMemento.getDef();
}
//-------------内部类 将备忘录放在发起人角色里面----------
//实现Memento接口
private class RoleStateMemento implements Memento {
private int HP;
private int atk;
private int def;
public RoleStateMemento() {
}
public RoleStateMemento(int HP, int atk, int def) {
this.HP = HP;
this.atk = atk;
this.def = def;
}
//内部类的get和set方法省略。。。
}
//------------------------------------
//初始化状态
public void intiState() {
this.atk = 100;
this.HP = 100;
this.def = 5;
}
//战斗后,属性都变成了0
public void fight() {
this.atk = 0;
this.HP = 0;
this.def = 0;
}
//展示状态
public void display() {
System.out.println("生命:" + HP);
System.out.println("攻击:" + atk);
System.out.println("防御:" + def);
}
//get和set方法省略。。。
}
//备忘录管理这对象
public class RoleStateCareTaker {
//声明备忘录类型的变量
private Memento memento;
public Memento getMemento() {
return memento;
}
public void setMemento(Memento memento) {
this.memento = memento;
}
}
//测试
public class Client {
public static void main(String[] args) {
GameRole gameRole = new GameRole();
gameRole.intiState();
//展示初始状态
gameRole.display();
//创建管理者对象
RoleStateCareTaker roleStateCareTaker = new RoleStateCareTaker();
//存储当前状态
roleStateCareTaker.setMemento(gameRole.saveState());
//修改当前状态
gameRole.fight();
gameRole.display();
//恢复原先保存的状态
gameRole.recoverState(roleStateCareTaker.getMemento());
gameRole.display();
}
}
优点:
- 提供了一种可以恢复状态的机制。当用户需要时能够比较方便地将数据恢复到某个历史状态
- 实现了内部状态的封装。除了创建它的发起人之外,其他对象都不能够访问这些状态
- 简化了发起人类,发起人类不需要管理和保存其内部状态的各个备份,所有状态信息都保存在备忘录中,并由管理者进行管理,这符合单一职责原则
缺点:资源消耗大。如果要保存的内部状态信息过多或者特别频繁,将会占用比较大的内存资源
使用场景:
- 需要保存与恢复数据的场景,如玩游戏时的中间结果的存档功能
- 需要提供一个可回滚操作的场景,如编译器的ctrl+z
解释器模式
定义:
给定一个语言,定义它的文法表示,并定义一个解释器,这个解释器使用该标识来解释语言中的句子
结构:
- 抽象表达式角色:定义解释器的接口,约定解释器的解释操作,主要包含解释方法interpret()
- 终结符表达式角色:是抽象表达式的子类,用来实现文法中与终结符相关的操作,文法中的每一个终结符都有一个具体终结表达式与之对应
- 非终结符表达式角色:也是抽象表达式的子类,用来实现文法中与非终结符相关的操作,文法中的每条规则都对应一个非终结符表达式
- 环境角色:通常包含各个解释器需要的数据或是公共的功能,一般用来传递被所有解释器共享的数据,后面的解释器可以从这里获取这些值
- 客户端:主要任务是将需要分析的句子或表达式转换成使用解释器对象描述的抽象语法树,然后调用解释器的解释方法
案例:
设计实现加减法的软件
//抽象表达式角色
public abstract class AbstractExpression {
//解释方法
public abstract int interpret(Context context);
}
//变量 非终结符表达式角色
public class Variable extends AbstractExpression{
//声明存储变量名的成员变量
private String name;
@Override
public int interpret(Context context) {
//根据对象获取对应的值
return context.getValue(this);
}
//设置变量名
public Variable(String name) {
this.name = name;
}
}
//终结符表达式角色
public class Minus extends AbstractExpression{
//减号左右两边的表达式
private AbstractExpression left;
private AbstractExpression right;
public Minus(AbstractExpression left, AbstractExpression right) {
this.left = left;
this.right = right;
}
@Override
public int interpret(Context context) {
return left.interpret(context) - right.interpret(context);
}
}
//终结符表达式角色
public class Plus extends AbstractExpression{
//加号左右两边的表达式
private AbstractExpression left;
private AbstractExpression right;
public Plus(AbstractExpression left, AbstractExpression right) {
this.left = left;
this.right = right;
}
@Override
public int interpret(Context context) {
return left.interpret(context) + right.interpret(context);
}
}
//环境角色:通常包含各个解释器需要的数据或是公共的功能
public class Context {
//定义一个map集合,用来存储变量及对应的值
private Map<Variable,Integer> map = new HashMap<Variable, Integer>();
//添加变量功能
public void assign(Variable var, Integer value) {
map.put(var,value);
}
//根据变量获取对应的值
public int getValue(Variable var) {
return map.get(var);
}
}
//测试
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);
//获取抽象语法树 a - ( (b-c) +d )
AbstractExpression expression = new Minus(a,new Plus(new Minus(b,c),d));
//解释
int result = expression.interpret(context);
System.out.println(result);
}
}
优点:
- 易于改变和扩展文法
- 实现文法较为容易
- 增加新的解释表达式较为容易
缺点:
- 对于复杂文法难以维护:每条规则至少需要定义一个类,因此如果一个语言包含太多文法规则,类的个数将急剧增加,导致系统难以管理和维护
- 执行效率较低:使用了大量的循环和递归调用
使用场景:
- 当语言的文法较为简单,且执行效率不是关键问题时
- 当问题重复出现,且可以用一种简单的语言来表达时
- 当一个语言需要解释执行,并且语言的句子可以表示为一个抽象语法树的时候
版权声明:本文标题:设计模式学习笔记(下) 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.freenas.com.cn/jishu/1715924558h658015.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论