目录

前言

你收到了一份需求

面向对象分析 (OOA)

初版程式实作

察觉 Forces

套用责任链模式 (OOD)

封装变动之处 (Encapsulate what varies)

萃取共同行为 (Abstract common behaviors)

委派/複合 (Delegation / Composition)

重构出第二版程式码

责任链模式总结

责任链模式中重複的 If-Else 程式码

责任链模式 x 样板方法程式实作

总複习

软体设计模式精通之旅

试吃课程送 Astah UML Editor


前言

大家好,我是水球潘。

今天我们要来介绍一个 GoF 软体设计模式中的一位狠角色,责任链模式 (Chain Of Responsibility Pattern),简称 CoR。责任链模式是我最喜欢的模式之一,因为他能够允许开发者持续地往一个类别中添加新的行为,使该类别得以组合各式各样的需求。

我喜欢借由「完整的实战演练」来谈论软体设计模式,而非「过度简化的案例」。如此一来才能让大家感受到设计理论与实务上之间的紧密配合。因此,我会遵照以下步骤来介绍每一个模式:

  1. 先上一份「需求」
  2. 对着需求做「面向对象分析 (OOA)」,绘制出初版类别图
  3. 照着初版类别图实作出初版程序码
  4. 察觉程序码中的 Forces
  5. 提出我们要解决的 Problem,寻找对应的软体设计模式
  6. 套用软体设计模式来解决 Problem (Forces),绘製第二版类别图
  7. 照着第二版类别图重构出第二版程式码
  8. 软体设计模式六大元素总结

你收到了一份需求

今天我们的实战演练就是在知名社群平台 Discord 上开发一款多功能的 Discord 机器人!

你要为水球软体学院开发一个 Discord 机器人 (WaterballBot)。这个机器人支援许多功能,社群成员可以在 Discord 频道中发布讯息 (Message) 来请求某一道功能。而当机器人收到讯息指令时,就会做相对应功能的处理 (Handle)。

举例来说,如果你想要看看货币汇率,你可以在频道中发布讯息 "currency",然后学院机器人就会立刻回复你汇率资讯。

目前支援以下三者讯息指令:查看机器人有哪些讯息指令可用。

  1. "help":查看机器人有哪些讯息指令可用。
  2. "currency":查看目前货币汇率。
  3. "dcard":查看最新 Dcard(知名论坛) 的文章。

讯息指令不区分大小写,不管你是打成大写开头的 Help 或是 全大写的 HELP,机器人全都能辨认。每一个讯息指令只会对应一个功能。如果讯息无法匹配到任何功能的话,机器人就会无视这则讯息。

再来,稍微说一下 Discord 的推播机制,由于这是第三方的技术,并不是本文的重点,各位稍微有个印象就好。我们能够简单地使用 Discord API,来让机器人连线 (Connect) 且登入至 Discord 之 中。登入后就能开始侦听 (Listen) 各种事件 (Event),而我们主要想侦听的事件是  MessageCreateEvent 也就是新讯息事件。每当有人在某频道中传讯息时,Discord 会通知机器人说哪一个文字频道 (Message Channel) 有了一则新讯息 (Message)。

满实用又满有挑战性的,对吧。
走吧!先进行面向对象分析。

面向对象分析 (OOA)

好,这就是我们分析出来的初版类别图了,灰色区块的部分为第三方的 Discord API 中的类别。我们先专注在学院领域模型中的 WaterballBot 类别:当呼叫 WaterballBot 的 connect operation 时,WaterballBot 会透过 Discord API 连线和登入,之后 WaterballBot 会向 Discord API 侦听MessageCreateEvent。每一次有新讯息 (Message) 时,Discord 会推播新讯息给 WaterballBot 并告知此讯息是来源自哪一个文字频道 (MessageChannel),此时 WaterballBot 就会处理 (handle) 这则讯息,讯息处理方法 (handle 方法) 的行为如同粉红色便条纸所示;主要有三种不同的讯息指令,各对应到不同的处理。

接着,我们来实作初版的程式码~

初版程式实作

WaterballBot 类别的实作分成两部分,第一部分是第三方套件的使用细节:connect 方法中撰写着我们如何透过 Discord API 让机器人连线、登入和侦听新讯息的事件:

public class WaterballBot {// 透過 Discord API 讓機器人連線、登入和偵聽新訊息的事件public void connect() throws IOException {String token = getDiscordToken();DiscordClient client = DiscordClient.create(token);GatewayDiscordClient gateway = client.login().block();gateway.on(MessageEvent.class).subscribe(e -> {if (e instanceof MessageCreateEvent) {MessageCreateEvent mce = (MessageCreateEvent) e;Message message = mce.getMessage();MessageChannel channel = message.getChannel().block();handle(message, channel);}});gateway.onDisconnect().block();}private static String getDiscordToken() throws IOException {try (InputStream in = currentThread().getContextClassLoader().getResourceAsStream("token.properties")) {Properties properties = new Properties();properties.load(in);return String.valueOf(properties.get("TOKEN"));}}...

这部分大略阅读过去就行,直接来看第二部分今天的重点,机器人该如何做 message handling:

    public void handle(Message message, MessageChannel channel) {if ("help".equalsIgnoreCase(message.getContent())) {channel.createMessage("▋ HELP ▋").block();channel.createMessage("Commands: dcard, currency").block();} else if ("dcard".equalsIgnoreCase(message.getContent())) {String dcardBody = crawlDcardBody();channel.createMessage("▋ DCARD ▋").block();channel.createMessage(dcardBody).block();} else if ("currency".equalsIgnoreCase(message.getContent())) {String currencyBody = crawlCurrencyBody();channel.createMessage("▋ CURRENCY ▋").block();channel.createMessage(currencyBody).block();}}private String crawlDcardBody() { /* 實作 Dcard 爬蟲 */ }private String crawlCurrencyBody() { /* 實作匯率資訊爬蟲 */}

将 Message 和 MessageChannel 传入 handle 方法。handle 方法中使用条件式来匹配不同讯息对应的处理行为。举例来说,如果新讯息的内容是 “currency”,那便是请求汇率资讯的功能,此时讯息处理的行为就是:「先去汇率相关网站爬虫来搜集汇率资讯,然后再将这些资讯整理成简单易读的文字回传到 Discord 频道中」。而 Dcard 和 Help 讯息也有对应的处理行为。

至于 crawlDcardBody 和 crawlCurrencyBody 的实作内容就不带各位理解囉,裡面是非常单纯的爬虫使用,有兴趣的伙伴可以至文末的 Github 连结中阅读原始码。

接着我们回到 Main method 中把 WaterballBot 实体化出来,让它连接且登入至 Discord,如此一来就大功告成了。

public class Main {public static void main(String[] args) throws IOException {WaterballBot waterballBot = new WaterballBot();waterballBot.connect();}
}

察觉 Forces

接着,我们要来感受一下这个 handle method 内部的程式码,察觉一下我们面临了哪些 Forces?

我们第一个要察觉的 Force 就是「变动性 (Variation)」。

1. 我们能从这段程式码或是需求中感受到行为变动性 (Behavioral Variation):

需求方希望能增加五花八门的功能到机器人的指令列表中,像是支援 CNN 新闻爬文功能、查看今日天气、翻译英文单字⋯⋯等等。而社群成员 (可视为是机器人的 Client) 请求每一道功能所下的讯息指令也不同,导致程式实作中被迫新增更多 If-Else cases。而正因为 WaterballBot 的讯息处理行为会不断增加,此为行为变动性的 Force——行为会变,而且变动的方向是让 WaterballBot 的行为量由少变多。

好,需求方满意了;接着,各个功能端的开发者就有意见了:「不管是处理 Dcard 讯息还是处理 Currency 讯息,大家都挤在 WaterballBot 类别中开发,导致程式码之间常常需要相互顾忌着冲突,既不好维护也不好扩充,有完没完啊?能不能将这些程式独立出来成各个讯息处理者类别啊?」

2. 此时我们察觉到了第二道 Force:工程师对于程式码维护性 (Maintainability) 上的要求:能否让工程师更专注地维护各个既有功能程式码而不受其他功能干扰?

再来,应用端的工程师也跟着有意见了:「如果把不同功能独立出去的话,那到底该如何将这些功能整合进 WaterballBot 中?要是未来负责 CNN 爬文功能的工程师开发好 CNN 功能的话,他要怎麽有弹性地在 WaterballBot 中 扩充此功能啊?如果哪天我们不需要 Dcard 功能时,要怎麽有弹性地从 WaterballBot 中移除此功能?」

3. 此时我们察觉到了第三道 Force:程式弹性 (Flexibility) 上的要求 ,让工程师能够有弹性地在 WaterballBot 中决定要支援哪些功能好吗?

这三道 Forces 彼此冲突,重重地束缚了相关的程式码,使工程师们感到压抑。

因此,我们必须解决这一道难题,我们将 Problem 定义为:如何解耦 WaterballBot 和所有的讯息处理者(i.e., 讯息处理行为),以致于能够有弹性地在系统中决定要支援哪些功能?

套用责任链模式 (OOD)

我们来看看责任链模式的模式语言,与策略模式十分相似:先使用「依赖反转之重构三步骤 (Dependency Inversion 3 Steps),重构出责任链模式的 Form,然后再套用依赖注入模式来处理依赖。

封装变动之处 (Encapsulate what varies)

这一步,我们要将行为变种封装至类别方法中。所以在这段程式码裡头有哪些行为变种 (Behavioral Variant) 呢?这边有三个条件式的 cases,每个 case 都撰写不同的「需求 → 处理需求」,从这裡我们可以看出有三个行为变种。

开完这三个 Handler 类别并将各自变种的行为封装进 handle 方法中之后,就算是做完第一步了,结果如下图所示:

萃取共同行为 (Abstract common behaviors)

下一步,我们要观察这三个行为变种,并把他们共同的行为,萃取至一个介面之中。

首先,我们能发现这些 Handler 之间有一个共同的行为:「只处理隶属于自己责任范围内的讯息」。因此,开了 MessageHandler 介面并宣告一个 handle operation 在介面中表示着「处理隶属于自己责任范围内讯息」的能力

但是,我们依然还没有萃取「如果讯息不隶属于自己的责任范围,那就要把讯息丢给下一个 case」 —— 条件式 else 部分的行为。我们来想一下,把讯息丢给下一个 case,是什麽意思呢?

其实,就是请下一位 Handler 来处理的意思。因此,每一位 Handler 都必须认识在其之后的下一位 Handler,当讯息不隶属于自己的责任范围时,才有办法请下一位 Handler 接续处理。

所以我们修改类别图,将「下一位 Handler (The Next handler) 」萃取至 MessageHandler 介面之中,但是由于介面只能包含纯然抽象的行为,我们必须将 MessageHandler 改成抽象类别。

并且多开设一道关联:「每一位 MessageHandler 都认识他的下一位 MessageHandler」。大家可以把此关联想像成会在类别中多一个类型为 MessageHandler 的 next 属性。next 的参与者数量为 0..1,意即此 next 属性可能存在,也可能为 null。

接着我们便能完整地定义这三个 Handler 子类别的共同行为,如果讯息隶属于自身的责任范围就进行处理,否则先检查下一位 Handler 是否存在,存在的话就请下一位 Handler 接续处理。

委派/複合 (Delegation / Composition)

而最后一步,就是在 WaterballBot 类别之中,将讯息处理的职责委派给此 MessageHandler 抽象类别。

如此一来,我们就做完依赖反转之重构三步骤了。而重构完后也就套完了责任链模式 (Chain Of Responsibility) 囉。接着,我们来重构出第二版的程式码吧!

重构出第二版程式码

1. 首先先创建 MessageHandler 抽象类别,

public abstract class MessageHandler {protected MessageHandler next;public MessageHandler(MessageHandler next) {this.next = next;}public abstract void handle(Message message, MessageChannel channel);
}

从建构子依赖注入下一位 MessageHandler,next 可能为 null 也可能不为 null。 另外,我们必须将此 next 属性的存取权设为 protected,所以子类别才能够存取它。

之后宣告抽象方法 handle,这一步就算完成了。

2. 再来我们依序实作 Help、Currency、Dcard 讯息的处理者类别,先以 HelpHandler 为例:

开好 HelpHandler 类别后,使其继承 MessageHandler、复写建构子和 handle 方法。然后将原本 case 中的行为搬进来 handle 方法中,并且将不隶属于自己责任范围内的讯息交给 the next handler 接续处理:

public class HelpMessageHandler extends MessageHandler {public HelpMessageHandler(MessageHandler next) {super(next);}@Overridepublic void handle(Message message, MessageChannel channel) {if ("help".equalsIgnoreCase(message.getContent())) {channel.createMessage("▋ HELP ▋").block();channel.createMessage("Commands: dcard, currency").block();} else if (next != null) {next.handle(message, channel);}}
}

再来 Currency 讯息的处理者也是如法炮製:

public class CurrencyHandler extends MessageHandler {public CurrencyHandler(MessageHandler next) {super(next);}@Overridepublic void handle(Message message, MessageChannel channel) {if ("currency".equalsIgnoreCase(message.getContent())) {String currencyBody = crawlCurrencyBody();channel.createMessage("▋ CURRENCY ▋").block();channel.createMessage(currencyBody).block();} else if (next != null) {next.handle(message, channel);}}private String crawlCurrencyBody() {...}
}

最后则是 Dcard 的讯息处理者,如此一来我们就将所有讯息处理的部份封装进各个处理者类别中了。

public class DcardHandler extends MessageHandler {public DcardHandler(MessageHandler next) {super(next);}@Overridepublic void handle(Message message, MessageChannel channel) {if ("dcard".equalsIgnoreCase(message.getContent())) {String dcardBody = crawlDcardBody();channel.createMessage("▋ DCARD ▋").block();channel.createMessage(dcardBody).block();} else if (next != null) {next.handle(message, channel);}}private String crawlDcardBody() {...}
}

现在回到 WaterballBot 类别,将 MessageHandler 从建构子中依赖注入:

public class WaterballBot {private MessageHandler handler;public WaterballBot(MessageHandler handler) {this.handler = handler;}
...

并将讯息处理的职责全部委派给 MessageHandler:

public void handle(Message message, MessageChannel channel) {handler.handle(message, channel); // 委派
}

如此一来就能解耦 WaterballBot 与所有讯息的处理细节,WaterballBot 的类别中再也看不见处理的相关行为,可以说是成功地解耦了 WaterballBot 以及所有的处理者实作。

最后,我们回到 Main method 中,把整条 MessageHandler 责任链实体化出来,这就是套用完责任链的程式码:

public class Main {public static void main(String[] args) throws IOException {WaterballBot waterballBot = new WaterballBot(new HelpMessageHandler(new DcardHandler(new CurrencyHandler(null))));waterballBot.connect();}
}

非常帅气吧!就像一个链条一样,把所有功能串在一起,一个串一个,直到在最后一个 Handler 传入 null 为止。

责任链模式总结

Context:你的程式需要支援很多不同种类的需求,并且每个需求都对应到一个处理行为。

注意:每个需求都只有对应到「一个」处理行为。

责任链模式解决的 Problem 为:如何解耦需求者及所有的需求处理者,以致于能够有弹性地在系统中决定要支援哪些需求?

具体来说,责任链模式化解了以下这三道 Forces:

  1. 需求会持续新增,而且处理各需求的行为不同 —— 行为变动性 (Behavioral Variation)。行为变动的方向是由少到多。
  2. 你希望每个需求的处理都是一段独立的程式码,不同处理者之间彼此互不影响,因此能有更好的程式扩充性和维护性 (Extensibility & Maintainability)。
  3. 系统未来可能会支援新的需求,也有可能会停止支援某些需求,你希望系统能够很有弹性地决定要支援哪些需求 —— 系统弹性 (Flexibility) 上的要求。

责任链提出的解决方案 (Form or Solution):

首先会萃取出一个 Handler 抽象类别,每一个 Handler 都认识(关联)the next Handler,就像一条链子。然后,Handler 的具体实作 ConcreteHandler 在 handle 时首先会先判断这个 Request 是否隶属于自己的责任范围内,如果是的话就 handle 它,要不然就会将此 Request 交给 the next Handler 接续处理(如果 the next handler 存在)。

将所有 ConcreteHandler 串起来串成一条链子,并且每个 ConcreteHandler 都只处理隶属于自身责任范围内的 Request,这也是为什麽这个模式被称之为责任链 (Chain Of Responsibility, CoR) 。

责任链上的每个 Handler,就像公司中那些本位主义的员工,不在其位,不谋其政。在责任链模式的设计中,给定一个 Request,只会被一个 ConcreteHandler 所处理。也有可能没有 ConcreteHandler 愿意处理它,就代表系统不支援这个 Request,你可以依照自己的 Context 来做小幅度的变化。

各位可以很容易地将责任链模式的 Form 对应到我们的案例中。

最后,套用完每一个模式之后,都会得到一个结果;或者说,套用完模式后我们会得到一个 Resulting Context(以 Form 类别图来看)

1.  Context 类别 (i.e., WaterballBot) 作为需求者,已经再也无法看到每个需求究竟会被哪一位 ConcreteHandler 所处理,也完全看不见处理行为的细节—— 完全解耦了 Context 和 ConcreteHandler 。而正是只有在完全解耦的前提之下,我们才能在不影响 Context 类别内部程式码的情况,轻鬆地支援新的需求。

2.  另外,现在我们能够专注地维护各个 ConcreteHandler 类别:你能够在 ConcreteHandler 类别中实作着非常複杂的处理行为,而完全不会影响到其他 ConcreteHandler。很多时候在一个专案中,不同功能是由不同的工程师、甚至是由不同团队负责开发,将大家的程式码独立出来,对团队的整体生产力有正向帮助。

试想一下,假如公司有一百位工程师,而现在要开发一百个独立功能,如果我们在套用责任链模式的 Resulting Context 中将这一百个独立功能平均分配给这一百位工程师来开发,则工作的平行度就会趋近于 100,意即能够平行开发这一百个独立功能,顶多只有在整合责任链时才比较有可能发生程式码冲突。

3. 除此之外,责任链模式最厉害的地方是,我们能够透过依赖注入 (Dependency Injection, DI),来在不修改 Context 类别内部程式码的前提之下,不断地扩充新的 Handler 来支援新的需求。

这种能做到「在不修改某些类别的前提下、扩充新功能」的特色又被称之为遵守着开闭原则 (Open-Closed Principle, OCP)

责任链模式中重複的 If-Else 程式码

我们回到责任链模式的类别图,看一下两个 ConcreteHandler 的 handle 行为,有没有发现这些行为其实都有着重複的程式码呢?可以说,唯一有变动的部分就只有需求的处理行为。

if can handle {do handling 1
}  else if (next != null) {next.handle(request)
}
if can handle {do handling 2
} else if (next != null) {next.handle(request)
}

在这裡,大家是否有想到我们可以套用哪个设计模式,来减少重複的程式码?

是的,我们能够套用样板方法 (Template Method),来减少责任链中 Handler 之间重複的程式码。在大多数 (99%) 的情境下,责任链模式和样板方法会一起套用,套用完的类别图如下图所示:

Handler 抽象类别中的 handle 被转成具体方法,我们将重複的程式提取至 handle 中成为样板方法,并把会变动的部分萃取成抽象的步骤。像是将需求责任范围的判断 萃取成抽象的 match 方法,将实际的需求处理行为萃取成抽象的 doHandling 方法。

带着这样子的设计,我们来重构我们的程式码吧。

责任链模式 x 样板方法程式实作

public abstract class MessageHandler {protected MessageHandler next;public MessageHandler(MessageHandler next) {this.next = next;}public void handle(Message message, MessageChannel channel) {if (match(message)) {doHandle(message, channel);} else if (next != null) {next.handle(message, channel);}}protected abstract boolean match(Message message);protected abstract void doHandle(Message message, MessageChannel channel);
}

依照类别图,将样板方法提取至 handle 方法中,如果 match 就 doHandling,要不然就请 next handler 作处理。而每一个 ConcreteHandler 都只要複写好 match 和 doHandling 就行了,

如此一来,每一个 ConcreteHandler 的程式码就不会重複,也乾淨许多。

public class HelpMessageHandler extends MessageHandler {public HelpMessageHandler(MessageHandler next) {super(next);}@Overrideprotected boolean match(Message message) {return "help".equalsIgnoreCase(message.getContent());}@Overrideprotected void doHandling(Message message, MessageChannel channel) {channel.createMessage("▋ HELP ▋").block();channel.createMessage("Commands: dcard, currency").block();}
}
public class CurrencyHandler extends MessageHandler {public CurrencyHandler(MessageHandler next) {super(next);}@Overrideprotected boolean match(Message message) {return "currency".equalsIgnoreCase(message.getContent());}@Overrideprotected void doHandling(Message message, MessageChannel channel) {String currencyBody = crawlCurrencyBody();channel.createMessage("▋ CURRENCY ▋").block();channel.createMessage(currencyBody).block();}private String crawlCurrencyBody() {    ... }
}
public class DcardHandler extends MessageHandler {public DcardHandler(MessageHandler next) {super(next);}@Overrideprotected boolean match(Message message) {return "dcard".equalsIgnoreCase(message.getContent());}@Overrideprotected void doHandling(Message message, MessageChannel channel) {String dcardBody = crawlDcardBody();channel.createMessage("▋ DCARD ▋").block();channel.createMessage(dcardBody).block();}private String crawlDcardBody() { ... }
}

总複习

你已经学完责任链模式了,现在是总複习时间。

什麽时候会套责任链模式?当你的程式需要处理各种不同种类的需求,并且你希望能够解耦需求者及所有的需求处理者,以致于你能够有弹性地在系统中决定要支援哪些需求。

怎麽套呢?藉由依赖反转之重构三步骤来将「处理隶属于自己责任范围内的需求」这项能力萃取出来,并封装至不同的处理者类别中,然后透过依赖注入,来让每一位处理者都认识下一位处理者,如同一条责任链。

责任链模式和样板方法、策略模式一样都是行为型模式 (Behavioral Pattern),并且大多数时候我们会用样板方法来去除责任链中重複的 if-else 程式码。

软体设计模式精通之旅

喜欢我的文章和我的教学方式吗?为了能够持续优化我的教材,水球软体学院持续招募「想要试吃免费设计模式课程,并给予我们回馈的朋友们」唷!课程中带有大量含金量高的内容的挑战题,欢迎来尝试和体验!

赶紧文末 Discord 连结加入学院的社群报名下一梯次吧!

试吃课程送 Astah UML Editor

另外,现在加入还会直接送 Astah UML Editor —— Astah 是笔者我已经使用了六年的 UML Editor,可以说是自我学会软体设计模式之后,一路陪着我过关斩将的武器呀——十分专业而且体验还很流畅。

软体设计是非常耗费脑力的事,如果没有一套好的工具帮你从思想禁锢中解放,那麽最后做出来的设计一定会充斥着盲点。这就是为什麽学院愿意为每一位学员准备好这一套专业的 UML Editor,如此一来学员们才能在优秀工具的加成下,熟悉善用工具来活化设计思维的过程,来在课程的任务挑战中持续过关斩将,最后满载而归。

水球软体学院 Discord 连结:https://pse.is/49bxqg

水球软体学院 软体设计模式精通之旅 Github 连结

从 Forces 开始分析责任链模式:「写一个 Discord 对话机器人」相关推荐

  1. 一起学设计模式 - 责任链模式

    责任链模式(ChainOfResponsibilityPattern)属于 行为型模式的一种,将请求沿着一条链传递,直到该链上的某个对象处理它为止. 概述 定义如下:一个请求有多个对象来处理,这些对象 ...

  2. 最近学习了责任链模式

    2019独角兽企业重金招聘Python工程师标准>>> 前言 来菜鸟这个大家庭10个月了,总得来说比较融入了环境,同时在忙碌的工作中也深感技术积累不够,在优秀的人身边工作必须更加花时 ...

  3. 行为型模式之责任链模式

    责任链模式(Chain of Responsibility Pattern)为请求创建了一个接收者对象的链.这种模式给与请求的类型,对请求的发送者和接收者进行解耦.这种类型的设计模式属于行为型模式 在 ...

  4. java里面的环链怎么做_Java模式开发之责任链模式

    从前往后按照一定操作顺序进行  申明一个过滤器接口,里面有一个"方法申明" 叫 规则过滤,返回处理后的结果 申明一些具体的操作类,全部实现过滤器接口,重写里头规则过滤的方法,返回规 ...

  5. [转]《JAVA与模式》之责任链模式

    http://www.cnblogs.com/java-my-life/archive/2012/05/28/2516865.html 在阎宏博士的<JAVA与模式>一书中开头是这样描述责 ...

  6. 23种设计模式(11):责任链模式

    定义:使多个对象都有机会处理请求,从而避免了请求的发送者和接收者之间的耦合关系.将这些对象连成一条链,并沿着这条链传递该请求,直到有对象处理它为止. 类型:行为类模式. 类图: 首先来看一段代码: p ...

  7. 前端设计模式责任链模式

    责任链的重点在 " 链 " 上,有一条链去处理相似的请求,在链中决定由于谁去处理这个请求,并返回相应的结果 使多个对象都有机会处理请求,直到有对应的分支进行处理. 使用场景:在一个 ...

  8. 【设计模式】责任链模式 ( 简介 | 适用场景 | 优缺点 | 代码示例 )

    文章目录 一.责任链模式简介 二.责任链模式相关设计模式 三.责任链模式 代码示例 1.用户账户类 2.校验器父类 3.用户名校验器 4.密码校验器 5.电话号码校验器 6.运行测试 一.责任链模式简 ...

  9. 责任链模式——HeadFirst设计模式学习笔记

    责任链模式:使一个以上的对象都有机会能够处理某个请求 特点: 链中的每个对象包含它下一个对象的引用和对事件的处理方法.请求在这个链上传递,直到链上的某一个对象决定处理此请求 发出这个请求的客户端并不知 ...

最新文章

  1. mysql安装图解 mysql图文安装教程(详细说明)
  2. arraylist切割_JAVA List和Map切割工具详解
  3. Android 机顶盒手势、数据分页演示DEMO
  4. 图片二进制编码_python3从零学习-5.7.4、quopri编码与解码经过MIME转码打印数据
  5. mysql索引要点_mysql表索引的一些要点_MySQL
  6. 关于面对对象和正则表达式的处理
  7. 解决Feign接口调用有时候不好用的分析思路
  8. iOS开发应用结构化资源储备
  9. c3p0三种配置方式(automaticTestTable)
  10. PAT 乙级 1037. 在霍格沃茨找零钱(20)Java版
  11. 第二次作业 时事点评
  12. 小米手机google play下载应用一直显示等待中的解决办法
  13. 计算机内存加速,电脑内存使用率过高怎么加速
  14. easyui 设置css样式,Easyui 条件设置行背景颜色_EasyUI 教程
  15. 软件项目管理读书体会
  16. addon游戏_SnowMobile Addon
  17. 固态硬盘计算机怎么自定义分区,如何将SSD固态硬盘设置为主硬盘,如何将SSD分区设置为主硬盘?...
  18. 真实的90后创业者是怎样的状态?
  19. 【山外笔记-计算机网络·第7版】第13章:计算机网络名词缩写汇总
  20. 小度机器人小胖机器人_小度机器人怎么升级?智能机器人百小度快速升级全攻略[多图]...

热门文章

  1. 韩国首发元宇宙 5 年计划,市民可戴 VR 头显见政府官员
  2. 使用C语言计算1+2+3+...+100
  3. android webdav客户端,WebDAV精灵
  4. 硬盘格式化数据恢复(图文教程)
  5. OriginPro绘图过程中遇到的问题及解决办法
  6. 100道iOS面试题
  7. 做了一个仿吃鸡游戏,可多人联网,算是学习总结。
  8. 语言与区域设置ID (Language ID、Locales ID / LCID)
  9. 电商平台快递物流解决方案
  10. 网易云音乐 真实地址