工具篇-Java中的设计模式积累(二)

目录

行为型设计模式

  1. 模版模式
  2. 策略模式
  3. 命令模式
  4. 责任链模式
  5. 状态模式
  6. 观察者模式
  7. 中介者模式 
  8. 迭代者模式
  9. 访问者模式
  10. 备忘录模式
  11. 解释器模式

 ------------------------------------------行为型设计模式

1. 模版模式

为何有模版模式:html

定义一个算法的骨架,而将一些步骤的实现延迟到子类中。模板方法使得子类能够在不改变算法结构的状况下,从新定义算法中某些步骤的具体实现;java

优势: git

  • 模板方法模式经过把不变的行为搬到父类,去除了子类中的重复代码;
  • 子类实现算法的某些细节,有助于算法的扩展;
  • 父类调用子类实现的操做,经过子类扩展增长新的行为,符合“开放-封闭原则” 

缺点:算法

  • 在模版模式中,子类执行的结果影响了父类的结果,会增长代码阅读的难度;数据库

应用场景:设计模式

  • 多个子类存在共有方法,且逻辑相同;    
  • 重要、复杂的算法,能够把核心算法设计为模板方法

实现:浏览器

模版模式中,基类方法的默认实现被退化为钩子Hooks的概念,他们被设计在子类中被重写,若是你指望一些方法在子类中不被重写,可让他们为final。例如,安全

模版抽象类微信

 1 public abstract class HouseTemplate {
 2 
 3     protected HouseTemplate(String name){
 4         this.name = name;
 5     }
 6 
 7     protected String name;
 8 
 9     protected abstract void buildDoor();
10 
11     protected abstract void buildWindow();
12 
13     protected abstract void buildWall();
14 
15     protected abstract void buildBase();
16 
17     protected abstract void buildToilet();
18 
19     //钩子方法
20     protected boolean isBuildToilet(){
21         return true;
22     }
23 
24     //公共逻辑
25     public final void buildHouse(){
26 
27         buildBase();
28         buildWall();
29         buildDoor();
30         buildWindow();
31         if(isBuildToilet()){ 
32             buildToilet();
33         }
34     }
35 
36 }

子类1网络

 1 public class HouseOne extends HouseTemplate {
 2 
 3     HouseOne(String name){
 4         super(name);
 5     }
 6 
 7     HouseOne(String name, boolean isBuildToilet){
 8         this(name);
 9         this.isBuildToilet = isBuildToilet;
10     }
11 
12     public boolean isBuildToilet;
13 
14     @Override
15     protected void buildDoor() {
16         System.out.println(name +"的门要采用防盗门");
17     }
18 
19     @Override
20     protected void buildWindow() {
21         System.out.println(name + "的窗户要面向北方");
22     }
23 
24     @Override
25     protected void buildWall() {
26         System.out.println(name + "的墙使用大理石建造");
27     }
28 
29     @Override
30     protected void buildBase() {
31         System.out.println(name + "的地基使用钢铁地基");
32     }
33 
34     @Override
35     protected void buildToilet() {
36         System.out.println(name + "的厕所建在东南角");
37     }
38 
39     @Override
40     protected boolean isBuildToilet(){
41         return isBuildToilet;
42     }
43 
44 }

子类2

 1 public class HouseTwo extends HouseTemplate {
 2 
 3     HouseTwo(String name){
 4         super(name);
 5     }
 6 
 7     @Override
 8     protected void buildDoor() {
 9         System.out.println(name + "的门采用木门");
10     }
11 
12     @Override
13     protected void buildWindow() {
14         System.out.println(name + "的窗户要向南");
15     }
16 
17     @Override
18     protected void buildWall() {
19         System.out.println(name + "的墙使用玻璃制造");
20     }
21 
22     @Override
23     protected void buildBase() {
24         System.out.println(name + "的地基使用花岗岩");
25     }
26 
27     @Override
28     protected void buildToilet() {
29         System.out.println(name + "的厕所建在西北角");
30     }
31 
32 }

客户端

 1 public class Clienter {
 2 
 3     public static void main(String[] args){
 4         HouseTemplate houseOne = new HouseOne("房子1", false);
 5         HouseTemplate houseTwo = new HouseTwo("房子2");
 6         houseOne.buildHouse();
 7         houseTwo.buildHouse();
 8     }
 9 
10 }

能够看到,经过重写钩子方法自定义了房子1不须要建造厕所(fasle),另外能够参考这篇文章:设计模式学习笔记之九:模板方法模式

2. 策略模式

为何有策略模式:

Define a family of algorithms,encapsulate each one,and make them interchangeable.
定义一组算法(我以为应该是一个行为的不一样实现吧),将每一个算法都封装起来,而且使它们之间能够互换。

通俗点讲是利用继承和多态机制,从而实现同一行为在不一样场景下的不一样实现吧。

优势: 

  • 算法能够自由切换;
  • 避免使用多重条件判断;
  • 扩展性良好,策略类遵顼里氏替换原则,增长一个策略只须要实现一个接口就能够了。

缺点:

  • 策略模式形成不少的策略类,每一个具体策略类都会产生一个新类;
  • 全部的策略类都须要对外暴露,客户端必须知道全部的策略类,并自行决定使用哪个策略类,违反了迪米特法,可以使用工厂方法模式、代理模式修整这个缺陷。

应用场景:

  • 若是在一个系统里面有许多类,它们之间的区别仅在于它们的行为,在运行时动态选择具体要执行的行为;
  • 在不一样状况下使用不一样的策略(算法),或者策略还可能在将来用其它方式来实现;
  • 对客户隐藏具体策略(算法)的实现细节,彼此彻底独立。

实现:

策略模式有三种角色:

抽象策略角色这个是一个抽象的角色,一般使用接口或者抽象类去实现。好比Comparator接口;
具体策略角色包装了具体的算法和行为。就是实现了Comparator接口的实现一组实现类;
环境角色内部会持有一个抽象角色的引用,好比内部必定会有一个策略类的一个成员变量,这样在构建函数中就能够接收外部传递的具体的策略类。

例如,

    //抽象策略类 Strategy
    interface IStrategy {
        void algorithm();
    }

    //具体策略类 ConcreteStrategyA
    static class ConcreteStrategyA implements IStrategy {
        @Override
        public void algorithm() {
            System.out.println("Strategy A");
        }
    }

    //具体策略类 ConcreteStrategyB
    static class ConcreteStrategyB implements IStrategy {
        @Override
        public void algorithm() {
            System.out.println("Strategy B");
        }
    }

客户端,

 1     //上下文环境角色
 2     static class Context {
 3         private IStrategy mStrategy; // 引用
 4 
 5         public Context(IStrategy strategy) {
 6             this.mStrategy = strategy;
 7         }
 8 
 9         public void algorithm() {
10             this.mStrategy.algorithm();
11         }
12     }

3. 命令模式

为何有命令模式:

将一个请求封装成一个对象,从而使请求参数化。

优势: 

  • 下降"行为请求者"与"行为实现者"的耦合;
  • 新的命令能够很容易添加到系统中。

缺点:

  • 可能会致使某些系统有过多的具体命令类。

应用场景:

在一些对行为进行"记录、撤销/重作、事务"等处理的场合,好比:线程池、工做队列、日志请求等

  • 工做队列:

在某一端添加命令,另外一端则是线程进行下面的动做:从队列中取出一个命令,调用它的execute()方法,等待这个调用完成,而后将此命令对象丢弃,再取出下一个命令......这里工做队列和命令对象之间是彻底解耦的,线程可能此刻在进行财务运算,下一刻却在读取网络数据。工做队列对象不在意到底作些什么,它们只知道取出命令对象,而后调用其execute()方法。相似地,它们只要实现命令模式的对象,就能够放入队列里,当线程可用时,就调用此对象的execute()方法。

  • 日志请求

多用于数据库管理系统的实现,咱们须要把一系列的操做记录下来(如写在硬盘上),在遇到系统故障时读出来以恢复数据,如何实现?利用对象的序列化把对象保存起来(记录日志),在须要的时候反序列化(恢复事务)。

注:这里的可撤销的操做就是:放弃该操做,回到未执行该操做前的状态。

有两种基本的思路来实现可撤销的操做:
① 一种是补偿式,又称反操做式
好比被撤销的操做是加的功能, 那撤消的实现就变成减的功能;好比被撤销的操做是打开的功能,那么撤销的实现就变成关闭的功能。
② 另一种方式是存储恢复式
意思就是把操做前的状态记录下来,而后要撤销操做的时候就直接恢复回去就能够了。

实现:

该模式具备如下角色:

抽象命令接口Command:定义接口,声明执行的方法。
具体的命令对象ConcreteCommand:持有接受者对象,完成具体的命令。
接受者对象Receiver:接受者对象,真正执行命令的对象。
传递命令对象Invoker:调用者,要求命令对象执行请求。
客户端对象Client:建立具体命令对象而且设置接受者。

好比:顾客来到餐馆点一碗面(发出请求) -> 柜台服务员记录下来(建立命令) -> 服务员把订单扔给厨房 -> 厨师很快作好了一碗面(请求被执行)。顾客不知道将由谁来作这碗面,柜台服务员也不知道,厨师不知道是谁点了这碗面,其中,订单就起解耦的做用。

首先,写命令接口

1 public interface Command {
2     public abstract void execute();//只须要定义一个统一的执行方法
3 }

而后是执行者

1 public abstract class Chef {
2     //在此定义厨师的公共属性
3     
4     //定义烹饪方法
5     public abstract void cook();
6 }

具体执行者,作面的厨师和作饼的厨师

1 public class NoodlesChef extends Chef{
2 
3     @Override
4     public void cook() {
5         System.out.println("作好了一碗美味的拉面");
6     }
7 }
1 public class PieChef extends Chef{
2 
3     @Override
4     public void cook() {
5         System.out.println("作好了一块香喷喷的大饼");
6     }
7 }

各类具体命令

 1 public class NoodlesCommand implements Command{
 2     private NoodlesChef chef;//专业作面的厨师
 3     
 4     public NoodlesCommand(){
 5         chef = new NoodlesChef();
 6     }
 7 
 8     @Override
 9     public void execute() {
10         chef.cook();
11         //调用其它须要的方法
12     }
13 }
 1 public class PieCommand implements Command{
 2     private PieChef chef;//专业作饼的厨师
 3     
 4     public PieCommand(){
 5         chef = new PieChef();
 6     }
 7 
 8     @Override
 9     public void execute() {
10         chef.cook();
11         //调用其它须要的方法
12     }
13 }

调用者invoker

 1 public class Waiter {
 2 
 3     private Command command;
 4 
 5     // 设置具体的命令
 6     public void setCommand(Command command) {
 7         this.command = command;
 8     }
 9 
10     // 执行命令
11     public void doCommand() {
12         command.execute();
13     }
14 }

客户端,店馆开张了

public class Test {    
    public static void main(String[] args) {
        System.out.println("第一位客户X先生");
        System.out.println("X先生:你好,我须要一碗面,我饿极了");
        NoodlesCommand nCmd = new NoodlesCommand();
        System.out.println("柜台服务员:好的厨房~~,接单");
        Waiter.doCommand(); //安排厨师作
                
        System.out.println("第二位客户XX先生");
        System.out.println("XX先生:你好,我须要一块饼,20分钟后来取");
        PieCommand pCmd = new PieCommand();
        System.out.println("柜台服务员:好的厨房~~,接单");
        Waiter.doCommand(); // 安排厨师作
    }
}

4. 责任链模式

为何有责任链模式:

搞一条对象链,这样请求不用知道具体执行的是哪个对象,就实现了请求与对象之间的解耦。

优势: 

  • 下降了对象之间的耦合度,上边已经说了;
  • 加强了系统的可扩展性。能够根据须要增长删除请求处理类,知足开闭原则
  • 加强了给对象指派职责的灵活性。能够动态地改变链内的成员的次序;
  • 责任分担。每一个类只须要处理本身该处理的工做,符合类的单一职责原则

缺点(针对不纯责任链模式):

  • 不能保证每一个请求必定被处理。,请求可能一直传到链的末端都得不处处理;
  • 对比较长的职责链,系统性能将受到必定影响;
  • 职责链创建的合理性要靠客户端来保证,可能会形成循环调用。

应用场景:

我以为好多if/else,switch/case操做能够考虑这个吧,仍是能充分利用这种模式的优势呗,好比:

Netty 中的 Pipeline 和 ChannelHandler 经过责任链设计模式来组织代码逻辑
Spring Security 使用责任链模式,能够动态地添加或删除责任(处理 request 请求)
Spring AOP 经过责任链模式来管理 Advisor

实现(具体参考:责任链模式妙用):

该模式具备如下角色:

抽象处理者(Handler):定义一个处理请求的接口(或抽象类),包含抽象处理方法和一个后继链接

具体处理者(Concrete Handler):实现抽象处理者的处理方法,判断可否处理本次请求,若是能够则处理,不然将该请求转给它的后继者。

客户类(Client):建立处理链,并向链头的具体处理者对象提交请求,它不关心处理细节和请求的传递过程。

抽象接口

 1 public abstract class BaseCase { 
 2     // 为 true 代表本身能够处理该 case 
 3     private boolean isConsume; 
 4     public BaseCase(boolean isConsume) {   
 5         this.isConsume = isConsume; 
 6     }
 7      // 下一个责任节点
 8      private BaseCase nextCase; 
 9     public void setNextCase(BaseCase nextCase) {  
10         this.nextCase = nextCase; 
11     }     
12     public void handleRequest() {  
13         if (isConsume) {    
14             // 若是当前节点能够处理,直接处理     
15             doSomething();  
16         } else {     
17             // 若是当前节点不能处理,而且有下个节点,交由下个节点处理     
18             if (null != nextCase) {     
19                 nextCase.handleRequest();    
20             } 
21         } 
22     } 
23     abstract protected void doSomething();
24 }
View Code

具体处理者,其中一个为例

 1 public class OneCase extends BaseCase { 
 2     public OneCase(boolean isConsume) {   
 3         super(isConsume); 
 4     }
 5  
 6     @Override protected void doSomething() {   
 7     // TODO do something   
 8         System.out.println(getClass().getName()); 
 9     }
10 }
View Code

客户端

1 String input = "1";      
2 OneCase oneCase = new OneCase("1".equals(input));   
3 TwoCase twoCase = new TwoCase("2".equals(input)); 
4 DefaultCase defaultCase = new DefaultCase(true); 
5 oneCase.setNextCase(twoCase); 
6 twoCase.setNextCase(defaultCase);      
7 oneCase.handleRequest();
View Code

5. 状态模式

为何有状态模式:

让一个对象在其内部状态改变的时候改变其行为

优势: 

  • 将不一样状态的行为分割开来,知足“单一职责原则”;
  • 将不一样的状态引入独立的对象中会使状态转换更加明确,减小对象间的相互依赖;
  • 有利于扩展,经过定义新的子类很容易地增长新的状态和转换

缺点:

  • 每个状态都对应一个子类,当你的状态很是多的时候就会发生类膨胀。

应用场景:

行为随着状态的改变而改变的时候,例如权限设计,人员的状态不一样即便执行相同的行为结果也会不一样,在这种状况下须要考虑使用状态模式。

实现:

该模式具备如下角色

环境(Context)角色:也称为上下文,它将与状态相关的操做委托给当前状态对象来处理;
抽象状态(State)角色:接口,用来封装对象中的特定状态所对应的行为;
具体状态(Concrete State)角色:实现抽象状态所对应的行为。

好比:用户登陆系统,同一个操做在不一样状态下会有不一样的操做,先定义一个用户状态的接口

1 public interface LoginState {  
2     public void loadUser();
3     public void loginIn();
4     public void loginOut();
5 }

登陆成功状态

 1 public class LoginInState implements LoginState{
 2 
 3     @Override
 4     public void loadUser() {
 5         // TODO Auto-generated method stub
 6         System.out.println("用户数据载入成功");
 7 
 8     }
 9 
10     @Override
11     public void loginIn() {
12         // TODO Auto-generated method stub
13         System.out.println("已经登陆成功了");
14     }
15 
16     @Override
17     public void loginOut() {
18         // TODO Auto-generated method stub
19         System.out.println("已经登陆成功了,请按退出登陆按钮");
20     }
21 
22 }
View Code

退出登陆状态

 1 public class LoginErrorState implements LoginState{
 2     @Override
 3     public void loadUser() {
 4         // TODO Auto-generated method stub
 5         System.out.println("已经退出,请按登陆按钮");
 6     }
 7 
 8     @Override
 9     public void loginIn() {
10         // TODO Auto-generated method stub
11         System.out.println("已经退出,请先登陆");
12     }
13 
14     @Override
15     public void loginOut() {
16         // TODO Auto-generated method stub
17         System.out.println("退出登陆成功");
18     }
19 }
View Code

用LoginContext管理这两个状态

 1 /**
 2  * 登陆环境上下文
 3  * @author ccj
 4  *
 5  */
 6 public class LoginContext {
 7     private LoginState loginState=new LoginErrorState();
 8 
 9     /**
10      * 能够根据需求,进行单例化,工厂化
11      */
12     public void loginIn(){
13         loginState=new LoginInState();
14         loginState.loginIn();
15     }
16 
17     public void loginOut(){
18         loginState=new LoginErrorState();
19         loginState.loginOut();
20     }
21     
22     public void loadUser(){
23         loginState.loadUser();
24     }
25 }
View Code

客户端按键

 1 public class Test {
 2     /**
 3      * 将登陆登出两个动做, 分别对应两个按钮
 4      * @param args
 5      */
 6     public static void main(String[] args) {
 7         LoginContext context =new LoginContext();
 8         System.out.println("=======点击登陆按钮===>>=状态:登陆=====>>选择加载用户数据===========");
 9         context.loginIn();
10         context.loadUser();
11     
12         System.out.println("=====点击退出按钮>>======状态:退出=======>>选择加载用户数据=========");
13         context.loginIn();
14         context.loginOut();
15         context.loadUser();
16     }
17 }
View Code

测试结果

1 =======点击登陆按钮===>>=状态:登陆=====>>选择加载用户数据===========
2 已经登陆成功了
3 用户数据载入成功
4 =====点击退出按钮>>======状态:退出=======>>选择加载用户数据=========
5 已经登陆成功了
6 退出登陆成功
7 已经退出,请按登陆按钮

⚠️注意:在行为受状态约束的时候使用状态模式,并且状态不超过5个。

策略模式和状态模式对比:

策略模式须要客户端必须彻底知晓全部的策略方法,才可以知道究竟哪个策略是当前须要的。而状态模式客户端在使用的时候,能够没必要关心有哪些状态,他只须要去调用环境的行为就能够了,在环境的内部维护了这些状态,就是策略模式服务的对象是不固定的,可是状态模式服务的对象是固定的,每次都是那一个。

6. 观察者模式

为何有观察者模式:

主要是松解耦(对应Kafka依赖broker发布订阅的这种彻底的解耦方式),对于对象间一对多的依赖关系,使得每当一个对象改变状态,则全部依赖它的对象都会获得通知并自动更新。 

优势: 

  • 下降了目标与观察者之间的耦合关系;

  • 目标与观察者之间创建了一套触发机制

缺点:

  • 目标与观察者之间的依赖关系并无彻底解除,并且有可能出现循环引用。
  • 当观察者对象不少时,通知的发布会花费不少时间。

应用场景:

  • 对象间存在一对多关系,一个对象的状态改变会影响其余对象,而不知道具体有多少对象须要被改变;
  • 当一个抽象模型有两个方面,其中一个方面依赖于另外一方面时,可将这两者封装在独立的对象中以使它们能够各自独立地改变和复用

实现:

该模式具备如下角色

抽象主题(Subject):提供一个用于保存观察者对象的汇集类和增长、删除观察者对象的方法,以及通知全部观察者的抽象方法;
具体主题(Concrete Subject):在具体主题内部状态改变时,给全部登记过的观察者发出通知。
抽象观察者(Observer):为全部的具体观察者定义一个接口,在获得主题更改通知时更新本身。
具体观察者(Concrete Observer):实现抽象观察者中定义的抽象方法,以便在获得目标的更改通知时更新自身的状态。

让耦合的双方依赖于抽象,而不是依赖于具体,从而使各自的变化不会影响另外一边的变化,这是依赖倒转原则的最佳体现。

首先是一个观察者接口

1 public interface Subscriber {
2     int receive(String publisher, String articleName);
3 }

有一个具体的观察者(微信客户端),实现receive方法

 1 public class WeChatClient implements Subscriber {
 2     private String username;
 3 
 4     public WeChatClient(String username) {
 5         this.username = username;
 6     }
 7 
 8     @Override
 9     public int receive(String publisher, String articleName) {
10         // 接收到推送时的操做
11         System.out.println(String.format("用户<%s> 接收到 <%s>微信公众号 的推送,文章标题为 <%s>", username, publisher, articleName));
12         return 0;
13     }
14 }
View Code

接着是一个抽象主题,该类维护了一个订阅者列表,实现了订阅、取消订阅、通知全部订阅者等功能

 1 public class Publisher {
 2     private List<Subscriber> subscribers;
 3     private boolean pubStatus = false;
 4 
 5     public Publisher() {
 6         subscribers = new ArrayList<Subscriber>();
 7     }
 8 
 9     protected void subscribe(Subscriber subscriber) {
10         this.subscribers.add(subscriber);
11     }
12 
13     protected void unsubscribe(Subscriber subscriber) {
14         if (this.subscribers.contains(subscriber)) {
15             this.subscribers.remove(subscriber);
16         }
17     }
18 
19     protected void notifySubscribers(String publisher, String articleName) {
20         if (this.pubStatus == false) {
21             return;
22         }
23         for (Subscriber subscriber : this.subscribers) {
24             subscriber.receive(publisher, articleName);
25         }
26         this.clearPubStatus();
27     }
28 
29     protected void setPubStatus() {
30         this.pubStatus = true;
31     }
32 
33     protected void clearPubStatus() {
34         this.pubStatus = false;
35     }
36 }
View Code

最后是一个具体主题微信公众号类,该类提供了 publishArticles 方法,用于发布推送,当文章发布完毕时调用父类的通知全部订阅者方法

 1 public class WeChatAccounts extends Publisher {
 2     private String name;
 3 
 4     public WeChatAccounts(String name) {
 5         this.name = name;
 6     }
 7 
 8     public void publishArticles(String articleName, String content) {
 9         System.out.println(String.format("\n<%s>微信公众号 发布了一篇推送,文章名称为 <%s>,内容为 <%s> ", this.name, articleName, content));
10         setPubStatus();
11         notifySubscribers(this.name, articleName);
12     }
13 }
View Code

客户端使用

 1 public class Test {
 2     public static void main(String[] args) {
 3         WeChatAccounts accounts = new WeChatAccounts("小旋锋");
 4 
 5         WeChatClient user1 = new WeChatClient("张三");
 6         WeChatClient user2 = new WeChatClient("李四");
 7         WeChatClient user3 = new WeChatClient("王五");
 8 
 9         accounts.subscribe(user1);
10         accounts.subscribe(user2);
11         accounts.subscribe(user3);
12 
13         accounts.publishArticles("设计模式 | 观察者模式及典型应用", "观察者模式的内容...");
14 
15         accounts.unsubscribe(user1);
16         accounts.publishArticles("设计模式 | 单例模式及典型应用", "单例模式的内容....");
17     }
18 }
View Code

输出,当公众号发布一篇推送时,订阅该公众号的用户可及时接收到推送的通知

1 <小旋锋>微信公众号 发布了一篇推送,文章名称为 <设计模式 | 观察者模式及典型应用>,内容为 <观察者模式的内容...> 
2 用户<张三> 接收到 <小旋锋>微信公众号 的推送,文章标题为 <设计模式 | 观察者模式及典型应用>
3 用户<李四> 接收到 <小旋锋>微信公众号 的推送,文章标题为 <设计模式 | 观察者模式及典型应用>
4 用户<王五> 接收到 <小旋锋>微信公众号 的推送,文章标题为 <设计模式 | 观察者模式及典型应用>
5 
6 <小旋锋>微信公众号 发布了一篇推送,文章名称为 <设计模式 | 单例模式及典型应用>,内容为 <单例模式的内容....> 
7 用户<李四> 接收到 <小旋锋>微信公众号 的推送,文章标题为 <设计模式 | 单例模式及典型应用>
8 用户<王五> 接收到 <小旋锋>微信公众号 的推送,文章标题为 <设计模式 | 单例模式及典型应用>

7. 中介者模式

为何有中介者模式:

把原来对象之间多对多的交互变成一对多的交互;减小各个对象之间的耦合,能够更方便的增长减小对象。 

优势: 

  • 简化了对象之间的交互,将网状结构转换成相对简单的星型结构,一对多关系更容易理解、维护和扩展;

  • 松耦合,能够独立的改变和复用每个同事和中介者,增长新的中介者和新的同事类都比较方便,更好地符合 “开闭原则

缺点:

  • 具体中介者类中包含了大量同事之间的交互细节,会致使具体中介者类很是复杂,系统很差维护

应用场景:

  • 一组定义良好的对象,如今要进行复杂的相互通讯;
  • 想经过一个中间类来封装多个类中的行为,而又不想生成太多的子类。
  • 示例不少,好比
    • MVC模式中,Controller 是中介者,根据View 层的请求来操做Model 层

    • 好比组成原理中的主板,上边插了CPU、内存、网卡、声卡、硬盘各类东西

实现(参考https://blog.csdn.net/wwwdc1012/article/details/83389158):

该模式具备如下角色

抽象中介者(Mediator)角色:它是中介者的接口,提供了同事对象注册与转发同事对象信息的抽象方法。

具体中介者(ConcreteMediator)角色:实现中介者接口,定义一个 List 来管理同事对象,协调各个同事角色之间的交互关系,所以它依赖于同事角色。

抽象同事类(Colleague)角色:定义同事类的接口,保存中介者对象,提供同事对象交互的抽象方法,实现全部相互影响的同事类的公共功能

具体同事类(Concrete Colleague)角色:实现了在抽象同事类中声明的抽象方法,每一个同事都知道中介者对象,要与同事通讯则把通讯告诉中介者。

好比,具体同事类,两个本身写的定时任务

 1 public class MyOneTask extends TimerTask {
 2     private static int num = 0;
 3     @Override
 4     public void run() {
 5         System.out.println("I'm MyOneTask " + ++num);
 6     }
 7 }
 8 
 9 public class MyTwoTask extends TimerTask {
10     private static int num = 1000;
11     @Override
12     public void run() {
13         System.out.println("I'm MyTwoTask " + num--);
14     }
15 }
View Code

客户端

1 public class TimerTest {
2     public static void main(String[] args) {
3         // 注意:多线程并行处理定时任务时,Timer运行多个TimeTask时,只要其中之一没有捕获抛出的异常,
4         // 其它任务便会自动终止运行,使用ScheduledExecutorService则没有这个问题
5         Timer timer = new Timer();
6         timer.schedule(new MyOneTask(), 3000, 1000); // 3秒后开始运行,循环周期为 1秒
7         timer.schedule(new MyTwoTask(), 3000, 1000);
8     }
9 }
View Code

输出

 1 I'm MyOneTask 1
 2 I'm MyTwoTask 1000
 3 I'm MyTwoTask 999
 4 I'm MyOneTask 2
 5 I'm MyOneTask 3
 6 I'm MyTwoTask 998
 7 I'm MyTwoTask 997
 8 I'm MyOneTask 4
 9 I'm MyOneTask 5
10 I'm MyTwoTask 996
11 I'm MyTwoTask 995
12 I'm MyOneTask 6
13 ...

具体中介者Timer

 1 public class Timer {
 2     private final TaskQueue queue = new TaskQueue();
 3     private final TimerThread thread = new TimerThread(queue);
 4     public void schedule(TimerTask task, long delay) {
 5         if (delay < 0)
 6             throw new IllegalArgumentException("Negative delay.");
 7         sched(task, System.currentTimeMillis()+delay, 0);
 8     }
 9     
10     public void schedule(TimerTask task, Date time) {
11         sched(task, time.getTime(), 0);
12     }
13     
14     private void sched(TimerTask task, long time, long period) {
15         if (time < 0)
16             throw new IllegalArgumentException("Illegal execution time.");
17 
18         if (Math.abs(period) > (Long.MAX_VALUE >> 1))
19             period >>= 1;
20 
21         // 获取任务队列的锁(同一个线程屡次获取这个锁并不会被阻塞,不一样线程获取时才可能被阻塞)
22         synchronized(queue) {
23             // 若是定时调度线程已经终止了,则抛出异常结束
24             if (!thread.newTasksMayBeScheduled)
25                 throw new IllegalStateException("Timer already cancelled.");
26 
27             // 再获取定时任务对象的锁(为何还要再加这个锁呢?想不清)
28             synchronized(task.lock) {
29                 // 判断线程的状态,防止多线程同时调度到一个任务时屡次被加入任务队列
30                 if (task.state != TimerTask.VIRGIN)
31                     throw new IllegalStateException(
32                         "Task already scheduled or cancelled");
33                 // 初始化定时任务的下次执行时间
34                 task.nextExecutionTime = time;
35                 // 重复执行的间隔时间
36                 task.period = period;
37                 // 将定时任务的状态由TimerTask.VIRGIN(一个定时任务的初始化状态)设置为TimerTask.SCHEDULED
38                 task.state = TimerTask.SCHEDULED;
39             }
40             
41             // 将任务加入任务队列
42             queue.add(task);
43             // 若是当前加入的任务是须要第一个被执行的(也就是他的下一次执行时间离如今最近)
44             // 则唤醒等待queue的线程(对应到上面提到的queue.wait())
45             if (queue.getMin() == task)
46                 queue.notify();
47         }
48     }
49     
50     // cancel会等到全部定时任务执行完后马上终止定时线程
51     public void cancel() {
52         synchronized(queue) {
53             thread.newTasksMayBeScheduled = false;
54             queue.clear();
55             queue.notify();  // In case queue was already empty.
56         }
57     }
58     // ...
59 }
View Code

Timer 中在 schedulexxx 方法中经过 TaskQueue 协调各类 TimerTask 定时任务,Timer 是中介者,TimerTask 是抽象同事类,而咱们本身写的任务则是具体同事类

TimerThread 是 Timer 中定时调度线程类的定义,这个类会作为一个线程一直运行来执行 Timer 中任务队列中的任务。

Timer 这个中介者的功能就是定时调度咱们写的各类任务,将任务添加到 TaskQueue 任务队列中,给 TimerThread 执行,让任务与执行线程解耦
注:

  1. 实现过程当中可能存在一些问题,好比若是中介者只有一个的话,并且预计中也没有扩展的要求,那么就能够不定义Mediator接口,把中介者对象设为单例
  2. 实现同事和中介者的通讯:一种实现方式是在Mediator接口中定义一个通用的方法,让各个同事类来调用这个方法 ;另一种实现方式是能够采用观察者模式,把Mediator实现成为观察者,而各个同事类实现成为Subject,这样同事类发生了改变,会通知Mediator。
  3. 一般会去掉同事对象的父类,这样可让任意的对象,只要须要相互交互就能够成为同事;
  4. 中介者对象能够不把同事做为本身的属性,能够在处理方法中建立或者获取须要的同事对象;
  5. 同事类须要知道中介者对象,在改变时通知它,但不必做为属性这么强依赖,能够把中介对象作成单例,直接在同事类的方法里面去调用中介者对象。

8. 迭代器模式

为何有迭代器模式:

将遍历和实现分开,顺序的访问集合内部的对象,而又不暴露集合内部的表示。 

优势: 

  • 遍历由迭代器完成,简化了集合类;
  • 支持以不一样方式遍历一个集合,能够自定义迭代器的子类以支持新的遍历;
  • 增长新的集合类和迭代器类都很方便,无须修改原有代码;
  • 封装性良好,为遍历不一样的集合结构提供一个统一的接口。

缺点:

  • 增长类的个数,好比增长新的集合类须要增长新的具体迭代器

应用场景:

这个JDK已经提供了Iterator接口,咱们直接使用就行,如今不多本身写迭代器了吧

实现

 该模式具备如下角色:

抽象迭代器(Iterator):抽象迭代器负责定义访问和遍历元素的接口,并且基本上是有一些固定的方法:

  • first()得到第一个元素
  • next()访问下一个元素
  • hasNext()是否已经访问到底部

具体迭代器(ConcreteIterator):具体迭代器角色要实现迭代器接口,完成容器元素的遍历

抽象容器(Aggregate):容器角色负责提供建立具体迭代器角色的接口,必然提供一个相似createIterator()这样的方法,在Java中通常是iterator()方法

具体容器(Concrete Aggregate):具体容器实现容器接口定义的方法,建立出容纳迭代器的对象

好比,首先iterator,Iterator中最重要的两个方法就是next方法和hasNext方法,构成了遍历整个容器数据的两个方法。remove方法若是没有实现的话默认是会抛出一个不支持该操做的异常。

1 public interface Iterator<E> {
2     boolean hasNext();
3     E next();
4     default void remove() {
5         throw new UnsupportedOperationException();
6     }
7 }

而后ConcreteIterator

 1 private class Itr<E> implements Iterator<E> {
 2         private int position = -1;
 3         private Object[] data = elements;
 4         @Override
 5         public boolean hasNext() {
 6             return ++ position < data.length;
 7         }
 8 
 9         @Override
10         public E next() {
11             return (E) data[position];
12         }
13 }

而后是aggregate

1 public interface Iterable<E> {
2     Iterator<E> createIterator();
3 }

concrete aggregate

 1 public class MyContainer<E> implements Iterable<E>{
 2 
 3     Object[] elements;
 4     public MyContainer() {
 5         elements = new Byte[10];
 6         for (int i =0; i < 10; i ++)
 7             elements[i] = (byte) i;
 8     }
 9     @Override
10     public Iterator<E> createIterator() {
11         return new Itr();
12     }
13 
14 }

客户端

1 iterator it = new MyContainer<Byte>().createIterator();
2 while (elements.hasNext()) {
3     System.out.println(elements.next());
4 }

注:为何不让容器直接继承Iterator接口,还要整个Iterable接口?

1 为了提供容器内数据安全性。假设容器实现了Iterator接口,那么咱们全部经过Iterator接口进行的数据访问修改操做都会直接影响容器内的数据,由于咱们访问的数据和容器维护的数据是同一份数据。因此不如让Iterator接口访问数据副原本的安全。本身用本身的,互不影响。

9. 访问者模式

为何有访问者模式:

将稳定的数据结构和易变的数据访问操做分离,知足开闭原则。 

优势: 

  • 将有关元素对象的访问行为集中到一个访问者对象中,而不是分散在一个个的元素类中,符合单一职责原则
  • 能够经过接受新的访问者增长新的访问操做十分方便,对扩展开放,符合开闭原则

缺点:

  • 访问者模式中具体元素对访问者公布细节,破坏了对象的封装性,违反了迪米特原则;
  • 每增长一个新的元素类,都要在每个具体访问者类中增长相应的具体操做,违反了开闭原则;
  • 违反了依赖倒置原则,依赖了具体类,没有依赖抽象

应用场景:

最复杂的设计模式,而且使用频率不高。通常场景以下:

 一、对象结构中对象对应的元素类不多改变,但常常须要在此对象结构上定义新的操做;

 二、须要对一个对象结构中的元素进行不少不一样的而且不相关的操做,而须要避免让这些操做"污染"

实现:

抽象访问者(Vistor):抽象访问者为对象结构中每个具体元素类声明一个访问操做,从这个操做的名称或参数类型能够清楚知道须要访问的具体元素的类型,具体访问者须要实现这些操做方法,定义对这些元素的访问操做;
具体访问者(ConcreteVisitor):具体访问者实现了每一个由抽象访问者声明的操做;
抽象元素(Element):抽象元素通常是抽象类或者接口,它定义一个accept()方法,该方法一般以一个抽象访问者做为参数;
具体元素(ConcreteElement):具体元素实现了accept()方法,在accept()方法中调用访问者的访问方法以便完成对一个元素的操做;
对象结构(ObjectStructure):对象结构是一个元素的集合,它用于存放元素对象,而且提供了遍历其内部元素的方法。

 它实际上是对不一样数据结构的访问封装,例如

 1 // 抽象元素
 2 public interface Element {
 3     void accept(Visitor visitor);
 4 }
 5 // 具体元素A 
 6 public class ElementA implements Element {
 7     @Override
 8     public void accept(Visitor visitor) {
 9         visitor.visit(this);
10     }
11 }
12 // 具体元素B 
13 public class ElementB implements Element {
14     @Override
15     public void accept(Visitor visitor) {
16         visitor.visit(this);
17     }
18 }
19 // 抽象访问者 
20 public abstract class Visitor {
21     public abstract void visit(ElementA elementA);
22     public abstract void visit(ElementB elementB);
23 }
24 // 具体访问者A 
25 public class VisitorA extends Visitor {
26     @Override
27     public void visit(ElementA elementA) {
28         System.out.println(this + ":elementA");
29     }
30  
31     @Override
32     public void visit(ElementB elementB) {
33         System.out.println(this + ":elementB");
34     }
35 }
36 // 对象结构 
37 public class ObjectStructure {
38     private List<Element> elementList;
39  
40     public ObjectStructure() {
41         this.elementList = new ArrayList<>();
42     }
43  
44     public void addElement(Element element) {
45         elementList.add(element);
46     }
47  
48     public void removeElement(Element element) {
49         elementList.remove(element);
50     }
51  
52     public void accept(Visitor visitor) {
53         elementList.forEach(element -> element.accept(visitor));
54     }
55 }
56 // 客户端 
57 @Test
58 public void VisitorTest() {
59    ObjectStructure objectStructure = new ObjectStructure();
60    objectStructure.addElement(new ElementA());
61    objectStructure.addElement(new ElementB());
62    objectStructure.accept(new VisitorA());
63 }
View Code

输出:

com.test.visitor.visitorA@33a10788:ElementA
com.test.visitor.visitorA@33a10788:ElementB

10. 备忘录模式

为何有备忘录模式:

保存对象的状态,恢复对象的状态 

优势: 

  • 给用户提供了一种能够恢复状态的机制,能够是用户可以方便的回到某个历史的状态;
  • 简化了发起人类。发起人不须要管理和保存其内部状态的各个备份,全部状态信息都保存在备忘录中,并由管理者进行管理,符合单一职责原则

缺点:

  • 资源消耗过大,若是须要保存的发起人类的成员变量太多,就不可避免须要占用大量的存储空间,每保存一次对象的状态都须要消耗必定的系统资源

应用场景:

JDK、Spring不多用备忘录的场景,通常经常使用于须要保存/恢复数据的相关状态场景,好比:

浏览器回退:当咱们能够在浏览器左上角点击左箭头回退到上一次的页面,也能够点击右箭头从新回到当前页面

数据库备份与还原:备份当前已有的数据或者记录保留,还原已经保留的数据

编辑器撤销与重作:写错时能够按快捷键 Ctrl + z 撤销,撤销后能够按 Ctrl + y 重作(md 居然如今才知道......)

虚拟机生成快照与恢复:虚拟机能够生成一个快照,当虚拟机发生错误时能够恢复到快照的样子

Git版本管理:Git是最多见的版本管理软件,每提交一个新版本,实际上Git就会把它们自动串成一条时间线,每一个版本都有一个版本号,使用 git reset --hard 版本号 便可回到指定的版本

棋牌游戏悔棋:在棋牌游戏中能够悔棋,回退到上一步状态

实现:

该模式具备如下角色:

发起人角色(Originator):当前时刻的内部状态信息,提供建立备忘录和恢复备忘录数据的功能,实现其余业务功能;

备忘录角色(Memento):负责存储发起人的内部状态,在须要的时候提供这些内部状态给发起人。

管理者角色(Caretaker):对备忘录进行管理,提供保存与获取备忘录的功能,但其不能对备忘录的内容进行访问与修改。

还须要注意下宽接口和窄接口:

窄接口:只容许它把备忘录对象传给其余的对象,针对的是负责人对象和其余除发起人对象以外的任何对象。

宽接口:  容许它读取全部的数据,以便根据这些数据恢复这个发起人对象的内部状态,针对发起人。 

 1 // 备忘录类
 2 public class Memento {
 3    private String state;
 4  
 5    public Memento(String state){
 6       this.state = state;
 7    }
 8  
 9    public String getState(){
10       return state;
11    }  
12 }
View Code
 1 // 发起人类
 2 public class Originator {
 3    private String state;
 4  
 5    public void setState(String state){
 6       this.state = state;
 7    }
 8  
 9    public String getState(){
10       return state;
11    }
12  
13    public Memento saveStateToMemento(){
14       return new Memento(state);
15    }
16  
17    public void getStateFromMemento(Memento Memento){
18       state = Memento.getState();
19    }
20 }
View Code
 1 // 管理者类
 2 import java.util.ArrayList;
 3 import java.util.List;
 4  
 5 public class CareTaker {
 6    private List<Memento> mementoList = new ArrayList<Memento>();
 7  
 8    public void add(Memento state){
 9       mementoList.add(state);
10    }
11  
12    public Memento get(int index){
13       return mementoList.get(index);
14    }
15 }
View Code
 1 // 客户端
 2 public class MementoPatternDemo {
 3    public static void main(String[] args) {
 4       Originator originator = new Originator();
 5       CareTaker careTaker = new CareTaker();
 6       originator.setState("State #1");
 7       originator.setState("State #2");
 8       careTaker.add(originator.saveStateToMemento());
 9       originator.setState("State #3");
10       careTaker.add(originator.saveStateToMemento());
11       originator.setState("State #4");
12  
13       System.out.println("Current State: " + originator.getState());    
14       originator.getStateFromMemento(careTaker.get(0));
15       System.out.println("First saved State: " + originator.getState());
16       originator.getStateFromMemento(careTaker.get(1));
17       System.out.println("Second saved State: " + originator.getState());
18    }
19 }
View Code
// 输出
Current State: State #4 First saved State: State #2 Second saved State: State #3

11. 解释器模式

为何有解释器模式:

对于一些固定文法构建一个解释句子的解释器,其中文法是文法是用于描述语言语法结构的形式规则

优势: 

  • 扩展性,修改语法规则只要修改相应的非终结符表达式就能够了;若扩展语法,则只要增长非终结符类就能够了

缺点:

  • 执行效率较低。解释器模式中一般使用大量的循环和递归调用,当要解释的句子较复杂时,其运行速度很慢,且代码的调试过程也比较麻烦;
  • 会引发类膨胀。解释器模式中的每条规则至少须要定义一个类,当包含的文法规则不少时,类的个数将急剧增长,致使系统难以管理与维护;
  • 可应用的场景比较少。在软件开发中,须要定义语言文法的应用实例很是少,因此这种模式不多被使用到。

应用场景:

解释器模式在实际的软件开发中使用比较少,由于它会引发效率、性能以及维护等问题。若是碰到对表达式的解释,在JAVA 中能够用 Expression4J 或 Jep 等来设计。该模式主要应用在:

  • 当语言的文法较为简单,且执行效率不是关键问题时;
  • 当问题重复出现,且能够用一种简单的语言来进行表达时;
  • 当一个语言须要解释执行,而且语言中的句子能够表示为一个抽象语法树的时候,如XML文档解释

实现:

该模式具备如下角色:

抽象解释器(AbstractExpression):它包含了解释方法 interpret;
终结符表达式(TerminalExpression):实现与文法中的元素相关联的解释操做,一般一个解释器模式中只有一个终结表达式,但对应不一样的终结符,好比四则运算中的终结符就是运算元素;
非终结符表达式(NonterminalExpression):文法中的每条规则对应于一个非终结表达式,好比四则运算中的加法运算、减法运算等
上下文(Context): 上下文环境类,包含解释器以外的全局信息,通常是 HashMap

实现一个简单的计算器 

 1 // 解释器接口
 2 public interface Expression {
 3     int interpreter(Context context);//必定会有解释方法
 4 }
 5 
 6 // 终结符表达式(在这个例子,用来存放数字,或者表明数字的字符)
 7 public class TerminalExpression implements Expression{
 8 
 9     String variable;
10     public TerminalExpression(String variable){
11 
12         this.variable = variable;
13     }
14     @Override
15     public int interpreter(Context context) {
16         return context.lookup(this);
17     }
18 }
19 
20 // 抽象非终结符表达式
21 public abstract class NonTerminalExpression implements Expression{
22     Expression e1,e2;
23     public NonTerminalExpression(Expression e1, Expression e2){
24 
25         this.e1 = e1;
26         this.e2 = e2;
27     }
28 }
29 
30 // 加法运算
31 public class PlusOperation extends NonTerminalExpression {
32 
33     public PlusOperation(Expression e1, Expression e2) {
34         super(e1, e2);
35     }
36 
37     //将两个表达式相加
38     @Override
39     public int interpreter(Context context) {
40         return this.e1.interpreter(context) + this.e2.interpreter(context);
41     }
42 }
43 
44 // 减法表达式实现类
45 public class MinusOperation extends NonTerminalExpression {
46 
47     public MinusOperation(Expression e1, Expression e2) {
48         super(e1, e2);
49     }
50 
51  //将两个表达式相减
52     @Override
53     public int interpreter(Context context) {
54         return this.e1.interpreter(context) - this.e2.interpreter(context);
55     }
56 }
57 
58 
59 // 上下文类(这里主要用来将变量解析成数字【固然一开始要先定义】
60 public class Context {
61     private Map<Expression, Integer> map = new HashMap<>();
62 
63     //定义变量
64     public void add(Expression s, Integer value){
65         map.put(s, value);
66     }
67     //将变量转换成数字
68     public int lookup(Expression s){
69         return map.get(s);
70     }
71 }
72 
73 // 测试类
74 public class Test {
75     public static void main(String[] args) {
76 
77         Context context = new Context();
78         TerminalExpression a = new TerminalExpression("a");
79         TerminalExpression b = new TerminalExpression("b");
80         TerminalExpression c = new TerminalExpression("c");
81         context.add(a, 4);
82         context.add(b, 8);
83         context.add(c, 2);
84 
85         System.out.println(new MinusOperation(new PlusOperation(a,b), c).interpreter(context));
86     }
87 }
88 
89 运行结果以下
90 -----------------------------------
91 10
View Code
相关文章
相关标签/搜索