网站建设公司郴州,中企动力是国企还是央企,电子营销主要做什么,wordpress企业免费主题下载地址1.访问者模式
访问者模式在实际开发中使用的非常少,因为它比较难以实现并且应用该模式肯能会导致代码的可读性变差,可维护性变差,在没有特别必要的情况下,不建议使用访问者模式.
访问者模式(Visitor Pattern) 的原始定义是#xff1a;
允许在运行时将一个或多个操作应用于一…1.访问者模式
访问者模式在实际开发中使用的非常少,因为它比较难以实现并且应用该模式肯能会导致代码的可读性变差,可维护性变差,在没有特别必要的情况下,不建议使用访问者模式.
访问者模式(Visitor Pattern) 的原始定义是
允许在运行时将一个或多个操作应用于一组对象将操作与对象结构分离。
这个定义会比较抽象但是我们依然能看出两个关键点
一个是: 运行时使用一组对象的一个或多个操作比如对不同类型的文件.pdf、.xml、.properties进行扫描 另一个是: 分离对象的操作和对象本身的结构比如扫描多个文件夹下的多个文件对于文件来说扫描是额外的业务操作如果在每个文件对象上都加一个扫描操作太过于冗余而扫描操作具有统一性非常适合访问者模式。
访问者模式主要解决的是数据与算法的耦合问题, 尤其是在数据结构比较稳定,而算法多变的情况下.为了不污染数据本身,访问者会将多种算法独立归档,并在访问数据时根据数据类型自动切换到对应的算法,实现数据的自动响应机制,并确保算法的自由扩展.
1.1访问者模式原理 1.1.1访问者模式包含以下主要角色: 抽象访问者Visitor角色 可以是接口或者抽象类,定义了一系列操作方法,用来处理所有数据元素,通常为同名的访问方法,并以数据元素类作为入参来确定那个重载方法被调用. 具体访问者ConcreteVisitor角色 访问者接口的实现类,可以有多个实现,每个访问者都需要实现所有数据元素类型的访问重载方法. 抽象元素Element角色 被访问的数据元素接口,定义了一个接受访问者的方法accept其意义是指每一个元素都要可以被访问者访问。 具体元素ConcreteElement角色 具体数据元素实现类,提供接受访问方法的具体实现而这个具体的实现通常情况下是使用访问者提供的访问该元素类的方法,其accept实现方法中调用访问者并将自己 this 传回。 对象结构Object Structure角色 包含所有可能被访问的数据对象的容器,可以提供数据对象的迭代功能,可以是任意类型的数据结构. 客户端 ( Client ) : 使用容器并初始化其中各类数据元素,并选择合适的访问者处理容器中的所有数据对象. 1.2访问者模式实现
我们以超市购物为例,假设超市中的三类商品: 水果,糖果,酒水进行售卖. 我们可以忽略每种商品的计价方法,因为最终结账时由收银员统一集中处理,在商品类中添加计价方法是不合理的设计.我们先来定义糖果类和酒类、水果类.
/*** 抽象商品父类* author spikeCong* date 2022/10/18**/
public abstract class Product {private String name; //商品名private LocalDate producedDate; // 生产日期private double price; //单品价格public Product(String name, LocalDate producedDate, double price) {this.name name;this.producedDate producedDate;this.price price;}public String getName() {return name;}public void setName(String name) {this.name name;}public LocalDate getProducedDate() {return producedDate;}public void setProducedDate(LocalDate producedDate) {this.producedDate producedDate;}public double getPrice() {return price;}public void setPrice(double price) {this.price price;}
}/*** 糖果类* author spikeCong* date 2022/10/18**/
public class Candy extends Product{public Candy(String name, LocalDate producedDate, double price) {super(name, producedDate, price);}
}/*** 酒水类* author spikeCong* date 2022/10/18**/
public class Wine extends Product{public Wine(String name, LocalDate producedDate, double price) {super(name, producedDate, price);}
}/*** 水果类* author spikeCong* date 2022/10/18**/
public class Fruit extends Product{//重量private float weight;public Fruit(String name, LocalDate producedDate, double price, float weight) {super(name, producedDate, price);this.weight weight;}public float getWeight() {return weight;}public void setWeight(float weight) {this.weight weight;}
}访问者接口
收银员就类似于访问者,访问用户选择的商品,我们假设根据生产日期进行打折,过期商品不能够出售. 注意这种计价策略不适用于酒类,作为收银员要对不同商品应用不同的计价方法.
/*** 访问者接口-根据入参不同调用对应的重载方法* author spikeCong* date 2022/10/18**/
public interface Visitor {public void visit(Candy candy); //糖果重载方法public void visit(Wine wine); //酒类重载方法public void visit(Fruit fruit); //水果重载方法
}具体访问者
创建计价业务类,对三类商品进行折扣计价,折扣计价访问者的三个重载方法分别实现了3类商品的计价方法,体现了visit() 方法的多态性.
/*** 折扣计价访问者类* author spikeCong* date 2022/10/18**/
public class DiscountVisitor implements Visitor {private LocalDate billDate;public DiscountVisitor(LocalDate billDate) {this.billDate billDate;System.out.println(结算日期: billDate);}Overridepublic void visit(Candy candy) {System.out.println(糖果: candy.getName());//获取产品生产天数long days billDate.toEpochDay() - candy.getProducedDate().toEpochDay();if(days 180){System.out.println(超过半年的糖果,请勿食用!);}else{double rate 0.9;double discountPrice candy.getPrice() * rate;System.out.println(糖果打折后的价格NumberFormat.getCurrencyInstance().format(discountPrice));}}Overridepublic void visit(Wine wine) {System.out.println(酒类: wine.getName(),无折扣价格!);System.out.println(原价: NumberFormat.getCurrencyInstance().format(wine.getPrice()));}Overridepublic void visit(Fruit fruit) {System.out.println(水果: fruit.getName());//获取产品生产天数long days billDate.toEpochDay() - fruit.getProducedDate().toEpochDay();double rate 0;if(days 7){System.out.println(超过七天的水果,请勿食用!);}else if(days 3){rate 0.5;}else{rate 1;}double discountPrice fruit.getPrice() * fruit.getWeight() * rate;System.out.println(水果价格: NumberFormat.getCurrencyInstance().format(discountPrice));}public static void main(String[] args) {LocalDate billDate LocalDate.now();Candy candy new Candy(徐福记,LocalDate.of(2022,10,1),10.0);System.out.println(糖果: candy.getName());double rate 0.0;long days billDate.toEpochDay() - candy.getProducedDate().toEpochDay();System.out.println(days);if(days 180){System.out.println(超过半年的糖果,请勿食用!);}else{rate 0.9;double discountPrice candy.getPrice() * rate;System.out.println(打折后的价格NumberFormat.getCurrencyInstance().format(discountPrice));}}
}客户端
public class Client {public static void main(String[] args) {//德芙巧克力,生产日期2002-5-1 ,原价 10元Candy candy new Candy(德芙巧克力,LocalDate.of(2022,5,1),10.0);Visitor visitor new DiscountVisitor(LocalDate.of(2022,10,11));visitor.visit(candy);}
}
上面的代码虽然可以完成当前的需求,但是设想一下这样一个场景: 由于访问者的重载方法只能对当个的具体商品进行计价,如果顾客选择了多件商品来结账时,就可能会引起重载方法的派发问题(到底该由谁来计算的问题).
首先我们定义一个接待访问者的类 Acceptable,其中定义了一个accept(Visitor visitor)方法, 只要是visitor的子类都可以接收.
/*** 接待者接口(抽象元素角色)* author spikeCong* date 2022/10/18**/
public interface Acceptable {//接收所有的Visitor访问者的子类实现类public void accept(Visitor visitor);
}/*** 糖果类* author spikeCong* date 2022/10/18**/
public class Candy extends Product implements Acceptable{public Candy(String name, LocalDate producedDate, double price) {super(name, producedDate, price);}Overridepublic void accept(Visitor visitor) {//accept实现方法中调用访问者并将自己 this 传回。this是一个明确的身份,不存在任何泛型visitor.visit(this);}
}//酒水与水果类同样实现Acceptable接口,重写accept方法测试
public class Client {public static void main(String[] args) {// //德芙巧克力,生产日期2002-5-1 ,原价 10元Candy candy new Candy(德芙巧克力,LocalDate.of(2022,5,1),10.0);Visitor visitor new DiscountVisitor(LocalDate.of(2022,10,11));visitor.visit(candy);//模拟添加多个商品的操作ListAcceptable products Arrays.asList(new Candy(金丝猴奶糖,LocalDate.of(2022,6,10),10.00),new Wine(衡水老白干,LocalDate.of(2020,6,10),100.00),new Fruit(草莓,LocalDate.of(2022,10,12),50.00,1));Visitor visitor new DiscountVisitor(LocalDate.of(2022,10,17));for (Acceptable product : products) {product.accept(visitor);}}
}代码编写到此出,就可以应对计价方式或者业务逻辑的变化了,访问者模式成功地将数据资源需实现接待者接口与数据算法 需实现访问者接口分离开来。重载方法的使用让多样化的算法自成体系多态化的访问者接口保证了系统算法的可扩展性数据则保持相对固定最终形成⼀个算法类对应⼀套数据。
1.3访问者模式总结
1.3.1访问者模式优点
扩展性好
在不修改对象结构中的元素的情况下为对象结构中的元素添加新的功能。 复用性好
通过访问者来定义整个对象结构通用的功能从而提高复用程度。 分离无关行为
通过访问者来分离无关的行为把相关的行为封装在一起构成一个访问者这样每一个访问者的功能都比较单一。
1.3.2访问者模式缺点
对象结构变化很困难
在访问者模式中每增加一个新的元素类都要在每一个具体访问者类中增加相应的具体操作这违背了“开闭原则”。 违反了依赖倒置原则
访问者模式依赖了具体类而没有依赖抽象类。
1.3.3使用场景
当对象的数据结构相对稳定而操作却经常变化的时候。 比如上面例子中路由器本身的内部构造也就是数据结构不会怎么变化但是在不同操作系统下的操作可能会经常变化比如发送数据、接收数据等。 需要将数据结构与不常用的操作进行分离的时候。 比如扫描文件内容这个动作通常不是文件常用的操作但是对于文件夹和文件来说和数据结构本身没有太大关系树形结构的遍历操作扫描是一个额外的动作如果给每个文件都添加一个扫描操作会太过于重复这时采用访问者模式是非常合适的能够很好分离文件自身的遍历操作和外部的扫描操作。 需要在运行时动态决定使用哪些对象和方法的时候。 比如对于监控系统来说很多时候需要监控运行时的程序状态但大多数时候又无法预知对象编译时的状态和参数这时使用访问者模式就可以动态增加监控行为。
2.备忘录模式
备忘录模式提供了一种对象状态的撤销实现机制,当系统中某一个对象需要恢复到某一历史状态时可以使用备忘录模式进行设计. 很多软件都提供了撤销Undo操作如 Word、记事本、Photoshop、IDEA等软件在编辑时按 CtrlZ 组合键时能撤销当前操作使文档恢复到之前的状态还有在 浏览器 中的后退键、数据库事务管理中的回滚操作、玩游戏时的中间结果存档功能、数据库与操作系统的备份操作、棋类游戏中的悔棋功能等都属于这类。
备忘录模式(memento pattern)定义: 在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样可以在以后将对象恢复到原先保存的状态.
2.1备忘录模式原理 2.1.1备忘录模式的主要角色 发起人Originator角色 状态需要被记录的元对象类, 记录当前时刻的内部状态信息提供创建备忘录和恢复备忘录数据的功能实现其他业务功能它可以访问备忘录里的所有信息。 备忘录Memento角色 负责存储发起人的内部状态在需要的时候提供这些内部状态给发起人。 看护人Caretaker角色 对备忘录进行管理提供保存与获取备忘录的功能但其不能对备忘录的内容进行访问与修改。 2.2.备忘录模式实现
下面我们再来看看 UML 对应的代码实现。首先我们创建原始对象 Originator对象中有四个属性分别是 state 用于显示当前对象状态id、name、phone 用来模拟业务属性并添加 get、set 方法、create() 方法用于创建备份对象restore(memento) 用于恢复对象状态。
/*** 发起人类* author spikeCong* date 2022/10/19**/
public class Originator {private String state 原始对象;private String id;private String name;private String phone;public Originator() {}//创建备忘录对象public Memento create(){return new Memento(id,name,phone);}//恢复对象状态public void restore(Memento m){this.state m.getState();this.id m.getId();this.name m.getName();this.phone m.getPhone();}public String getState() {return state;}public void setState(String state) {this.state state;}public String getId() {return id;}public void setId(String id) {this.id id;}public String getName() {return name;}public void setName(String name) {this.name name;}public String getPhone() {return phone;}public void setPhone(String phone) {this.phone phone;}Overridepublic String toString() {return Originator{ state state \ , id id \ , name name \ , phone phone \ };}
}/*** 备忘录对象* 访问权限为: 默认,也就是同包下可见(保证只有发起者类可以访问备忘录类)* author spikeCong* date 2022/10/19**/
class Memento {private String state 从备份对象恢复为原始对象;private String id;private String name;private String phone;public Memento() {}public Memento(String id, String name, String phone) {this.id id;this.name name;this.phone phone;}//get、set、toString......
}/*** 负责人类-保存备忘录对象* author spikeCong* date 2022/10/19**/
public class Caretaker {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) {//创建发起人对象Originator originator new Originator();originator.setId(1);originator.setName(spike);originator.setPhone(13512341234);System.out.println( originator);//创建负责人对象,并保存备忘录对象Caretaker caretaker new Caretaker();caretaker.setMemento(originator.create());//修改originator.setName(update);System.out.println( originator);//从负责人对象中获取备忘录对象,实现撤销originator.restore(caretaker.getMemento());System.out.println( originator);}
}2.3 备忘录模式应用实例
设计一个收集水果和获取金钱数的掷骰子游戏,游戏规则如下
1. 游戏玩家通过扔骰子来决定下一个状态 2. 当点数为1,玩家金钱增加 3. 当点数为2,玩家金钱减少 4. 当点数为6,玩家会得到水果 5. 当钱消耗到一定程度,就恢复到初始状态
Memento类: 表示玩家的状态
/*** Memento 表示状态* author spikeCong* date 2022/10/19**/
public class Memento {int money; //所持金钱ArrayList fruits; //获得的水果//构造函数Memento(int money) {this.money money;this.fruits new ArrayList();}//获取当前玩家所有的金钱int getMoney() {return money;}//获取当前玩家所有的水果List getFruits() {return (List)fruits.clone();}//添加水果void addFruit(String fruit){fruits.add(fruit);}
}Player玩家类,只要玩家的金币还够,就会一直进行游戏,在该类中会设置一个createMemento方法,其作用是保存当前玩家状态.还会包含一个restore撤销方法,相当于复活操作.
package com.mashibing.memento.example02;import java.util.ArrayList;
import java.util.List;
import java.util.Random;/*** author spikeCong* date 2022/10/19**/
public class Player {private int money; //所持金钱private ListString fruits new ArrayList(); //获得的水果private Random random new Random(); //随机数对象private static String[] fruitsName{ //表示水果种类的数组苹果,葡萄,香蕉,橘子};//构造方法public Player(int money) {this.money money;}//获取当前所持有的金钱public int getMoney() {return money;}//获取一个水果public String getFruit() {String prefix ;if (random.nextBoolean()) {prefix 好吃的;}//从数组中获取水果String f fruitsName[random.nextInt(fruitsName.length)];return prefix f;}//掷骰子游戏public void yacht(){int dice random.nextInt(6) 1; //掷骰子if(dice 1){money 100;System.out.println(所持有的金钱增加了..);}else if(dice 2){money / 2;System.out.println(所持有的金钱减半..);}else if(dice 6){ //获取水果String fruit getFruit();System.out.println(获得了水果: fruit);fruits.add(fruit);}else{//骰子结果为3、4、5System.out.println(无效数字,继续投掷);}}//拍摄快照public Memento createMemento(){Memento memento new Memento(money);for (String fruit : fruits) {if(fruit.startsWith(好吃的)){memento.addFruit(fruit);}}return memento;}//撤销方法public void restore(Memento memento){this.money memento.money;this.fruits memento.getFruits();}Overridepublic String toString() {return Player{ money money , fruits fruits };}
}测试: 由于引入了备忘录模式,可以保存某个时间点的玩家状态,这样就可以对玩家进行复活操作.
public class MainApp {public static void main(String[] args) throws InterruptedException {Player player new Player(100); //最初所持的金钱数Memento memento player.createMemento(); //保存最初状态for (int i 0; i 100; i) {//显示扔骰子的次数System.out.println( i);//显示当前状态System.out.println(当前状态: player);//开启游戏player.yacht();System.out.println(所持有的金钱为: player.getMoney() 元);//决定如何操作Mementoif(player.getMoney() memento.getMoney()){System.out.println(赚到金币,保存当前状态,继续游戏!);memento player.createMemento();}else if(player.getMoney() memento.getMoney() / 2){System.out.println(所持金币不多了,将游戏恢复到初始状态!);player.restore(memento);}Thread.sleep(1000);System.out.println();}}
}2.4备忘录模式总结
2.4.1备忘录模式的优点
1. 提供了一种状态恢复的实现机制,使得用户可以方便的回到一个特定的历史步骤,当新的状态无效或者存在问题的时候,可以使用暂时存储起来的备忘录将状态复原. 2. 备忘录实现了对信息的封装,一个备忘录对象是一种发起者对象状态的表示,不会被其他代码所改动.备忘录保存了发起者的状态,采用集合来存储备忘录可以实现多次撤销的操作
2.4.2备忘录模式的缺点
资源消耗过大,如果需要保存的发起者类的成员变量比较多, 就不可避免的需要占用大量的存储空间,每保存一次对象的状态,都需要消耗一定系统资源
2.4.3备忘录模式使用场景
1. 需要保存一个对象在某一时刻的状态时,可以使用备忘录模式. 2. 不希望外界直接访问对象内部状态时.
3.命令模式
命令模式(command pattern)的定义: 命令模式将请求命令封装为一个对象这样可以使用不同的请求参数化其他对象将不 同请求依赖注入到其他对象并且能够支持请求命令的排队执行、记录日志、撤销等 附加控制功能。
命令模式的核心是将指令信息封装成一个对象,并将此对象作为参数发送给接收方去执行,达到使命令的请求与执行方解耦,双方只通过传递各种命令对象来完成任务.
在实际的开发中如果你用到的编程语言并不支持用函数作为参数来传递那么就可以借助命令模式将函数封装为对象来使用。
我们知道C 语 言支持函数指针我们可以把函数当作变量传递来传递去。但是在大部分编程语言中函 数没法儿作为参数传递给其他函数也没法儿赋值给变量。借助命令模式我们可以将函数 封装成对象。具体来说就是设计一个包含这个函数的类实例化一个对象传来传去这样 就可以实现把函数像对象一样使用。
3.1命令模式原理
命令模式包含以下主要角色 抽象命令类Command角色 定义命令的接口声明执行的方法。 具体命令Concrete Command角色 具体的命令实现命令接口通常会持有接收者并调用接收者的功能来完成命令要执行的操作。 实现者/接收者Receiver角色 接收者真正执行命令的对象。任何类都可能成为一个接收者只要它能够实现命令要求实现的相应功能。 调用者/请求者Invoker角色 要求命令对象执行请求通常会持有命令对象可以持有很多的命令对象。这个是客户端真正触发命令并要求命令执行相应操作的地方也就是说相当于使用命令对象的入口。 3.2命令模式实现
模拟酒店后厨的出餐流程,来对命令模式进行一个演示,命令模式角色的角色与案例中角色的对应关系如下:
服务员: 即调用者角色,由她来发起命令. 厨师: 接收者,真正执行命令的对象. 订单: 命令中包含订单
/*** 订单类* author spikeCong* date 2022/10/19**/
public class Order {private int diningTable; //餐桌号码//存储菜名与份数private MapString,Integer foodMenu new HashMap();public int getDiningTable() {return diningTable;}public void setDiningTable(int diningTable) {this.diningTable diningTable;}public MapString, Integer getFoodMenu() {return foodMenu;}public void setFoodDic(MapString, Integer foodMenu) {this.foodMenu foodMenu;}
}/*** 厨师类 - Receiver角色* author spikeCong* date 2022/10/19**/
public class Chef {public void makeFood(int num,String foodName){System.out.println(num 份, foodName);}
}/*** 抽象命令接口* author spikeCong* date 2022/10/19**/
public interface Command {void execute(); //只需要定义一个统一的执行方法
}/*** 具体命令* author spikeCong* date 2022/10/19**/
public class OrderCommand implements Command {//持有接收者对象private Chef receiver;private Order order;public OrderCommand(Chef receiver, Order order) {this.receiver receiver;this.order order;}Overridepublic void execute() {System.out.println(order.getDiningTable() 桌的订单: );SetString keys order.getFoodMenu().keySet();for (String key : keys) {receiver.makeFood(order.getFoodMenu().get(key),key);}try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(order.getDiningTable() 桌的菜已上齐.);}
}/*** 服务员- Invoker调用者* author spikeCong* date 2022/10/19**/
public class Waiter {//可以持有很多的命令对象private ArrayListCommand commands;public Waiter() {commands new ArrayList();}public Waiter(ArrayListCommand commands) {this.commands commands;}public void setCommands(Command command) {commands.add(command);}//发出命令 ,指挥厨师工作public void orderUp(){System.out.println(服务员: 叮咚,有新的订单,请厨师开始制作......);for (Command cmd : commands) {if(cmd ! null){cmd.execute();}}}
}public class Client {public static void main(String[] args) {Order order1 new Order();order1.setDiningTable(1);order1.getFoodMenu().put(鲍鱼炒饭,1);order1.getFoodMenu().put(茅台迎宾,1);Order order2 new Order();order2.setDiningTable(3);order2.getFoodMenu().put(海参炒面,1);order2.getFoodMenu().put(五粮液,1);//创建接收者Chef receiver new Chef();//将订单和接收者封装成命令对象OrderCommand cmd1 new OrderCommand(receiver,order1);OrderCommand cmd2 new OrderCommand(receiver,order2);//创建调用者Waiter invoke new Waiter();invoke.setCommands(cmd1);invoke.setCommands(cmd2);//将订单发送到后厨invoke.orderUp();}
}
3.3命令模式总结
3.3.1命令模式优点
降低系统的耦合度。命令模式能将调用操作的对象与实现该操作的对象解耦。 增加或删除命令非常方便。采用命令模式增加与删除命令不会影响其他类它满足“开闭原则”对扩展比较灵活。 可以实现宏命令。命令模式可以与组合模式结合将多个命令装配成一个组合命令即宏命令。
3.3.2命令模式缺点
使用命令模式可能会导致某些系统有过多的具体命令类。 系统结构更加复杂。
3.3.3使用场景
系统需要将请求调用者和请求接收者解耦使得调用者和接收者不直接交互。 系统需要在不同的时间指定请求、将请求排队和执行请求。 系统需要支持命令的撤销(Undo)操作和恢复(Redo)操作。
4.解释器模式
解释器模式使用频率不算高通常用来描述如何构建一个简单“语言”的语法解释器。它只在一些非常特定的领域被用到比如编译器、规则引擎、正则表达式、SQL 解析等。不过了解它的实现原理同样很重要能帮助你思考如何通过更简洁的规则来表示复杂的逻辑。
解释器模式(Interpreter pattern)的原始定义是用于定义语言的语法规则表示并提供解释器来处理句子中的语法。
我们通过一个例子给大家解释一下解释器模式
假设我们设计一个软件用来进行加减计算。我们第一想法就是使用工具类提供对应的加法和减法的工具方法。
//用于两个整数相加的方法
public static int add(int a , int b){return a b;
}//用于三个整数相加的方法
public static int add(int a , int b,int c){return a b c;
}public static int add(Integer ... arr){int sum 0;for(Integer num : arr){sum num;}return sum;
} - 上面的形式比较单一、有限如果形式变化非常多这就不符合要求因为加法和减法运算两个运算符与数值可以有无限种组合方式。比如: 5-32-1, 10-520....
文法规则和抽象语法树
解释器模式描述了如何为简单的语言定义一个文法,如何在该语言中表示一个句子,以及如何解释这些句子.
在上面提到的加法/减法解释器中,每一个输入表达式(比如:234-5) 都包含了3个语言单位,可以使用下面的文法规则定义:
文法是用于描述语言的语法结构的形式规则。 expression :: value | plus | minus plus :: expression ‘’ expression minus :: expression ‘-’ expression value :: integer 注意 这里的符号“::”表示“定义为”的意思竖线 | 表示或左右的其中一个引号内为字符本身引号外为语法。
上面规则描述为
表达式可以是一个值也可以是plus或者minus运算而plus和minus又是由表达式结合运算符构成值的类型为整型数。
抽象语法树
在解释器模式中还可以通过一种称为抽象语法树的图形方式来直观的表示语言的构成,每一棵抽象语法树对应一个语言实例,例如加法/减法表达式语言中的语句 1 2 3 - 4 1 可以通过下面的抽象语法树表示 4.1解释器模式原理 4.1.1解释器模式包含以下主要角色。 抽象表达式Abstract Expression角色 定义解释器的接口约定解释器的解释操作主要包含解释方法 interpret()。 终结符表达式Terminal Expression角色 是抽象表达式的子类用来实现文法中与终结符相关的操作文法中的每一个终结符都有一个具体终结表达式与之相对应。上例中的value 是终结符表达式. 非终结符表达式Nonterminal Expression角色 也是抽象表达式的子类用来实现文法中与非终结符相关的操作文法中的每条规则都对应于一个非终结符表达式。上例中的 plus , minus 都是非终结符表达式 环境Context角色 通常包含各个解释器需要的数据或是公共的功能一般用来传递被所有解释器共享的数据后面的解释器可以从这里获取这些值。 客户端Client 主要任务是将需要分析的句子或表达式转换成使用解释器对象描述的抽象语法树然后调用解释器的解释方法当然也可以通过环境角色间接访问解释器的解释方法。 4.2解释器模式实现
为了更好的给大家解释一下解释器模式, 我们来定义了一个进行加减乘除计算的“语言”语法规则如下
运算符只包含加、减、乘、除并且没有优先级的概念 表达式中先书写数字后书写运算符空格隔开
我们举个例子来解释一下上面的语法规则:
比如 “ 9 5 7 3 - * ”这样一个表达式我们按照上面的语法规则来处理取出数字 “9、5” 和 “-” 运算符计算得到 4于是表达式就变成了 “ 4 7 3 * ”。然后我们再取出 “4 7”和“ ”运算符计算得到 11表达式就变成了“ 11 3 * ”。最后我们取出“ 11 3”和“ * ”运算符最终得到的结果就是 33。
代码示例:
用户按照上 面的规则书写表达式传递给 interpret() 函数就可以得到最终的计算结果。
/*** 表达式解释器类* author spikeCong* date 2022/10/20**/
public class ExpressionInterpreter {//Deque双向队列可以从队列的两端增加或者删除元素private DequeLong numbers new LinkedList();//接收表达式进行解析public long interpret(String expression){String[] elements expression.split( );int length elements.length;//获取表达式中的数字for (int i 0; i (length1)/2; i) {//在 Deque的尾部添加元素numbers.addLast(Long.parseLong(elements[i]));}//获取表达式中的符号for (int i (length1)/2; i length; i) {String operator elements[i];//符号必须是 - * / 否则抛出异常boolean isValid .equals(operator) || -.equals(operator)|| *.equals(operator) || /.equals(operator);if (!isValid) {throw new RuntimeException(Expression is invalid: expression);}//pollFirst()方法, 移除Deque中的第一个元素,并返回被移除的值long number1 numbers.pollFirst(); //数字long number2 numbers.pollFirst();long result 0; //运算结果//对number1和number2进行运算if (operator.equals()) {result number1 number2;} else if (operator.equals(-)) {result number1 - number2;} else if (operator.equals(*)) {result number1 * number2;} else if (operator.equals(/)) {result number1 / number2;}//将运算结果添加到集合头部numbers.addFirst(result);}//运算完成numbers中应该保存着运算结果,否则是无效表达式if (numbers.size() ! 1) {throw new RuntimeException(Expression is invalid: expression);}//移除Deque的第一个元素,并返回return numbers.pop();}
}4.3代码重构
代码的所有的解析逻辑都耦合在一个函数中这样显然是不合适的。这 个时候我们就要考虑拆分代码将解析逻辑拆分到独立的小类中, 前面定义的语法规则有两类表达式一类是数字一类是运算符运算符又包括加减乘除。 利用解释器模式我们把解析的工作拆分到以下五个类:plu,sub,mul,div
NumExpression PluExpression SubExpression MulExpression DivExpression
/*** 表达式接口* author spikeCong* date 2022/10/20**/
public interface Expression {long interpret();
}/*** 数字表达式* author spikeCong* date 2022/10/20**/
public class NumExpression implements Expression {private long number;public NumExpression(long number) {this.number number;}public NumExpression(String number) {this.number Long.parseLong(number);}Overridepublic long interpret() {return this.number;}
}/*** 加法运算* author spikeCong* date 2022/10/20**/
public class PluExpression implements Expression{private Expression exp1;private Expression exp2;public PluExpression(Expression exp1, Expression exp2) {this.exp1 exp1;this.exp2 exp2;}Overridepublic long interpret() {return exp1.interpret() exp2.interpret();}
}/*** 减法运算* author spikeCong* date 2022/10/20**/
public class SubExpression implements Expression {private Expression exp1;private Expression exp2;public SubExpression(Expression exp1, Expression exp2) {this.exp1 exp1;this.exp2 exp2;}Overridepublic long interpret() {return exp1.interpret() - exp2.interpret();}
}/*** 乘法运算* author spikeCong* date 2022/10/20**/
public class MulExpression implements Expression {private Expression exp1;private Expression exp2;public MulExpression(Expression exp1, Expression exp2) {this.exp1 exp1;this.exp2 exp2;}Overridepublic long interpret() {return exp1.interpret() * exp2.interpret();}
}/*** 除法* author spikeCong* date 2022/10/20**/
public class DivExpression implements Expression {private Expression exp1;private Expression exp2;public DivExpression(Expression exp1, Expression exp2) {this.exp1 exp1;this.exp2 exp2;}Overridepublic long interpret() {return exp1.interpret() / exp2.interpret();}
}//测试
public class Test01 {public static void main(String[] args) {ExpressionInterpreter e new ExpressionInterpreter();long result e.interpret(6 2 3 2 4 / - *);System.out.println(result);}
}4.4解释器模式总结
4.4.1解释器优点
易于改变和扩展文法 因为在解释器模式中使用类来表示语言的文法规则的,因此就可以通过继承等机制改变或者扩展文法.每一个文法规则都可以表示为一个类,因此我们可以快速的实现一个迷你的语言 实现文法比较容易 在抽象语法树中每一个表达式节点类的实现方式都是相似的,这些类的代码编写都不会特别复杂 增加新的解释表达式比较方便 如果用户需要增加新的解释表达式,只需要对应增加一个新的表达式类就可以了.原有的表达式类不需要修改,符合开闭原则
4.4.2解释器缺点
对于复杂文法难以维护 在解释器中一条规则至少要定义一个类,因此一个语言中如果有太多的文法规则,就会使类的个数急剧增加,当值系统的维护难以管理. 执行效率低 在解释器模式中大量的使用了循环和递归调用,所有复杂的句子执行起来,整个过程也是非常的繁琐
4.4.3使用场景
当语言的文法比较简单,并且执行效率不是关键问题. 当问题重复出现,且可以用一种简单的语言来进行表达 当一个语言需要解释执行,并且语言中的句子可以表示为一个抽象的语法树的时候
5.中介者模式
提到中介模式有一个比较经典的例子就是航空管制。 为了让飞机在飞行的时候互不干扰每架飞机都需要知道其他飞机每时每刻的位置这就需要时刻跟其他飞机通信。飞机通信形成的通信网络就会无比复杂。这个时候我们通过引 入“塔台”这样一个中介让每架飞机只跟塔台来通信发送自己的位置给塔台由塔台来 负责每架飞机的航线调度。这样就大大简化了通信网络。 中介模式(mediator pattern)的定义:
定义一个单独的(中介)对象,来封装一组对象之间的交互,将这组对象之间的交互委派给予中介对象交互,来避免对象之间的交互.
中介者对象就是用于处理对象与对象之间的直接交互封装了多个对象之间的交互细节。中介模式的设计跟中间层很像,通过引入中介这个中间层,将一组对象之间的交互关系从多对多的网状关系转换为一对多的星状关系.原来一个对象要跟N个对象交互,现在只需要跟一个中介对象交互,从而最小化对象之间的交互关系,降低代码的复杂度,提高代码的可读性和可维护性.
5.1中介者模式原理 5.1.1中介者模式包含以下主要角色 抽象中介者Mediator角色 它是中介者的接口提供了同事对象注册与转发同事对象信息的抽象方法。 具体中介者ConcreteMediator角色 实现中介者接口定义一个 List 来管理同事对象协调各个同事角色之间的交互关系因此它依赖于同事角色。 抽象同事类Colleague角色 定义同事类的接口保存中介者对象提供同事对象交互的抽象方法实现所有相互影响的同事类的公共功能。 具体同事类Concrete Colleague角色 是抽象同事类的实现者当需要与其他同事对象交互时由中介者对象负责后续的交互。 5.2中介者模式实现
/*** 抽象中介者* author spikeCong* date 2022/10/20**/
public interface Mediator {void apply(String key);
}/*** 具体中介者* author spikeCong* date 2022/10/20**/
public class MediatorImpl implements Mediator {Overridepublic void apply(String key) {System.out.println(最终中介者执行操作,key为: key);}
}/*** 抽象同事类* author spikeCong* date 2022/10/20**/
public abstract class Colleague {private Mediator mediator;public Colleague(Mediator mediator) {this.mediator mediator;}public Mediator getMediator() {return mediator;}public abstract void exec(String key);
}/*** 具体同事类* author spikeCong* date 2022/10/20**/
public class ConcreteColleagueA extends Colleague {public ConcreteColleagueA(Mediator mediator) {super(mediator);}Overridepublic void exec(String key) {System.out.println(在组件A中,通过中介者执行!);getMediator().apply(key);}
}public class ConcreteColleagueB extends Colleague {public ConcreteColleagueB(Mediator mediator) {super(mediator);}Overridepublic void exec(String key) {System.out.println(在组件B中,通过中介者执行!);getMediator().apply(key);}
}public class Client {public static void main(String[] args) {//创建中介者MediatorImpl mediator new MediatorImpl();//创建同事对象Colleague c1 new ConcreteColleagueA(mediator);c1.exec(key-A);Colleague c2 new ConcreteColleagueB(mediator);c2.exec(key-B);}
}在组件A中,通过中介者执行!
最终中介者执行操作,key为: key-A
在组件B中,通过中介者执行!
最终中介者执行操作,key为: key-B5.3中介者模式应用实例
【例】租房
现在租房基本都是通过房屋中介房主将房屋托管给房屋中介而租房者从房屋中介获取房屋信息。房屋中介充当租房者与房屋所有者之间的中介者。
/*** 抽象中介者* author spikeCong* date 2022/10/20**/
public abstract class Mediator {//申明一个联络方法public abstract void contact(String message,Person person);
}/*** 抽象同事类* author spikeCong* date 2022/10/20**/
public abstract class Person {protected String name;protected Mediator mediator;public Person(String name, Mediator mediator) {this.name name;this.mediator mediator;}
}/*** 中介机构* author spikeCong* date 2022/10/20**/
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;}Overridepublic void contact(String message, Person person) {if(person houseOwner){ //如果是房主,则租房者获得信息tenant.getMessage(message);}else { //如果是租房者则获取房主信息houseOwner.getMessage(message);}}
}/*** 具体同事类-房屋拥有者* author spikeCong* date 2022/10/20**/
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 spikeCong* date 2022/10/20**/
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 mediator new MediatorStructure();//房主和租房者只需要知道中介机构即可HouseOwner houseOwner new HouseOwner(路飞, mediator);Tenant tenant new Tenant(娜美, mediator);//中介收集房租和租房者信息mediator.setHouseOwner(houseOwner);mediator.setTenant(tenant);tenant.contact(需要一个两室一厅的房子,一家人住);houseOwner.contact(出租一套两室一厅带电梯,月租5000);}
}5.4中介者模式总结
5.4.1中介者模式的优点
中介者模式简化了对象之间的交互,他用中介者和同事的一对多代替了原来的同事之间的多对多的交互,一对多关系更好理解 易于维护和扩展,将原本难以理解的网状结构转换成习相对简单的星型结构. 可以将各个同事就对象进行解耦.中介者有利于各个同事之间的松耦合,可以独立的改变或者复用每一个同事或者中介者,增加新的中介者类和新的同事类都比较方便,更符合开闭原则 可以减少子类生成,中介者将原本分布与多个对象的行为集中在了一起,改变这些行为只需要生成新的中介者的子类即可,使得同事类可以被重用,无需直接对同事类进行扩展.
5.4.2中介者模式的缺点
在具体中介者类中包含了大量同事之间的交互细节,可能会导致中介者类变得非常的复杂,使得系统不好维护.
5.4.3中介者模式使用场景
系统中对象之间存在复杂的引用关系,系统结构混乱且难以理解. 一个对象由于引用了其他的很多对象并且直接和这些对象进行通信,导致难以复用该对象. 想要通过一个中间类来封装多个类中的行为,而又不想生成太多的子类,此时可以通过引用中介者类来实现,在中介者类中定义对象的交互的公共行为,如果需要改变行为则可以在增加新的中介类.