行为型模式续(中介者模式 + 解释器模式)
1、中介者模式(Mediator)
在现实生活中,常常会出现好多对象之间存在复杂的交互关系,这种交互关系常常是“网状结构”,它要求每个对象都必须知道它需要交互的对象例如,每个人必须记住他(她)所有朋友的电话;而且,朋友中如果有人的电话修改了,他(她)必须告诉其他所有的朋友修改,这叫作“牵一发而动全身”,非常复杂。如果把这种“网状结构”改为“星形结构”的话,将大大降低它们之间的“稠合性”,这时只要找一个“中介者”就可以了。如前面所说的“每个人必须记住所有朋友电话”的问题,只要在网上建立一个每个朋友都可以访问的“通信录”就解决了这样的例子还有很多,例如,你刚刚参加工作想租房,可以找“房屋中介”;或者,自己刚刚到一个陌生城市找工作,可以找“人才交流中心”帮忙。在软件的开发过程中,这样的例子也很多,例如,在MVC框架中,控制器(c)就是模型(M)和视图(v)的中介者;还有大家常用的QQ聊天程序的“中介者”是QQ服务器。所有这些,都可以采用“中介者模式”来实现,它将大大降低对象之间的相合性,提高系统的灵活性。
1.1 模式的定义与结构
中介者模式的定义:定义一个中介对象来封装一系列对象之间的交互,使原有对象之间的精合松散,且可以独立地改变它们之间的交互。中介者模式又叫调停模式,它是迪米特法则的典型应用。
中介者模式是一种对象行为型模式,其主要优点如下。①降低了对象之间的相合性,使得对象易于独立地被复用。②将对象间的一对多关联转变为一对一的关联,提高系统的灵活性,使得系统易于维护和扩展。其主要缺点是:当同事类太多时,中介者的职责将很大,它会变得复杂而庞大,以至于系统难以维护。
中介者模式的结构与实现:介者模式实现的关键是找出“中介者”。包含以下主要角色。
(1)抽象中介者(Mediator)角色:它是中介者的接口,提供了同对象注册与转发同事对象信息的抽象方法。
(2)具体中介者(ConcreteMediator)角色:实现中介者接口,定义一个List来管理同事对象,协调各个同事角色之间的交互关系,因此它依赖于同事角色
(3)抽象同事类(Colleague)角色:定义同事类的接口,保存中介者对象,提供同事对象交互的抽象方法,实现所有相互影响的同事类的公共功能。
(4)具体同事类(ConcreteColleague)角色:是抽象同事类的实现者,当需要与其他同事对象交互时,由中介者对象负责后续的交互。中介者模式的结构图如下图所示。
实现代码如下:
//抽象中介者 abstract class Mediator { public abstract void register(Colleague colleague); public abstract void reply(Colleague colleague); } //具体中介者 class ConsreteMediator extends Mediator{ private List<Colleague> colleagues = new ArrayList1<>(); @Override public void register(Colleague colleague) { if(!colleagues.contains(colleague)) { colleagues.add(colleague); colleague.setMediator(this); } } @Override public void reply(Colleague colleague) { for(Colleague coll : colleagues) { if(!coll.equals(colleague)) { coll.receice(); } } } } //抽象同事类 abstract class Colleague { protected Mediator mediator; public void setMediator(Mediator mediator) { this.mediator = mediator; } public abstract void receice(); public abstract void send(); } //具体同事类1 class ConcreteColleague1 extends Colleague { @Override public void receice() { System.out.println("具体同事类1收到请求"); } @Override public void send() { System.out.println("具体同事类1发出请求"); mediator.reply(this);//请中介者发送 } } //具体同事类2 class ConcreteColleague2 extends Colleague { @Override public void receice() { System.out.println("具体同事类2收到请求"); } @Override public void send() { System.out.println("具体同事类2发出请求"); mediator.reply(this);//请中介者发送 } } public class TestMediatorPattern { public static void main(String[] args) { Mediator mediator = new ConsreteMediator(); Colleague colleague1 = new ConcreteColleague1(); Colleague colleague2 = new ConcreteColleague2(); mediator.register(colleague1); mediator.register(colleague2); colleague1.send(); System.out.println("-------------"); colleague2.send(); } }
1.2 模式的应用实例
用中介者模式编写一个“北京二手房信息交流平台”程序。
说明:北京二手房信息交流平台是“房地产中介公司”提供 给“卖方客户”与“买方客户”进行信息交流的平台,比较适合用中介者模式来实现。首先,定义一个中介公司(Mediator)接口,它是抽象中介者,它包含了客户注册方法register(Customermember)和信息转发方法relay(Stringfrom,Stringad);再定义一个北京房地产中介(PekingEstateMediator)公司,它是具体中介者类,它包含了保存客户信息的List对象,并实现了中介公司中的抽象方法;然后,定义一个客户(Customer)类,它是抽象同事类,其中包含了中介者的对象,和发送信息的send(Stringad)方法与接收信息的receive(Stringfrom,Stringad)方法的接口。
本程序实现窗体程序,继承JFrame类,并实现动作事件的处理方法actionPerformed(ActionEvente);最后,定义卖方(Seller)类和买方(Buyer)类,它们是具体同事类,是客户(Customer)类的子类,它们实现了父类中的抽象方法,通过中介者类进行信息交流,其结构图如图所示。
实现代码如下:
1.3 模式的应用场景和扩展
中介者模式的应用场景:
(1)当对象之间存在复杂的网状结构关系而导致依赖关系混乱且难以复用时。
(2)当想创建一个运行于多个类之间的对象,又不想、生成新的子类时。
模式的扩展在实际开发中,通常采用以下两种方法来简化中介者模式,使开发变得更简单。
(1)不定义中介者接口,把具体中介者对象实现成为单例。
(2)同事对象不持有中介者,而是在需要的时候直接获取中介者对象并调用。图示是简化中介者模式的结构图。
简化中介者模式的示例代码如下:
//简单单例中介者 class SimpleMediator { private static SimpleMediator smd=new SimpleMediator(); private List<SimpleColleague> colleagues=new ArrayList<SimpleColleague>(); private SimpleMediator(){} public static SimpleMediator getMedium() { return(smd); } public void register(SimpleColleague colleague) { if(!colleagues.contains(colleague)) { colleagues.add(colleague); } } public void relay(SimpleColleague scl) { for(SimpleColleague ob:colleagues) { if(!ob.equals(scl)) { ((SimpleColleague)ob).receive(); } } } } //抽象同事类 interface SimpleColleague { void receive(); void send(); } //具体同事类 class SimpleConcreteColleague1 implements SimpleColleague { SimpleConcreteColleague1(){ SimpleMediator smd=SimpleMediator.getMedium(); smd.register(this); } public void receive() { System.out.println("具体同事类1:收到请求。"); } public void send() { SimpleMediator smd=SimpleMediator.getMedium(); System.out.println("具体同事类1:发出请求..."); smd.relay(this); //请中介者转发 } } //具体同事类 class SimpleConcreteColleague2 implements SimpleColleague { SimpleConcreteColleague2(){ SimpleMediator smd=SimpleMediator.getMedium(); smd.register(this); } public void receive() { System.out.println("具体同事类2:收到请求。"); } public void send() { SimpleMediator smd=SimpleMediator.getMedium(); System.out.println("具体同事类2:发出请求..."); smd.relay(this); //请中介者转发 } }
public class SimpleMediatorPattern
{
public static void main(String[] args)
{
SimpleColleague c1,c2;
c1=new SimpleConcreteColleague1();
c2=new SimpleConcreteColleague2();
c1.send();
System.out.println("-----------------");
c2.send();
}
}
2、解释器模式
在软件开发过程中,会遇到有些问题多次重复出现,而且有一定的相似性和规律性。如果将它们归纳成一种简单的语言,那么这些问题实例将是该语言的一些句子,这样就可以用“编译原理”中的解释器模式来实现。 就比如正则表达式,它就是解释器模型的一种应用,解释器为正则表达式定义了一个文法,如何表示一个特定的正则表达式,以及如何解释这个正则表达式。
2.1 模式的定义和特点
解释器模式的定义:在分析对象定义的一个语言,并且定义该语言的文法表示,在设定一个解析器来解释语言中的句子。也就是说,用编译语言的方式来分析应用中的实例。这种模式实现了文法表达式处理的接口,该接口解释一个特定的上下文。
解释器模式扩展性好,容易实现,但是执行效率低。引起类膨胀,应用场景少, 采用递归调用方法,将会导致调试非常复杂。
2.2 模式的结构和实现
解释器模式是一种行为类模式,常用于对简单语言的编译或者实例分析中,为了掌握好它的结构与实现,必须先了解编译原理中的“文法、句子、语法书”的相关概念。
(1)文法:用于描述语言的语法规则的形式规则,任何事情都要有规则,语言一样,不管是机器语言还是自然语言,都有自己的文法规则。
(2)句子,语言的基本单位,是语言集中的一个元素,它由终结符构成,能由“文法”推导出。
(3)语法树,句子结构的一种树形表示,它代表了句子的推导结果,有利于理解句子语法结构的层次。
解释器模式包含以下的角色:
(1)抽象表达式角色:定义解释器的接口,约定解释器的解释操作,主要包含愁抽象方法。
(2)终结符表达式角色:是抽象表达式的子类,用来实现文法与终结符相关的操作,文法中每一个终结符都有一个具体的终结符表达式与之相对应。
(3)非终结符表达式角色:也是抽象表达式的子类,用来实现文法中与非终结符相关的操作,文法中每条规则都对应于一个非终结符的表达式。
(4)环境角色:通常包含各个解释器需要的数据或是公共的功能,一般用来传递被所有解释器共享的数据,后面的解释器可以从这里获取这些值。
(5)客户端角色:主要任务是将需要分析的句子表达式转换成使用解释器对象描述的抽象语法树,然后调用解释器的解释方法。当然也可以通过环境角色间接访问解释器的解释方法。
结构图如下:
//抽象表达式类 interface AbstractExpression { public Object interpreter(); } //终结符表达式 class TerminalExpersion implements AbstractExpression { @Override public Object interpreter() { //对终结符表达式的处理 return null; } } //非终结表达方式 class NonTerminalExpersion implements AbstractExpression { private AbstractExpression abstractExpression; private AbstractExpression abstractExpression2; @Override public Object interpreter() { //对非终结者表达式的处理 return null; } } //环境类 class ContextEnviroment { private AbstractExpression exp; public ContextEnviroment(AbstractExpression exp) { this.exp = exp; } public void operation(String info){ // 调用相关表达式类的解释方法 } } public class TestInterpreterPattern { public static void main(String[] args) { } }
4.3 模式的应用实例
1、利用解释器模式设计一个“北京公交一卡通”的公交卡读卡程序
说明:一卡通读卡程序可以识别乘客的身份,如果是老年卡可以免费乘车。
分析:本实例用“解释器模式”设计比较适合,首先设计其文法规则如下。
<expression> ::= <city>的<person>
<city> ::= 北京
<person> ::= 老人|学生
然后,根据文法规则按以下步骤设计读卡器程序的类图。
- 定义一个抽象表达式(Expression)接口,它包含了解释方法 interpret(String info)。
- 定义一个终结符表达式(Terminal Expression)类,它用集合(Set)类来保存满足条件的城市或人,并实现抽象表达式接口中的解释方法 interpret(Stringinfo),用来判断被分析的字符串是否是集合中的终结符。
- 定义一个非终结符表达式(AndExpressicm)类,它也是抽象表达式的子类,它包含满足条件的城市的终结符表达式对象和满足条件的人员的终结符表达式对象,并实现 interpret(String info) 方法,用来判断被分析的字符串是否是满足条件的城市中的满足条件的人员。
- 最后,定义一个环境(Context)类,它包含解释器需要的数据,完成对终结符表达式的初始化,并定义一个方法 freeRide(String info) 调用表达式对象的解释方法来对被分析的字符串进行解释。其结构图如下图 所示。
程序代码如下:
/*文法规则 <expression> ::= <city>的<person> <city> ::= 北京 <person> ::= 老人 */ //抽象表达式类 interface Expression { public boolean interpret(String info); } //终结符表达式类 class TerminalExpression implements Expression { private Set<String> set= new HashSet<String>(); public TerminalExpression(String[] data) { for(int i=0;i<data.length;i++){ set.add(data[i]); } } public boolean interpret(String info) { if(set.contains(info)) { return true; } return false; } } //非终结符表达式类 class AndExpression implements Expression { private Expression city=null; private Expression person=null; public AndExpression(Expression city,Expression person) { this.city=city; this.person=person; } public boolean interpret(String info) { String s[]=info.split("的"); return city.interpret(s[0])&&person.interpret(s[1]); } } //环境类 class ContextEnv { private String[] citys={"北京"}; private String[] persons={"老人"}; private Expression cityPerson; public ContextEnv() { Expression city=new TerminalExpression(citys); Expression person=new TerminalExpression(persons); cityPerson=new AndExpression(city,person); } public void freeRide(String info) { boolean ok=cityPerson.interpret(info); if(ok) System.out.println("您是"+info+",您本次乘车免费!"); else System.out.println(info+",您不是免费人员,本次乘车扣费2元!"); } } public class InterpreterPattern { public static void main(String[] args) { ContextEnv contextEnv = new ContextEnv(); contextEnv.freeRide("北京的老人"); contextEnv.freeRide("北京的学生"); } }
2、现在通过解释器模式来实现加减乘除四则运算,如计算a+b的值。类图如下:
使用Calculator构造函数传参,并解析封装。这里根据栈的“先进后出”来安排运算的先后顺序(主要用在乘除法,这里只写了加减法比较简单)。以加法为例,Calculator构造函数接收一个表达式,然后把表达式转换为char数组,并判断运算符号,如果是‘+’则进行加法运算,把左边的数(left变量)和右边的数(right变量)加起来即可。
例如a+b-c这个表达式,根据for循环,首先被压入栈中的是a元素生成的VarExpression对象,然后判断到加号时,把a元素的对象从栈中pop出来,与右边的数组b进行相加,而b是通过当前的数组游标下移一个单元格得来的(为了防止该元素被再次遍历,通过++i的方式跳过下一遍历)。减法运算同理。
实现代码如下:
//环境类 class Calculator{ //定义表达式 private CalculatorExpression exp; //构造函数传参、并解析 public Calculator(String exp) { //安排运算的先后顺序 Stack<CalculatorExpression> stack = new Stack<>(); //表达式拆分为字符数组 char[] chars = exp.toCharArray(); CalculatorExpression left = null; CalculatorExpression right = null; for (int i = 0; i < chars.length; i++) { switch(chars[i]) { case '+' : left = stack.pop(); right = new VarExpression(String.valueOf(chars[++i])); stack.push(new AddExpression(left,right)); break; case '-': left = stack.pop(); right = new VarExpression(String.valueOf(chars[++i])); stack.push(new SubExpression(left,right)); break; default: //公式中的变量 stack.push(new VarExpression(String.valueOf(chars[i]))); break; } } this.exp = stack.peek(); } public int run(HashMap<String, Integer> var) { return this.exp.interpreter(var); } } //抽象表达式类: //通过Map键值对,使键对应公式参数,如a、b、c等,值为运算时取得的具体数值。 abstract class CalculatorExpression { //解析公式和数值,key是公式中的参数,value是具体的数值 public abstract int interpreter(HashMap<String, Integer> var) ; } //变量解析器 //通过interpreter()方法从map中取之。 class VarExpression extends CalculatorExpression { private String key; public VarExpression(String key){ this.key = key; } @Override public int interpreter(HashMap<String, Integer> var) { return var.get(this.key); } } //抽象运算符合解析器 //每个运算符合都只和自己左右两个数字有关系, //但左右两个数字有可能也是一个解析的结果,无论何种类型,都是Expression类的实现类。 class SymbolExperssion extends CalculatorExpression { protected CalculatorExpression left; protected CalculatorExpression right; public SymbolExperssion(CalculatorExpression left, CalculatorExpression right) { this.left = left; this.right = right; } @Override public int interpreter(HashMap<String, Integer> var) { // TODO Auto-generated method stub return 0; } } //加法解析器 class AddExpression extends SymbolExperssion { public AddExpression( CalculatorExpression left, CalculatorExpression right){ super(left, right); } public int interpreter(HashMap<String, Integer> var) { return super.left.interpreter(var) + super.right.interpreter(var); } } //减法解析器 class SubExpression extends SymbolExperssion { public SubExpression(CalculatorExpression left, CalculatorExpression right) { super(left, right); } public int interpreter(HashMap<String, Integer> var) { return super.left.interpreter(var) - super.right.interpreter(var); } } //客户端类 //通过getExpStr()方法获取表达式, // 再通过getValue()方法获取值的映射, // 最后再实例化Calculator类,通过run()方法获取最终的运算结果。 public class ExperssionPattern { public static void main(String[] args) throws IOException { String expStr = getExpStr(); HashMap<String, Integer> var = getValue(expStr); Calculator calculator = new Calculator(expStr); System.out.println("运算结果:" + expStr + "=" + calculator.run(var)); } //获得表达式 public static String getExpStr() throws IOException { System.out.print("请输入表达式:"); return (new BufferedReader(new InputStreamReader(System.in))).readLine(); } //获得值映射 public static HashMap<String, Integer> getValue(String expStr) throws IOException { HashMap<String, Integer> map = new HashMap<>(); for(char ch : expStr.toCharArray()) { if(ch != '+' && ch != '-' ) { if(! map.containsKey(String.valueOf(ch))) { System.out.print("请输入" + String.valueOf(ch) + "的值:"); String in = (new BufferedReader(new InputStreamReader(System.in))).readLine(); map.put(String.valueOf(ch), Integer.valueOf(in)); } } } return map; } }
2.4 模式的应用场景
(1)可以将该语言中的句子表示为一个抽象语法树时,比如Json、xml
(2)一些重复出现的问题可以用一种简单的语言来表达
(3)一个简单语法需要解释的场景
2.5 模式的拓展
日常的项目开发过程中,如果要对数据表达式进行分析与计算,无须再用解释器模式进行设计了,Java提供了一下强大的数学公式解释器:Experssion4j,MESP和Jep(Java expression Parser)等,他们可以解释一些复杂的文法,功能强大,使用简单。
实例使用Jep,Java表达式分析器,用来转换和计算数学表达式的的Java库,通过这个数据库,用户可以以字符串的形式输入一个任意的公司,然后快速计算出结果,而且Jep支持用户自定义变量,常量和函数,包含许多常用的数学函数和常量。
class JepDemo { public static void main(String[] args) { //方式一 /* //一个数学表达式 JEP jep = new JEP(); //给变量赋值 String exp = "((a+b)*(c+b))/(c+a)/b"; jep.addVariable("a", 16); jep.addVariable("b", 5); jep.addVariable("c", 66); try { //执行 jep.parseExpression(exp); Object result = jep.getValue(); System.out.println("计算结果: " + result); } catch (Exception e) { System.out.println("An error occured: " + e.getMessage()); }*/ //方式二 try { String exp = "((a+b)*(c+b))/(c+a)/b"; JEP jep = new JEP(); jep.addVariable("a", 16); jep.addVariable("b", 5); jep.addVariable("c", 66); Node parse = jep.parse(exp); Object evaluate = jep.evaluate(parse); System.out.println(evaluate); } catch (ParseException e) { e.printStackTrace(); } } }
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· 字符编码:从基础到乱码解决
· 提示词工程——AI应用必不可少的技术