十一大行为模式(下)

编程入门 行业动态 更新时间:2024-10-10 05:21:23

十<a href=https://www.elefans.com/category/jswz/34/1757659.html style=一大行为模式(下)"/>

十一大行为模式(下)

行为模式之七:观察者模式

定义对象间一种一对多的依赖关系,使得每当一个对象改变状态,则所有依赖于它的对象都会得到通知并被自动更新。
(来自《设计模式之禅》)

你要的故事

想来想去,就拿我们现在生活中最常体会到的事情来讲观察者模式--朋友圈。小明、小红、小东 3 人是好朋友,最近他们的父母都给安排了手机,刚用上手机那是相当的兴奋呀。他们立马从 QQ 转投到微信的怀抱,对微信的朋友圈玩的不亦乐乎,什么事情都往上面发。突然有一天,小明和小红因为一些小事争执闹别扭了,原因就是他们对一道数学题有不同的见解。就跟我们小时候和朋友玩得好好的,突然因为一点小事就闹翻了。小红比较孩子气,立马就屏蔽了小明的朋友圈,不想再看到有关小明相关的信息。故事就是这么一回事,关注点就在这朋友圈上。朋友圈就是运用观察者模式的一个很好的样例。为什么这么说?我们发朋友圈的时候,那些没有屏蔽我们朋友圈的好友,会收到信息推送。也就是没有屏蔽我们朋友圈的好友其实是订阅了我们朋友圈,好友相当于观察者,我们是被观察的对象。符合观察者模式这个关系。

我们通过代码来描述小明、小红、小东他们在朋友圈玩的场景。利用观察者模式,需要观察对象和被观察对象,所以我们先定义 2 个接口,分别是 Observable (可被观察接口) 和 Observer (观察者接口)。

实现 Observable 接口的对象说明是可被订阅观察的,所以它需要 addObserver() 新增订阅者方法和 removeObserver() 移除订阅者方法,另外还有一个是必须的,就是通知各个订阅者消息的方法 notifyObservers()。那 Observable 接口代码如下所示。

interface Observable {void addObserver(Observer observer);void removeObserver(Observer observer);void notifyObservers(String message);
}

实现 Observer 接口的对象说明是可以去订阅观察的,也就是说可以接收被订阅的对象发出来的消息,那就需要一个接收消息的方法 update()。代码如下所示。

interface Observer {void update(String name, String message);
}

为了让大家不混淆,先把观察者和被观察者分离开,其实在这个例子中,观察者和被观察者是同一个对象 User 的。这里就分开,分成 User 和 Friend,后面会给出正确的代码,稍安勿躁哈。这里 User 作为被观察者,实现了 Observable 接口,而 Friend 作为观察者,实现了 Observer 接口。代码如下。

class User implements Observable {private List<Observer> friends;private String name;public User(String name) {this.name = name;this.friends = new LinkedList<>();}public void sendMessage(String message) {this.notifyObservers(message);}@Overridepublic void addObserver(Observer observer) {this.friends.add(observer);}@Overridepublic void removeObserver(Observer observer) {this.friends.remove(observer);}@Overridepublic void notifyObservers(String message) {this.friends.forEach(friend -> {friend.update(this.name, message);});}
}class Friend implements Observer {private String name;public Friend(String name) {this.name = name;}@Overridepublic void update(String name, String message) {System.out.println("【" + this.name + "】看到【" + name + "】发的朋友圈:" + message);}
}public class ObserverTest {public static void main(String[] args) {User xiaoMing = new User("小明");Friend xiaoHong = new Friend("小红");Friend xiaoDong = new Friend("小东");xiaoMing.addObserver(xiaoHong);xiaoMing.addObserver(xiaoDong);xiaoMing.sendMessage("今天真开心");// 小红和小明闹别扭了,小红取消订阅小明的朋友圈xiaoMing.removeObserver(xiaoHong);xiaoMing.sendMessage("希望明天也像今天一样开心");}}打印结果:
【小红】看到【小明】发的朋友圈:今天真开心
【小东】看到【小明】发的朋友圈:今天真开心
【小东】看到【小明】发的朋友圈:希望明天也像今天一样开心

看到代码执行结果,小红和小东都订阅了小明的朋友圈,小明发了朋友圈:今天真开心。他们俩都收到了,因为小红和小明闹别扭,小红取消订阅小明的朋友圈,所以小明后来发的朋友圈,小红没收到。

上面代码其实是不对的,不应该用 User 和 Friend 2 个类来定义。如果小明订阅小红和小东的朋友圈呢?这样实现比较麻烦,主要是为了分清 观察者 和 被观察者 这 2 个概念,通过上面的例子应该分清楚了 2 个概念了,那就可以来看正确的代码,小明、小红、小东他们其实都是观察者和被观察者,所以我们用 User2 来定义他们就可以,User2 实现了 Observable 和 Observer 接口。代码如下。

class User2 implements Observable, Observer {private List<Observer> friends;private String name;public User2(String name) {this.name = name;this.friends = new LinkedList<>();}@Overridepublic void addObserver(Observer observer) {this.friends.add(observer);}@Overridepublic void removeObserver(Observer observer) {this.friends.remove(observer);}@Overridepublic void notifyObservers(String message) {this.friends.forEach(friend -> {friend.update(this.name, message);});}@Overridepublic void update(String name, String message) {System.out.println("【" + this.name + "】看到【" + name + "】发的朋友圈:" + message);}public void sendMessage(String message) {this.notifyObservers(message);}
}public class ObserverTest {public static void main(String[] args) {User2 xiaoMing2 = new User2("小明");User2 xiaoHong2 = new User2("小红");User2 xiaoDong2 = new User2("小东");xiaoMing2.addObserver(xiaoHong2);xiaoMing2.addObserver(xiaoDong2);xiaoMing2.sendMessage("今天真开心");xiaoMing2.removeObserver(xiaoHong);xiaoMing2.sendMessage("希望明天也像今天一样开心");xiaoHong2.addObserver(xiaoMing2);xiaoHong2.addObserver(xiaoDong2);xiaoHong2.sendMessage("今天和小明吵架了,屏蔽他的朋友圈");xiaoDong2.addObserver(xiaoMing2);xiaoDong2.addObserver(xiaoHong2);xiaoDong2.sendMessage("小明和小红吵架了,夹在中间好尴尬");}}打印结果:
【小红】看到【小明】发的朋友圈:今天真开心
【小东】看到【小明】发的朋友圈:今天真开心
【小红】看到【小明】发的朋友圈:希望明天也像今天一样开心
【小东】看到【小明】发的朋友圈:希望明天也像今天一样开心
【小明】看到【小红】发的朋友圈:今天和小明吵架了,屏蔽他的朋友圈
【小东】看到【小红】发的朋友圈:今天和小明吵架了,屏蔽他的朋友圈
【小明】看到【小东】发的朋友圈:小明和小红吵架了,夹在中间好尴尬
【小红】看到【小东】发的朋友圈:小明和小红吵架了,夹在中间好尴尬

从代码中,我们看到小明、小红、小东 3 个人互相订阅朋友圈,当然中途小红屏蔽了小明的朋友圈。这就是 观察者 和 被观察者 刚好是同一个对象的实现。

总结

观察者模式 是一个比较特殊的设计模式,它定义了触发机制,观察者只要订阅了被观察者,就可以第一时间得到被观察者传递的信息。在工作中,使用观察者模式的场景也比较多,比如消息队列消费,Android 开发中的事件触发机制等等。好,观察者模式就到这。

行为模式之八:状态模式

当一个对象内在状态改变时允许其改变行为,这个对象看起来像改变了其类。
(来自《设计模式之禅》)

你要的故事

现在有好多个人贷款软件,比如:支付宝、360借条(打广告。。。)等等。贷款会有一个用户状态流程,游客->注册用户->授信用户->借款用户(这里简化了状态,只用 4 个)。每个状态拥有的权限不一样,如下图所示。

状态

从上图可以看到,一个用户有 3 种行为,分别是注册、授信、借款。当注册成功后,用户的状态就从『游客』改变为『注册用户』;当授信成功后,用户的状态就从『注册用户』改变为『授信用户』;当借款成功后,用户的状态就从『授信用户』改变为『借款用户』。现在我们就来实现用户注册、授信、借款的过程,因为每个状态的权限不一样,所以这里需要根据用户的状态来限制用户行为。

很快,我们就完成下面的代码。

class User {private String state;public String getState() {return state;}public void setState(String state) {this.state = state;}public void register() {if ("none".equals(state)) {System.out.println("游客。注册中。。。");}else if ("register".equals(state)) {System.out.println("注册用户。不需要再注册。");} else if ("apply".equals(state)) {System.out.println("授信用户。不需要再注册。");} else if ("draw".equals(state)) {System.out.println("借款用户。不需要再注册。");}}public void apply() {if ("none".equals(state)) {System.out.println("游客。不能申请授信。");}else if ("register".equals(state)) {System.out.println("注册用户。授信申请中。。。");} else if ("apply".equals(state)) {System.out.println("授信用户。不需要再授信。");} else if ("draw".equals(state)) {System.out.println("借款用户。不需要再授信。");}}public void draw(double money) {if ("none".equals(state)) {System.out.println("游客。申请借款【" + money + "】元。不能申请借款。");} else if ("register".equals(state)) {System.out.println("注册用户。申请借款【" + money + "】元。还没授信,不能借款。");} else if ("apply".equals(state)) {System.out.println("授信用户。申请借款【" + money + "】元。申请借款中。。。");} else if ("draw".equals(state)) {System.out.println("授信用户。申请借款【" + money + "】元。申请借款中。。。");}}
}public class NoStateTest {public static void main(String[] args) {User user = new User();user.setState("register");user.draw(1000);}}打印结果:
注册用户。申请借款【1000.0】元。还没授信,不能借款。

上面代码实现了用户 register (注册),apply (授信),draw (借款) 这 3 种行为,每个行为都会根据状态 state 来做权限控制。看起来有点繁琐,扩展性不高,假设新增了一个状态,那么注册、授信、借款这 3 种行为的代码都要修改。下面通过状态模式来解决这个问题。

我们把状态给抽出来,作为一个接口,因为在每种状态中都可能有注册、授信、借款行为,所以把这 3 个行为作为状态接口的方法,让每个状态子类都实现相应的行为控制。如下代码所示。

interface State {void register();void apply();void draw(double money);
}/*** 游客*/
class NoneState implements State {@Overridepublic void register() {System.out.println("游客。注册中。。。");}@Overridepublic void apply() {System.out.println("游客。不能申请授信。");}@Overridepublic void draw(double money) {System.out.println("游客。申请借款【" + money + "】元。不能申请借款。");}
}/*** 注册状态*/
class RegisterState implements State {@Overridepublic void register() {System.out.println("注册用户。不需要再注册。");}@Overridepublic void apply() {System.out.println("注册用户。授信申请中。。。");}@Overridepublic void draw(double money) {System.out.println("注册用户。申请借款【" + money + "】元。还没授信,不能借款。");}
}/*** 授信状态*/
class ApplyState implements State {@Overridepublic void register() {System.out.println("授信用户。不需要再注册。");}@Overridepublic void apply() {System.out.println("授信用户。不需要再授信。");}@Overridepublic void draw(double money) {System.out.println("授信用户。申请借款【" + money + "】元。申请借款中。。。");}
}/*** 借款状态*/
class DrawState implements State {@Overridepublic void register() {System.out.println("借款用户。不需要再注册。");}@Overridepublic void apply() {System.out.println("借款用户。不需要再授信。");}@Overridepublic void draw(double money) {System.out.println("申请借款【" + money + "】元。申请借款中。。。");}
}class User1 {private State state;public State getState() {return state;}public void setState(State state) {this.state = state;}public void register() {this.state.register();}public void apply() {this.state.apply();}public void draw(double money) {this.state.draw(money);}
}public class StateTest {public static void main(String[] args) {User1 user1 = new User1();user1.setState(new RegisterState());user1.apply();user1.draw(1000);user1.setState(new ApplyState());user1.draw(2000);}}
打印结果:
注册用户。授信申请中。。。
注册用户。申请借款【1000.0】元。还没授信,不能借款。
授信用户。申请借款【2000.0】元。申请借款中。。。

看上面代码,我们抽象了 State 接口,4 种状态分别用 NoneState (游客)、RegisterState (注册)、ApplyState (授信)、DrawState (借款) 表示。而每个状态都有 3 种行为,它们各自对这些行为进行权限控制。这样子实现可以让权限逻辑分离开,分散到每个状态里面去,如果以后要业务扩展,要新增状态,那就很方便了,只需要再实现一个状态类就可以,不会影响到其他代码。这也是为什么《阿里巴巴 Java 开发手册》里面讲的,当超过 3 层的 if-else 的逻辑判断代码,推荐用状态模式来重构代码。

总结

状态模式 很好的减低了代码的复杂性,从而提高了系统的可维护性。在业务开发中可以尝试使用,比如在迭代开发中,业务逻辑越来越复杂,从而不得不使用很多 if-else 语句来实现时,就可以考虑一下是不是可以用 状态模式 来重构,特别是一些有状态流程转换方面的业务。看到这篇文章,想想工作中是不是有些复杂的代码可以重构,赶紧行动起来。

 

行为模式之九:备忘录模式

在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。
(来自《设计模式之禅》)

你要的故事

点开看这篇文章的各位,都是。。。程序界的大佬。作为程序猿,免不了『上线』这件小事。每逢上线必祭天。。。上线这件事我们很多人都操作过,每家公司有不同的上线流程以及上线的技术能力。按照发布的平台的完善程度大概分为 3 种。

  1. 发布平台牛逼的公司:只需要按下『一键部署』按钮,就搞定上线,按下『回滚』按钮,就搞定回滚上一个版本。

  2. 发布平台稍差点的公司:可能就得多个步骤操作了,上线:备份并关闭应用、部署并启动新应用;回滚:关闭新应用、恢复旧应用并启动。

  3. 没有发布平台的公司:那就全程手工操作,上线:关闭旧应用、复制旧应用到备份空间、复制新应用到部署环境、启动新应用;回滚:关闭新应用、删除新应用、从备份空间复制旧应用到部署环境、启动旧应用。

其实通过发布平台完善程度可以侧面反映企业的技术成熟程度。怎么说呢?发布系统的操作难易程度在我们作为程序猿心中,都有一个可接受的范围。假设刚开始是单体应用,一个 tomcat 和一个 war 就搞定,你需要发布平台么?并不需要,咱 ctrl+Cctrl+Vshutdownstartup 就行,还弄什么发布平台。当系统有多套服务时,每次上线都需要部署 10 个机器的应用,这时你能忍么?要是还是手工操作,那发布一次系统得花费好长时间,要是再搞不好,回滚一次,一晚上都没了;这时就会逼迫开发出一个简易的发布平台,把对多台机器的操作步骤放到发布平台上。当系统是以微服务的架构发展时,每个服务都有上百个实例,那这时就不能简单的把操作步骤搬到发布平台了,还得简化步骤,最终变成上面说的 一键部署 和一键回滚

上面的 3 种我都亲身经历过。。。在刚出来实习时候,就经历了第 3 种情况,因为是单体应用,一个应用搞定所有东西,手动部署已满足要求。到了银行工作,接触到了云平台,那时就只需要一个按钮就唰唰唰的部署了,也是上面说的第 1 种。而现在,正在经历第 2 种发布平台,只是简单的把操作步骤搬到了系统上,目前的情况是机器越来越多,操作步骤没删减的话,每次发布会花费很多时间,这也会去促进开发出更方便使用的发布平台。

回到今天的主题,今天讲的是备忘录模式,从字面上理解,就是讲备份东西,有了备份就可以恢复。上面讲了一大堆发布的东西,也是咱们工作中接触蛮多的事情,发布的最核心就是要支持部署新应用以及回滚老应用回滚特别重要,它能够保证在新应用出现异常的情况下,马上恢复到旧应用可用的状态,减少异常的影响面。发布这东东也很符合备忘录模式,下面通过模拟发布步骤代码来讲备忘录模式。这里讲的不是上面说的第 1 种,这里围绕着第 3 种发布步骤写,不管哪种发布平台,它们的底层都是一样的。

先定义应用实例这个类,应用一般会有应用名、版本号信息。

/*** 应用实例*/
class App {private String content;private String version;public App(String content, String version) {this.content = content;this.version = version;}public String getContent() {return content;}public void setContent(String content) {this.content = content;}public String getVersion() {return version;}public void setVersion(String version) {this.version = version;}@Overridepublic String toString() {return "App{" +"content='" + content + '\'' +", version='" + version + '\'' +'}';}
}

定义 AppBackup 来充当备忘录角色,它有一个属性就是 App,也就是备份的应用。

/*** 应用备份(充当备忘录角色)*/
class AppBackup {private App app;public AppBackup(App app) {this.app = app;}public App getApp() {return app;}public void setApp(App app) {this.app = app;}
}

有了备忘录,也需要一个空间来存放备忘录,并对外提供备忘录。

/*** 备份空间*/
class Space {private AppBackup appBackup;public AppBackup getAppBackup() {return appBackup;}public void setAppBackup(AppBackup appBackup) {this.appBackup = appBackup;}
}

有了这些备份机制,还需要有一个程序猿来部署,这位同学需要掌握发布步骤的所有过程,部署新应用以及回滚旧应用。

/*** 部署应用的同学*/
class Deployer {// 要部署的应用private App app;public App getApp() {return app;}// 设置部署应用public void setApp(App app) {this.app = app;}// 创建应用的备份public AppBackup createAppBackup() {return new AppBackup(app);}// 从备忘录恢复应用public void setAppBackup(AppBackup appBackup) {this.app = appBackup.getApp();}// 显示应用的信息public void showApp() {System.out.println(this.app.toString());}// 暂停应用public void stopApp() {System.out.println("暂停应用:" + this.app.toString());}// 启动应用public void startApp() {System.out.println("启动应用:" + this.app.toString());}
}

再献上测试代码。

public class MementoTest {public static void main(String[] args) {Deployer deployer = new Deployer();deployer.setApp(new App("apply-system", "1.0.0"));System.out.println("1. 暂停旧应用");deployer.stopApp();System.out.println("2. 备份旧应用");Space space = new Space();space.setAppBackup(deployer.createAppBackup());System.out.println("3. 拷贝新应用到服务器");deployer.setApp(new App("apply-system", "2.0.0"));deployer.showApp();System.out.println("4. 启动新应用");deployer.startApp();System.out.println("5. 有异常,暂停新应用");deployer.stopApp();System.out.println("6. 回滚旧应用,拷贝备份的旧应用到服务器");deployer.setAppBackup(space.getAppBackup());deployer.showApp();System.out.println("7. 启动备份的旧应用");deployer.startApp();}}打印结果:
1. 暂停旧应用
暂停应用:App{content='apply-system', version='1.0.0'}
2. 备份旧应用
3. 拷贝新应用到服务器
App{content='apply-system', version='2.0.0'}
4. 启动新应用
启动应用:App{content='apply-system', version='2.0.0'}
5. 有异常,暂停新应用
暂停应用:App{content='apply-system', version='2.0.0'}
6. 回滚旧应用,拷贝备份的旧应用到服务器
App{content='apply-system', version='1.0.0'}
7. 启动备份的旧应用
启动应用:App{content='apply-system', version='1.0.0'}

备忘录模式代码实现搞定。有同学会不会觉得挺麻烦的,为什么要有AppBackup?我们看看个人介绍,在对象之外保存状态,AppBackup 就是对象之外的对象,用来保存旧应用。

总结

备忘录模式定义了一个备份机制。在很多场景都有类似备忘录模式的实现,比如数据库的事务的回滚机制。在平常业务开发中并没有经常使用这个设计模式,但是我们有使用它的思想,比如我们用数据库或者其他中间件做备份数据,其中备份思想是一致的。

行为模式之十:解释器模式

给定一门语言,定义它的文法的一种表示,并定义一个解释器,该解释器使用该表示来解释语言中的句子。
(来自《设计模式之禅》)

你要的故事

解释器顾名思义就是对 2 个不同的表达方式进行转换,让本来不懂的内容解释成看得懂的。比如翻译官就是解释器,把英文翻译成中文,让我们明白外国人说什么。咱们工作中也有很多类似的场景,开发系统避免不了使用数据库,数据库有特定的语法,我们称为 SQL (Structured Query Language),而我们系统开发语言和 SQL 的语法不一样,这中间就需要做一层转换,像把 Java 语言中的 userDao.save(user) 变成 insert into user (name,age) values ('小明', 18),这一层转换也可以称为解释器。很多框架实现了这个功能,比如 Hibernate,我们称这些框架为 ORM

今天,我们就来简单的实现 SQL 拼接解释器,通过参数组装成我们要的 SQL 语句。好多开发同学都吐槽工作天天在 CRUD,也就是只干增删改查的活,对于 SQL 我们经常用的也就是这 4 种语法:insert 语句、delete 语句、update 语句、select 语句。这 4 种语法各有不同,也即需要不同的解释器去解析。利用今天要讲的解释器模式,我们来实现一番。

解释器模式中,会有一个上下文类,这个类用于给解释器传递参数。这里我们 SQL 解释器需要的参数分别是

  1. tableName :数据库名

  2. params :修改时更新后的数据

  3. wheres :where 语句后的条件

class Context {private String tableName;private Map<String, Object> params = new HashMap<>();private Map<String, Object> wheres = new HashMap<>();public String getTableName() {return tableName;}public void setTableName(String tableName) {this.tableName = tableName;}public Map<String, Object> getParams() {return params;}public void setParams(Map<String, Object> params) {this.params = params;}public Map<String, Object> getWheres() {return wheres;}public void setWheres(Map<String, Object> wheres) {this.wheres = wheres;}
}

解释器主角来了,定义 SQL 解释器抽象类,它有一个抽象方法 interpret,通过这个方法来把 context 中的参数解释成对应的 SQL 语句。

/*** SQL 解释器*/
abstract class SQLExpression {public abstract String interpret(Context context);}

我们上面说了 SQL 语句用的比较多的就是 4 种,每一种其实就是一个解释器,因为语法不一样,解释的逻辑也就不一样,我们就利用 SQLExpression 解释器抽象类,来实现 4 个具体的 SQL 解释器,分别如下:

Insert SQL 解释器代码实现:

/*** Insert SQL 解释器*/
class InsertSQLExpression extends SQLExpression {@Overridepublic String interpret(Context context) {StringBuilder insert = new StringBuilder();insert.append("insert into ").append(context.getTableName());// 解析 key valueStringBuilder keys = new StringBuilder();StringBuilder values = new StringBuilder();keys.append("(");values.append("(");for (String key : context.getParams().keySet()) {keys.append(key).append(",");values.append("'").append(context.getParams().get(key)).append("',");}keys = keys.replace(keys.length() - 1, keys.length(), ")");values = values.replace(values.length() - 1, values.length(), ")");// 拼接 keys valuesinsert.append(keys).append(" values ").append(values);System.out.println("Insert SQL : " + insert.toString());return insert.toString();}
}

Update SQL 解释器代码实现:

/*** Update SQL 解释器*/
class UpdateSQLExpression extends SQLExpression {@Overridepublic String interpret(Context context) {StringBuilder update = new StringBuilder();update.append("update ").append(context.getTableName()).append(" set ");StringBuilder values = new StringBuilder();for (String key : context.getParams().keySet()) {values.append(key).append(" = '").append(context.getParams().get(key)).append("',");}StringBuilder wheres = new StringBuilder();wheres.append(" 1 = 1 ");for (String key : context.getWheres().keySet()) {wheres.append(" and ").append(key).append(" = '").append(context.getWheres().get(key)).append("'");}update.append(values.substring(0, values.length() - 1)).append(" where ").append(wheres);System.out.println("Update SQL : " + update.toString());return update.toString();}
}

Select SQL 解释器代码实现:

/*** Select SQL 解释器*/
class SelectSQLExpression extends SQLExpression {@Overridepublic String interpret(Context context) {StringBuilder select = new StringBuilder();select.append("select * from ").append(context.getTableName()).append(" where ").append(" 1 = 1 ");for (String key : context.getWheres().keySet()) {select.append(" and ").append(key).append(" = '").append(context.getWheres().get(key)).append("'");}System.out.println("Select SQL : " + select.toString());return select.toString();}
}

Delete SQL 解释器代码实现

/*** Delete SQL 解释器*/
class DeleteSQLExpression extends SQLExpression {@Overridepublic String interpret(Context context) {StringBuilder delete = new StringBuilder();delete.append("delete from ").append(context.getTableName()).append(" where ").append(" 1 = 1");for (String key : context.getWheres().keySet()) {delete.append(" and ").append(key).append(" = '").append(context.getWheres().get(key)).append("'");}System.out.println("Delete SQL : " + delete.toString());return delete.toString();}
}

测试代码

public class InterpreterTest {public static void main(String[] args) {Context context = new Context();context.setTableName("user");// Insert SQLMap<String, Object> params = new HashMap<>();params.put("name", "小明");params.put("job", "Java 工程师");context.setParams(params);SQLExpression sqlExpression = new InsertSQLExpression();String sql = sqlExpression.interpret(context);// Delete SQLMap<String, Object> wheres = new HashMap<>();wheres.put("name", "小明");context.setParams(null);context.setWheres(wheres);sqlExpression = new DeleteSQLExpression();sql = sqlExpression.interpret(context);// Update SQLparams = new HashMap<>();params.put("job", "Java 高级工程师");wheres = new HashMap<>();wheres.put("name", "小明");context.setParams(params);context.setWheres(wheres);sqlExpression = new UpdateSQLExpression();sql = sqlExpression.interpret(context);// Select SQLwheres = new HashMap<>();wheres.put("name", "小明");context.setParams(null);context.setWheres(wheres);sqlExpression = new SelectSQLExpression();sql = sqlExpression.interpret(context);}}打印结果:Insert SQL : insert into user(name,job) values ('小明','Java 工程师')
Delete SQL : delete from user where  1 = 1 and name = '小明'
Update SQL : update user set job = 'Java 高级工程师' where  1 = 1  and name = '小明'
Select SQL : select * from user where  1 = 1  and name = '小明'

上面实现了整个解释器模式的代码,其实咱们在开发中,SQL 解析没有这么去实现,更多是用一个工具类把上面的各个 SQL 解释器的逻辑代码分别实现在不同方法中,如下代码所示。因为咱们可以预见的就这 4 种语法类型,基本上不用什么扩展,用一个工具类就足够了。

class SQLUtil {public static String insert(String tableName, Map<String, Object> params) {StringBuilder insert = new StringBuilder();insert.append("insert into ").append(tableName);// 解析 key valueStringBuilder keys = new StringBuilder();StringBuilder values = new StringBuilder();keys.append("(");values.append("(");for (String key : params.keySet()) {keys.append(key).append(",");values.append("'").append(params.get(key)).append("',");}keys = keys.replace(keys.length() - 1, keys.length(), ")");values = values.replace(values.length() - 1, values.length(), ")");// 拼接 keys valuesinsert.append(keys).append(" values ").append(values);System.out.println("Insert SQL : " + insert.toString());return insert.toString();}public static String update(String tableName, Map<String, Object> params, Map<String, Object> wheres) {StringBuilder update = new StringBuilder();update.append("update ").append(tableName).append(" set ");StringBuilder values = new StringBuilder();for (String key : params.keySet()) {values.append(key).append(" = '").append(params.get(key)).append("',");}StringBuilder wheresStr = new StringBuilder();wheresStr.append(" 1 = 1 ");for (String key : wheres.keySet()) {wheresStr.append(" and ").append(key).append(" = '").append(wheres.get(key)).append("'");}update.append(values.substring(0, values.length() - 1)).append(" where ").append(wheresStr);System.out.println("Update SQL : " + update.toString());return update.toString();}public static String select(String tableName, Map<String, Object> wheres) {StringBuilder select = new StringBuilder();select.append("select * from ").append(tableName).append(" where ").append(" 1 = 1 ");for (String key : wheres.keySet()) {select.append(" and ").append(key).append(" = '").append(wheres.get(key)).append("'");}System.out.println("Select SQL : " + select.toString());return select.toString();}public static String delete(String tableName, Map<String, Object> wheres) {StringBuilder delete = new StringBuilder();delete.append("delete from ").append(tableName).append(" where ").append(" 1 = 1");for (String key : wheres.keySet()) {delete.append(" and ").append(key).append(" = '").append(wheres.get(key)).append("'");}System.out.println("Delete SQL : " + delete.toString());return delete.toString();}
}

总结

上面用解释器模式实现了 SQL 解释器,然后又指明了实际上咱们开发中大多数是直接一个 SQLUtil 工具类就搞定,并不是说解释器模式没用,想表达的观点是:解释器在工作中很少使用,工作中我们一般遵循的是能用就好策略,满足当前需求,加上一些易扩展性就足够了。解释器模式有比较大的扩展性,就如上面,再加上个建表语句 create table 只需要加一个 CreateTableSQLExpression 就可以轻松实现,不用去改动其他解释器代码。

行为模式之十一:访问者模式

封装一些作用于某种数据结构中的各元素的操作,它可以在不改变数据结构的前提下定义作用于这些元素的新的操作。
(来自《设计模式之禅》)

你要的故事

先声明一下,下面故事全瞎编的。。。

我们是否还记得 N 年前反腐开始的时候,有一段时间提倡官员宴请吃饭只能几菜几汤,不能超出。我记得那会刚读大一,军事理论的老师说到这个问题,也发表了他的一些想法,他觉得这么做比较刻板。今天的故事就和宴请有关。现在中国企业发展越来越大,在社会中担任的责任也越来越大,政府也越来越重视企业,官员去参观企业是常有的事,而企业宴请官员也变得格外的常见。

故事的背景就是企业宴请各级官员。不同级别的官员宴请的菜式就不一样,每家企业的菜式丰富程度也不一样。我们这里的访问对象就用 Alibaba 和 Tencent 这 2 家公司,而访问者就用郭嘉领导人和省领导人做举例。这 2 家公司都跟喜来登酒店合作,Alibaba 合作方案是:宴请省级领导人及以下官员则十菜一汤,宴请郭嘉领导人及以上官员则十四菜两汤;Tencent 合作方案是:宴请省领导人及以下官员则八菜一汤,宴请郭嘉领导人及以上官员则十六菜两汤。

下面看看如何用访问者模式来实现上面的故事。

首先定义一个抽象类:企业。企业有一个共有的特性就是接受上级领导的访问。

/*** 企业*/
abstract class Company {public abstract void accept(Vistor vistor);}

上面故事我们举例了 2 家企业,分别是 Alibaba 和 Tencent,这里实现这 2 家公司的宴请方案,并实现接待访问者方法。

Alibaba 宴请郭嘉领导人及以上官员是十四菜两汤,宴请省领导及以下是十菜一汤。

/*** Alibaba 企业*/
class AlibabaCompany extends Company {@Overridepublic void accept(Vistor vistor) {vistor.visit(this);}public String entertainBelowProvincialLeader(String leader) {return "Alibaba 接待" + leader + ":十菜一汤";}public String entertainAboveNationalLeader(String leader) {return "Alibaba 接待" + leader + ":十四菜两汤";}}

Tencent 宴请郭嘉领导人及以上是十六菜两汤,宴请省领导及以下是八菜一汤。

/*** Tencent 企业*/
class TencentCompany extends Company {@Overridepublic void accept(Vistor vistor) {vistor.visit(this);}public String entertainBelowProvincialLeader(String leader) {return "Tencent 接待" + leader + ":八菜一汤";}public String entertainAboveNationalLeader(String leader) {return "Tencent 接待" + leader + ":十六菜两汤";}
}

这里定义访问者接口,访问者接口有 2 个方法,分别是访问 Alibaba 企业和访问 Tencent 企业。

/*** 访问者接口*/
interface Vistor {void visit(AlibabaCompany alibabaCompany);void visit(TencentCompany tencentCompany);}

上面故事中有 2 个访问者,一个是郭嘉领导人,另一个是省领导人,因为不同企业对应不同访问者有不同的宴请方案,所以这里访问企业是需要调用对应企业的宴请方式。

省领导人访问企业时,需要调用企业对省领导及以下官员的宴请方案,为entertainBelowProvincialLeader()

/*** 省领导访问*/
class ProvincialLeaderVistor implements Vistor {@Overridepublic void visit(AlibabaCompany alibabaCompany) {System.out.println(alibabaCompany.entertainBelowProvincialLeader("省领导"));}@Overridepublic void visit(TencentCompany tencentCompany) {System.out.println(tencentCompany.entertainBelowProvincialLeader("省领导"));}
}

郭嘉领导人访问企业时,需要调用企业对郭嘉领导人的宴请方案,为entertainAboveNationalLeader()

/*** 郭嘉领导访问*/
class NationalLeaderVistor implements Vistor {@Overridepublic void visit(AlibabaCompany alibabaCompany) {System.out.println(alibabaCompany.entertainAboveNationalLeader("省领导"));}@Overridepublic void visit(TencentCompany tencentCompany) {System.out.println(tencentCompany.entertainAboveNationalLeader("郭嘉领导"));}
}

上面是访问者和被访问者的代码,因为企业是在喜来登酒店宴请领导人,所以这里还需要一个酒店,酒店里面有企业合作的名单,以及负责宴请各路领导的方法提供。

/*** 酒店*/
class Hotel {private List<Company> companies = new ArrayList<>();public void entertain(Vistor vistor) {for (Company company : companies) {company.accept(vistor);}}public void add(Company company) {companies.add(company);}
}

下面提供测试代码,看看运行的结果怎样。

public class VisitorTest {public static void main(String[] args) {AlibabaCompany alibabaCompany = new AlibabaCompany();TencentCompany tencentCompany = new TencentCompany();ProvincialLeaderVistor provincialLeaderVistor = new ProvincialLeaderVistor();NationalLeaderVistor nationalLeaderVistor = new NationalLeaderVistor();Hotel xilaideng = new Hotel();xilaideng.add(alibabaCompany);xilaideng.add(tencentCompany);xilaideng.entertain(provincialLeaderVistor);xilaideng.entertain(nationalLeaderVistor);}}打印结果:
Alibaba 接待省领导:十菜一汤
Tencent 接待省领导:八菜一汤
Alibaba 接待郭嘉领导:十四菜两汤
Tencent 接待郭嘉领导:十六菜两汤

完整的访问者模式代码已经呈现,花 1 分钟思考一番,理解整个代码后我们来看看下面的总结。

总结

访问者模式有比较好的扩展性,看看访问者代码,我们如果要新增一个访问者:市领导人,只需新增市领导人类,便可实现。当然也有它不好的地方,就是把被访问者暴露给访问者,使得访问者可以直接了解被访问者的所有东西。明白了优缺点,才能更好的在实际中运用,一般访问者模式运用于要求遍历多个不同的对象的场景。

更多推荐

十一大行为模式(下)

本文发布于:2024-02-07 14:05:42,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1757263.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:一大   模式

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!