欢迎访问shiker.tech

请允许在我们的网站上展示广告

您似乎使用了广告拦截器,请关闭广告拦截器。我们的网站依靠广告获取资金。

行为型设计模式实战【三】-观察者、策略、状态
(last modified Sep 8, 2022, 1:08 AM )
by
侧边栏壁纸
  • 累计撰写 176 篇文章
  • 累计创建 61 个标签
  • 累计收到 4 条评论

目 录CONTENT

文章目录

行为型设计模式实战【三】-观察者、策略、状态

橙序员
2022-09-03 / 0 评论 / 0 点赞 / 366 阅读 / 7,289 字 / 正在检测百度是否收录... 正在检测必应是否收录...
文章摘要(AI生成)

观察者模式定义定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖它的对象都得到通知并被自动更新角色Subject(观察对象):Subject 角色表示观察对象。Subject角色定义了注册观察者和删除观察者的方法。此外,它还声明了“获取现在的状态”的方法。ConcreteSubj

观察者模式

定义

定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖它的对象都得到通知并被自动更新

观察者模式也叫做发布/订阅模型

优缺点

优点

  • 观察者和被观察者之间是抽象耦合

    如此设计,则不管是增加观察者还是被观察者都非常容易扩展,而且在Java中都已经实 现的抽象层级的定义,在系统扩展方面更是得心应手。

  • 建立一套触发机制

    根据单一职责原则,每个类的职责是单一的,那么怎么把各个单一的职责串联成真实世 界的复杂的逻辑关系呢?比如,我们去打猎,打死了一只母鹿,母鹿有三个幼崽,因失去了 母鹿而饿死,尸体又被两只秃鹰争抢,因分配不均,秃鹰开始斗殴,然后羸弱的秃鹰死掉, 生存下来的秃鹰,则因此扩大了地盘……这就是一个触发机制,形成了一个触发链。观察者 模式可以完美地实现这里的链条形式。

缺点

观察者模式需要考虑一下开发效率和运行效率问题,一个被观察者,多个观察者,开发 和调试就会比较复杂,而且在Java中消息的通知默认是顺序执行,一个观察者卡壳,会影响 整体的执行效率。在这种情况下,一般考虑采用异步的方式。 多级触发时的效率更是让人担忧,大家在设计时注意考虑。

使用场景

  • 关联行为场景。需要注意的是,关联行为是可拆分的,而不是“组合”关系。
  • 事件多级触发场景。
  • 跨系统的消息交换场景,如消息队列的处理机制。

最佳实践

观察者模式在实际项目和生活中非常常见,我们举几个经常发生的例子来说明

文件系统

比如,在一个目录下新建立一个文件,这个动作会同时通知目录管理器增加该目录,并 通知磁盘管理器减少1KB的空间,也就说“文件”是一个被观察者,“目录管理器”和“磁盘管理 器”则是观察者。

猫鼠游戏

夜里猫叫一声,家里的老鼠撒腿就跑,同时也吵醒了熟睡的主人,这个场景中,“猫”就 是被观察者,老鼠和人则是观察者。

ATM取钱

比如你到ATM机器上取钱,多次输错密码,卡就会被ATM吞掉,吞卡动作发生的时 候,会触发哪些事件呢?第一,摄像头连续快拍,第二,通知监控系统,吞卡发生;第三, 初始化ATM机屏幕,返回最初状态。一般前两个动作都是通过观察者模式来完成的,后一个 动作是异常来完成。

广播收音机

电台在广播,你可以打开一个收音机,或者两个收音机来收听,电台就是被观察者,收 音机就是观察者。

角色

  • Subject(观察对象):Subject 角色表示观察对象。Subject角色定义了注册观察者和删除观察者的方法。此外,它还声明了“获取现在的状态”的方法。
  • ConcreteSubject(具体的观察对象):ConcreteSubject角色表示具体的被观察对象。当自身状态发生变化后,它会通知所有已经注册的Observer角色。
  • Observer (观察者):Observer角色负责接收来自Subject角色的状态变化的通知。为此,它声明了update方法。
  • ConcreteObserver(具体的观察者):ConcreteObserver角色表示具体的Observer。当它的update方法被调用后,会去获取要观察的对象的最新状态。

image-20220903141629377

spring实现

下面我们来看一段使用了Observer模式的示例程序。这是一段简单的示例程序,观察者将观察一个会生成数值的对象,并将它生成的数值结果显示出来。不过,不同的观察者的显示方式不一样。DigitObserver会以数字形式显示数值,而Graph0bserver则会以简单的图示形式来显示数值。

观察对象

表示生成数值对象的抽象类

public abstract class NumberGenerator {

   private ArrayList<Observer> observers = new ArrayList<Observer>();

   public void deleteObserver(Observer observer){
      observers.remove(observer);
   }
   public void addObserver(Observer observer){
      observers.add(observer);
   }
   public void notifyObservers(){
      Iterator< Observer> it = observers.iterator();
      while (it.hasNext()) {
         Observer o = (Observer) it.next();
         o.update(this);
      }
   }
   public abstract int getNumber();
   public abstract void execute();
}

具体的观察对象

表示生成具体随机数的抽象类

@Component
public class RandomNumberGenerator extends NumberGenerator {

   private Random random  = new Random();
   private int number;
   @Override
   public int getNumber() {
      // TODO Auto-generated method stub
      return number;
   }

   @Override
   public void execute() {
      // TODO Auto-generated method stub
        for(int i=0;i<20;i++){
           number = random.nextInt(50);
           notifyObservers();
        }
   }

}

观察者

表示观察者接口

public interface Observer {

   void update(NumberGenerator generator);
}

具体观察者

获取打印具体数字

@Component
@Slf4j
public class DigitObserver implements Observer {
   @Override
   public void update(NumberGenerator generator) {
      // TODO Auto-generated method stub
      log.info("DigitObserver:"+generator.getNumber());
      try{
         Thread.sleep(100);
      }catch(InterruptedException e){
         
      }
   }

}

用图形显示数值:

@Component
@Slf4j
public class GraphObserver implements Observer {

   @Override
   public void update(NumberGenerator generator) {
      // TODO Auto-generated method stub
        StringBuilder stringBuilder = new StringBuilder("GraphObserver:");
        int count = generator.getNumber();
        for(int i=0;i<count;i++){
           stringBuilder.append("*");
        }
      log.info(stringBuilder.toString());
        try{
           Thread.sleep(100);
        }catch(InterruptedException e){
           
        }
   }

}

测试

@RestController
public class ObserverController {

   @Resource
   NumberGenerator numberGenerator;
   @Resource
   DigitObserver digitObserver;
   @Resource
   GraphObserver graphObserver;

   @GetMapping("observer")
   public String observer(){
      numberGenerator.addObserver(digitObserver);
      numberGenerator.addObserver(graphObserver);
      numberGenerator.execute();
      return "SUCCESS";
   }
}

测试结果如下:

2022-09-03 14:12:23.029  INFO 12672 --- [nio-8080-exec-2] c.e.d.Observer.DigitObserver             : DigitObserver:48
2022-09-03 14:12:23.132  INFO 12672 --- [nio-8080-exec-2] c.e.d.Observer.GraphObserver             : GraphObserver:************************************************
2022-09-03 14:12:23.241  INFO 12672 --- [nio-8080-exec-2] c.e.d.Observer.DigitObserver             : DigitObserver:20
2022-09-03 14:12:23.352  INFO 12672 --- [nio-8080-exec-2] c.e.d.Observer.GraphObserver             : GraphObserver:********************
2022-09-03 14:12:23.462  INFO 12672 --- [nio-8080-exec-2] c.e.d.Observer.DigitObserver             : DigitObserver:22
2022-09-03 14:12:23.574  INFO 12672 --- [nio-8080-exec-2] c.e.d.Observer.GraphObserver             : GraphObserver:**********************
2022-09-03 14:12:23.684  INFO 12672 --- [nio-8080-exec-2] c.e.d.Observer.DigitObserver             : DigitObserver:41
2022-09-03 14:12:23.793  INFO 12672 --- [nio-8080-exec-2] c.e.d.Observer.GraphObserver             : GraphObserver:*****************************************
2022-09-03 14:12:23.903  INFO 12672 --- [nio-8080-exec-2] c.e.d.Observer.DigitObserver             : DigitObserver:31
2022-09-03 14:12:24.012  INFO 12672 --- [nio-8080-exec-2] c.e.d.Observer.GraphObserver             : GraphObserver:*******************************
2022-09-03 14:12:24.124  INFO 12672 --- [nio-8080-exec-2] c.e.d.Observer.DigitObserver             : DigitObserver:23
2022-09-03 14:12:24.233  INFO 12672 --- [nio-8080-exec-2] c.e.d.Observer.GraphObserver             : GraphObserver:***********************
2022-09-03 14:12:24.344  INFO 12672 --- [nio-8080-exec-2] c.e.d.Observer.DigitObserver             : DigitObserver:40
2022-09-03 14:12:24.455  INFO 12672 --- [nio-8080-exec-2] c.e.d.Observer.GraphObserver             : GraphObserver:****************************************
2022-09-03 14:12:24.566  INFO 12672 --- [nio-8080-exec-2] c.e.d.Observer.DigitObserver             : DigitObserver:19
2022-09-03 14:12:24.678  INFO 12672 --- [nio-8080-exec-2] c.e.d.Observer.GraphObserver             : GraphObserver:*******************
2022-09-03 14:12:24.790  INFO 12672 --- [nio-8080-exec-2] c.e.d.Observer.DigitObserver             : DigitObserver:2

状态模式

定义

当一个对象内在状态改变时允许其改变行为,这个对象看起来像改变了其类。

优缺点

优点

  • 结构清晰

    避免了过多的switch…case或者if…else语句的使用,避免了程序的复杂性,提高系统的可 维护性。

  • 遵循设计原则

    很好地体现了开闭原则和单一职责原则,每个状态都是一个子类,你要增加状态就要增 加子类,你要修改状态,你只修改一个子类就可以了。

  • 封装性非常好

    这也是状态模式的基本要求,状态变换放置到类的内部来实现,外部的调用不用知道类 内部如何实现状态和行为的变换。

缺点

状态模式既然有优点,那当然有缺点了。但只有一个缺点,子类会太多,也就是类膨 胀。如果一个事物有很多个状态也不稀奇,如果完全使用状态模式就会有太多的子类,不好管理,这个需要大家在项目中自己衡量。其实有很多方式可以解决这个状态问题,如在数据库中建立一个状态表,然后根据状态执行相应的操作

使用场景

  • 行为随状态改变而改变的场景

    这也是状态模式的根本出发点,例如权限设计,人员的状态不同即使执行相同的行为结 果也会不同,在这种情况下需要考虑使用状态模式。

  • 条件、分支判断语句的替代者

    在程序中大量使用switch语句或者if判断语句会导致程序结构不清晰,逻辑混乱,使用 状态模式可以很好地避免这一问题,它通过扩展子类实现了条件的判断处理。

最佳实践

允许状态间的跳跃

状态间的自由切换k恶意通过建造者模式按照一定顺序进行重新组装

一个节点会有初始状态、挂起状态呢、完成状态等,可以通过上下文做妆台机的管理

角色

  • State(状态):State角色表示状态,定义了根据不同状态进行不同处理的接口(API)。该接口(API)是那些处理内容依赖于状态的方法的集合。
  • ConcreteState(具体状态):ConcreteState角色表示各个具体的状态,它实现了state接口。
  • Context(状况、前后关系、上下文):Context角色持有表示当前状态的ConcreteState角色。此外,它还定义了供外部调用者使用State模式的接口(API )。

image-20220903160219965

spring实现

这里我们来看一个警戒状态每小时会改变一次的警报系统。该系统并不会真正呼叫警报中心,只是在页面上显示呼叫状态。此外,如果以现实世界中的时间来测试程序就太慢了,所以我们假设程序中的1秒对应现实世界中的一个小时。

状态接口

提供了每个状态中对应的事件接口

public interface State {

   void doClock(Context context,int hour); //设置时间
   void doUse(Context context);//使用金库
   void doAlarm(Context context);//按下警铃
   void doPhone(Context context);//正常通话
}

具体状态

表示白天的状态

@Component
@Scope(BeanDefinition.SCOPE_PROTOTYPE)
public class DayState implements State {

   @Resource
   private StateGenerator stateGenerator;

   @Override
   public void doClock(Context context, int hour) {
      // TODO Auto-generated method stub

      if(hour<9||17<=hour){
         context.changeState(stateGenerator.nightState());
      }
   }

   @Override
   public void doUse(Context context) {
      // TODO Auto-generated method stub
        context.recordLog("使用金库(白天)");
   }

   @Override
   public void doAlarm(Context context) {
      // TODO Auto-generated method stub
        context.callSecurityCenter("按下警铃(白天)");
   }

   @Override
   public void doPhone(Context context) {
      // TODO Auto-generated method stub
        context.callSecurityCenter("正常通话(白天)");
   }

   @Override
   public String toString() {
      return "DayState [白天]";
   }

}

表示晚上的状态:

@Component
@Scope(BeanDefinition.SCOPE_PROTOTYPE)
public class NightState implements State {

   @Resource
   private StateGenerator stateGenerator;


   @Override
   public void doClock(Context context, int hour) {
      // TODO Auto-generated method stub
        if(9<=hour&&hour<17){
           context.changeState(stateGenerator.dayState());
        }
   }

   @Override
   public void doUse(Context context) {
      // TODO Auto-generated method stub
        context.callSecurityCenter("紧急:晚上使用金库");
   }

   @Override
   public void doAlarm(Context context) {
      // TODO Auto-generated method stub
        context.callSecurityCenter("按下警铃(晚上)");
   }

   @Override
   public void doPhone(Context context) {
      // TODO Auto-generated method stub
        context.recordLog("晚上的通话录音");
   }

   @Override
   public String toString() {
      return "NightState [晚上]";
   }
   

}

上下文

提供管理金库状态的接口

public interface Context {
   void setClock(int hour);
   void changeState(State state);
   void callSecurityCenter(String msg);
   void recordLog(String msg);
}

SafeFrame实现了context接口,提供按钮报警和画面显示等信息:

@Component
@Slf4j
public class SafeFrame implements Context {
   
   private State state;
   @Resource
   private StateGenerator stateGenerator;

   @PostConstruct
   private void init(){
      state = stateGenerator.dayState();
   }

   @Override
   public void setClock(int hour) {
      // TODO Auto-generated method stub
        String clockstring = "现在时间是 ";
        if(hour<10){
           clockstring += "0"+hour+":00";
        }else{
           clockstring +=hour+":00";
        }
        log.info(clockstring);
        state.doClock(this, hour);
   }

   @Override
   public void changeState(State state) {
      // TODO Auto-generated method stub
       log.info("从"+this.state+"状态变为了"+state+"状态。");
       this.state = state;
   }

   @Override
   public void callSecurityCenter(String msg) {
      // TODO Auto-generated method stub
        log.info("call!"+msg+"\n");
   }

   @Override
   public void recordLog(String msg) {
      // TODO Auto-generated method stub
      log.info("record..."+msg+"\n");
   }


   public void actionPerformed(String button) {
      // TODO Auto-generated method stub
        log.info(button);
        if(Objects.equals(button, "buttonUse")){
           state.doUse(this);
        }else if(Objects.equals(button, "buttonAlarm")){
           state.doAlarm(this);
        }else if(Objects.equals(button, "buttonPhone")){
           state.doPhone(this);
        }else{
           log.error("?");
        }
   }

}

测试

我们在一天中,每3个小时使用金库,每5个小时联络安全中心,每4个小时按下警铃

@RestController
public class StateController {

   @Resource
   private SafeFrame frame;

   @GetMapping("state")
   public String state() {
      for (int hour = 0; hour < 24; hour++) {
         frame.setClock(hour);
         if(hour%3 == 0) {
            frame.actionPerformed("buttonUse");
         }
         if(hour%5 == 0){
            frame.actionPerformed("buttonPhone");
         }
         if(hour%4 == 0){
            frame.actionPerformed("buttonAlarm");
         }
         try {
            Thread.sleep(1000);
         } catch (InterruptedException e) {

         }
      }
      return "SUCCESS";
   }
}

测试结果为:

2022-09-03 16:14:06.607  INFO 2416 --- [nio-8080-exec-1] c.example.designpattern.State.SafeFrame  : 现在时间是 00:00
2022-09-03 16:14:06.608  INFO 2416 --- [nio-8080-exec-1] c.example.designpattern.State.SafeFrame  : 从DayState [白天]状态变为了NightState [晚上]状态。
2022-09-03 16:14:06.608  INFO 2416 --- [nio-8080-exec-1] c.example.designpattern.State.SafeFrame  : buttonUse
2022-09-03 16:14:06.608  INFO 2416 --- [nio-8080-exec-1] c.example.designpattern.State.SafeFrame  : call!紧急:晚上使用金库

2022-09-03 16:14:06.608  INFO 2416 --- [nio-8080-exec-1] c.example.designpattern.State.SafeFrame  : buttonPhone
2022-09-03 16:14:06.608  INFO 2416 --- [nio-8080-exec-1] c.example.designpattern.State.SafeFrame  : record...晚上的通话录音

2022-09-03 16:14:06.608  INFO 2416 --- [nio-8080-exec-1] c.example.designpattern.State.SafeFrame  : buttonAlarm
2022-09-03 16:14:06.608  INFO 2416 --- [nio-8080-exec-1] c.example.designpattern.State.SafeFrame  : call!按下警铃(晚上)

2022-09-03 16:14:07.613  INFO 2416 --- [nio-8080-exec-1] c.example.designpattern.State.SafeFrame  : 现在时间是 01:00
2022-09-03 16:14:08.616  INFO 2416 --- [nio-8080-exec-1] c.example.designpattern.State.SafeFrame  : 现在时间是 02:00
2022-09-03 16:14:09.621  INFO 2416 --- [nio-8080-exec-1] c.example.designpattern.State.SafeFrame  : 现在时间是 03:00
2022-09-03 16:14:09.621  INFO 2416 --- [nio-8080-exec-1] c.example.designpattern.State.SafeFrame  : buttonUse
2022-09-03 16:14:09.621  INFO 2416 --- [nio-8080-exec-1] c.example.designpattern.State.SafeFrame  : call!紧急:晚上使用金库

2022-09-03 16:14:10.628  INFO 2416 --- [nio-8080-exec-1] c.example.designpattern.State.SafeFrame  : 现在时间是 04:00
2022-09-03 16:14:10.628  INFO 2416 --- [nio-8080-exec-1] c.example.designpattern.State.SafeFrame  : buttonAlarm
2022-09-03 16:14:10.628  INFO 2416 --- [nio-8080-exec-1] c.example.designpattern.State.SafeFrame  : call!按下警铃(晚上)

2022-09-03 16:14:11.636  INFO 2416 --- [nio-8080-exec-1] c.example.designpattern.State.SafeFrame  : 现在时间是 05:00
2022-09-03 16:14:11.636  INFO 2416 --- [nio-8080-exec-1] c.example.designpattern.State.SafeFrame  : buttonPhone
2022-09-03 16:14:11.636  INFO 2416 --- [nio-8080-exec-1] c.example.designpattern.State.SafeFrame  : record...晚上的通话录音

2022-09-03 16:14:12.644  INFO 2416 --- [nio-8080-exec-1] c.example.designpattern.State.SafeFrame  : 现在时间是 06:00
2022-09-03 16:14:12.644  INFO 2416 --- [nio-8080-exec-1] c.example.designpattern.State.SafeFrame  : buttonUse
2022-09-03 16:14:12.644  INFO 2416 --- [nio-8080-exec-1] c.example.designpattern.State.SafeFrame  : call!紧急:晚上使用金库

2022-09-03 16:14:13.651  INFO 2416 --- [nio-8080-exec-1] c.example.designpattern.State.SafeFrame  : 现在时间是 07:00
2022-09-03 16:14:14.658  INFO 2416 --- [nio-8080-exec-1] c.example.designpattern.State.SafeFrame  : 现在时间是 08:00
2022-09-03 16:14:14.658  INFO 2416 --- [nio-8080-exec-1] c.example.designpattern.State.SafeFrame  : buttonAlarm
2022-09-03 16:14:14.658  INFO 2416 --- [nio-8080-exec-1] c.example.designpattern.State.SafeFrame  : call!按下警铃(晚上)

2022-09-03 16:14:15.666  INFO 2416 --- [nio-8080-exec-1] c.example.designpattern.State.SafeFrame  : 现在时间是 09:00
2022-09-03 16:14:15.666  INFO 2416 --- [nio-8080-exec-1] c.example.designpattern.State.SafeFrame  : 从NightState [晚上]状态变为了DayState [白天]状态。
2022-09-03 16:14:15.666  INFO 2416 --- [nio-8080-exec-1] c.example.designpattern.State.SafeFrame  : buttonUse
2022-09-03 16:14:15.666  INFO 2416 --- [nio-8080-exec-1] c.example.designpattern.State.SafeFrame  : record...使用金库(白天)

2022-09-03 16:14:16.671  INFO 2416 --- [nio-8080-exec-1] c.example.designpattern.State.SafeFrame  : 现在时间是 10:00
2022-09-03 16:14:16.671  INFO 2416 --- [nio-8080-exec-1] c.example.designpattern.State.SafeFrame  : buttonPhone
2022-09-03 16:14:16.671  INFO 2416 --- [nio-8080-exec-1] c.example.designpattern.State.SafeFrame  : call!正常通话(白天)

2022-09-03 16:14:17.678  INFO 2416 --- [nio-8080-exec-1] c.example.designpattern.State.SafeFrame  : 现在时间是 11:00
2022-09-03 16:14:18.681  INFO 2416 --- [nio-8080-exec-1] c.example.designpattern.State.SafeFrame  : 现在时间是 12:00
2022-09-03 16:14:18.681  INFO 2416 --- [nio-8080-exec-1] c.example.designpattern.State.SafeFrame  : buttonUse
2022-09-03 16:14:18.681  INFO 2416 --- [nio-8080-exec-1] c.example.designpattern.State.SafeFrame  : record...使用金库(白天)

2022-09-03 16:14:18.681  INFO 2416 --- [nio-8080-exec-1] c.example.designpattern.State.SafeFrame  : buttonAlarm
2022-09-03 16:14:18.681  INFO 2416 --- [nio-8080-exec-1] c.example.designpattern.State.SafeFrame  : call!按下警铃(白天)

2022-09-03 16:14:19.686  INFO 2416 --- [nio-8080-exec-1] c.example.designpattern.State.SafeFrame  : 现在时间是 13:00
2022-09-03 16:14:20.692  INFO 2416 --- [nio-8080-exec-1] c.example.designpattern.State.SafeFrame  : 现在时间是 14:00
2022-09-03 16:14:21.693  INFO 2416 --- [nio-8080-exec-1] c.example.designpattern.State.SafeFrame  : 现在时间是 15:00
2022-09-03 16:14:21.693  INFO 2416 --- [nio-8080-exec-1] c.example.designpattern.State.SafeFrame  : buttonUse
2022-09-03 16:14:21.693  INFO 2416 --- [nio-8080-exec-1] c.example.designpattern.State.SafeFrame  : record...使用金库(白天)

2022-09-03 16:14:21.693  INFO 2416 --- [nio-8080-exec-1] c.example.designpattern.State.SafeFrame  : buttonPhone
2022-09-03 16:14:21.693  INFO 2416 --- [nio-8080-exec-1] c.example.designpattern.State.SafeFrame  : call!正常通话(白天)

2022-09-03 16:14:22.696  INFO 2416 --- [nio-8080-exec-1] c.example.designpattern.State.SafeFrame  : 现在时间是 16:00
2022-09-03 16:14:22.696  INFO 2416 --- [nio-8080-exec-1] c.example.designpattern.State.SafeFrame  : buttonAlarm
2022-09-03 16:14:22.696  INFO 2416 --- [nio-8080-exec-1] c.example.designpattern.State.SafeFrame  : call!按下警铃(白天)

2022-09-03 16:14:23.699  INFO 2416 --- [nio-8080-exec-1] c.example.designpattern.State.SafeFrame  : 现在时间是 17:00
2022-09-03 16:14:23.699  INFO 2416 --- [nio-8080-exec-1] c.example.designpattern.State.SafeFrame  : 从DayState [白天]状态变为了NightState [晚上]状态。
2022-09-03 16:14:24.703  INFO 2416 --- [nio-8080-exec-1] c.example.designpattern.State.SafeFrame  : 现在时间是 18:00
2022-09-03 16:14:24.703  INFO 2416 --- [nio-8080-exec-1] c.example.designpattern.State.SafeFrame  : buttonUse
2022-09-03 16:14:24.703  INFO 2416 --- [nio-8080-exec-1] c.example.designpattern.State.SafeFrame  : call!紧急:晚上使用金库

2022-09-03 16:14:25.707  INFO 2416 --- [nio-8080-exec-1] c.example.designpattern.State.SafeFrame  : 现在时间是 19:00
2022-09-03 16:14:26.713  INFO 2416 --- [nio-8080-exec-1] c.example.designpattern.State.SafeFrame  : 现在时间是 20:00
2022-09-03 16:14:26.713  INFO 2416 --- [nio-8080-exec-1] c.example.designpattern.State.SafeFrame  : buttonPhone
2022-09-03 16:14:26.713  INFO 2416 --- [nio-8080-exec-1] c.example.designpattern.State.SafeFrame  : record...晚上的通话录音

2022-09-03 16:14:26.713  INFO 2416 --- [nio-8080-exec-1] c.example.designpattern.State.SafeFrame  : buttonAlarm
2022-09-03 16:14:26.713  INFO 2416 --- [nio-8080-exec-1] c.example.designpattern.State.SafeFrame  : call!按下警铃(晚上)

2022-09-03 16:14:27.720  INFO 2416 --- [nio-8080-exec-1] c.example.designpattern.State.SafeFrame  : 现在时间是 21:00
2022-09-03 16:14:27.720  INFO 2416 --- [nio-8080-exec-1] c.example.designpattern.State.SafeFrame  : buttonUse
2022-09-03 16:14:27.720  INFO 2416 --- [nio-8080-exec-1] c.example.designpattern.State.SafeFrame  : call!紧急:晚上使用金库

2022-09-03 16:14:28.725  INFO 2416 --- [nio-8080-exec-1] c.example.designpattern.State.SafeFrame  : 现在时间是 22:00
2022-09-03 16:14:29.730  INFO 2416 --- [nio-8080-exec-1] c.example.designpattern.State.SafeFrame  : 现在时间是 23:00

策略模式

定义

定义一系列的算法,把他们一个个封装起来,并且使他们可以相互替换

优缺点

优点

  • 算法可以自由切换

    这是策略模式本身定义的,只要实现抽象策略,它就成为策略家族的一个成员,通过封 装角色对其进行封装,保证对外提供“可自由切换”的策略。

  • 避免使用多重条件判断

    如果没有策略模式,我们想想看会是什么样子?一个策略家族有5个策略算法,一会要 使用A策略,一会要使用B策略,怎么设计呢?使用多重的条件语句?多重条件语句不易维 护,而且出错的概率大大增强。使用策略模式后,可以由其他模块决定采用何种策略,策略 家族对外提供的访问接口就是封装类,简化了操作,同时避免了条件语句判断。

  • 扩展性良好

    这甚至都不用说是它的优点,因为它太明显了。在现有的系统中增加一个策略太容易 了,只要实现接口就可以了,其他都不用修改,类似于一个可反复拆卸的插件,这大大地符 合了OCP原则。

缺点

  • 策略类数量增多

    每一个策略都是一个类,复用的可能性很小,类数量增多。

  • 所有的策略类都需要对外暴露

    上层模块必须知道有哪些策略,然后才能决定使用哪一个策略,这与迪米特法则是相违 背的,我只是想使用了一个策略,我凭什么就要了解这个策略呢?那要你的封装类还有什么 意义?这是原装策略模式的一个缺点,幸运的是,我们可以使用其他模式来修正这个缺陷, 如工厂方法模式、代理模式或享元模式。

使用场景

  • 多个类只有在算法或行为上稍有不同的场景。
  • 算法需要自由切换的场景。
  • 需要屏蔽算法规则的场景。

最佳实践

策略模式是一个非常简单的模式。它在项目中使用得非常多,但它单独使用的地方就比 较少了,因为它有致命缺陷:所有的策略都需要暴露出去,这样才方便客户端决定使用哪一 个策略。例如,在例子中的赵云,实际上不知道使用哪个策略,他只知道拆第一个锦囊,而 不知道是BackDoor这个妙计。是的,诸葛亮已经在规定了在适当的场景下拆开指定的锦囊, 我们的策略模式只是实现了锦囊的管理,但是我们没有严格地定义“适当的场景”拆开“适当 的锦囊”,在实际项目中,我们一般通过工厂方法模式来实现策略类的声明,可以参考混编模式

角色

  • Strategy(策略):Strategy角色负责决定实现策略所必需的接口(API)。
  • ConcreteStrategy(具体的策略):ConcreteStrategy角色负责实现Strategy角色的接口(API),即负责实现具体的策略(战略、方向、方法和算法)。
  • Context(上下文):负责使用Strategy角色。Context角色保存了ConcreteStrategy角色的实例,并使用ConcreteStrategy角色去实现需求(总之,还是要调用Strategy角色的接口(API))。

image-20220903161715085

spring实现

下面我们来看一段使用了Strategy模式的示例程序。这段示例程序的功能是让电脑玩“猜拳”游戏。
我们考虑了两种猜拳的策略。第一种策略是“如果这局猜拳获胜,那么下一局也出一样的手势”( winningstrategy),这是一种稍微有些笨的策略;另外一种策略是“根据上一局的手势从概率上计算出下一局的手势”( ProbStrategy )。

定义实例,表示猜拳游戏中的手势:

public class Hand {

   public static final int HANDVALUE_GUU = 0;
   public static final int HANDVALUE_CHO = 1;
   public static final int HANDVALUE_PAA = 2;
    public static final Hand[] hand = {
       new Hand(HANDVALUE_GUU),
       new Hand(HANDVALUE_CHO),
       new Hand(HANDVALUE_PAA),
    };
    private static final String[] name ={
       "石头","剪刀","步",
    };
    private int handvalue;
   public Hand(int handvalue) {
      super();
      this.handvalue = handvalue;
   }
   public static Hand getHand(int handvalue){
      return hand[handvalue];
   }
   public boolean isStrongerThan(Hand h){
      return fight(h) == 1;
   }
   private int fight(Hand h) {
      // TODO Auto-generated method stub
      if(this==h){
         return 0;
      }else if((this.handvalue+1)%3==h.handvalue){
         return 1;
      }else{
         return -1;
      }
   }
   public String toString(){
      return name[handvalue];
   }
}

策略接口

表示猜拳游戏中策略的类

public interface Strategy {

   Hand nextHand();
   void study(boolean win);

   void setRandom(int seed);
}

具体策略

如果这局猜拳获胜,那么下一局也出一样的手势:

@Component
@StrategyName(StrategyEnum.WINNING_STRATEGY)
public class WinningStrategy implements Strategy {
   private Random random;
   private boolean won = false;
   private Hand preHand;

   public void setRandom(int seed) {
      random = new Random(seed);
   }

   @Override
   public Hand nextHand() {
      // TODO Auto-generated method stub
      if (!won) {
         preHand = Hand.getHand(random.nextInt(3));
      }
      return preHand;
   }

   @Override
   public void study(boolean win) {
      // TODO Auto-generated method stub
      won = win;
   }

}

根据上一局的手势从概率上计算出下一局的手势

@Component
@StrategyName(StrategyEnum.PROB_STRATEGY)
public class ProbStrategy implements Strategy {
    private Random random;
    private int prevHandValue = 0;
    private int currentHandValue = 0;
    private int[][] history = {
          {1,1,1,},
          {1,1,1,},
          {1,1,1,},
    };

    public void setRandom(int seed){
       random = new Random(seed);
    }
   @Override
   public Hand nextHand() {
      // TODO Auto-generated method stub
      int bet = random.nextInt(getSum(currentHandValue));
      int handvalue =  0;
      if(bet<history[currentHandValue][0]){
         handvalue = 0;
      }else if(bet<history[currentHandValue][0]+history[currentHandValue][1]){
         handvalue = 1;
      }else{
         handvalue = 2;
      }
      prevHandValue = currentHandValue;
      currentHandValue = handvalue;
      return Hand.getHand(handvalue);
   }

   private int getSum(int hv) {
      // TODO Auto-generated method stub
      int sum=0;
      for(int i=0;i<3;i++){
         sum+=history[hv][i];
      }
      return sum;
   }
   @Override
   public void study(boolean win) {
      // TODO Auto-generated method stub
        if(win){
           history[prevHandValue][currentHandValue]++;
        }else{
           history[prevHandValue][(currentHandValue+1)%3]++;
           history[prevHandValue][(currentHandValue+2)%3]++;
        }
   }

}

一种纯随机策略

@Component
@StrategyName(StrategyEnum.RANDOM_STRATEGY)
public class RandomStrategy implements Strategy {
   private Random random;
   private Hand hand;

   public void setRandom(int seed) {
      random = new Random(seed);
   }

   @Override
   public Hand nextHand() {
      // TODO Auto-generated method stub
      hand = Hand.getHand(random.nextInt(3));
      return hand;
   }

   @Override
   public void study(boolean win) {
   }

}

上下文

用来进行猜拳游戏的选手负责选用不同的策略进行出招

@Component
@Scope(BeanDefinition.SCOPE_PROTOTYPE)
public class Player {

   private String name;
   private Strategy strategy;
   private int wincount;
   private int losecount;
   private int gamecount;
   public void init(String name, Strategy strategy) {
      this.name = name;
      this.strategy = strategy;
   }
   public Hand nextHand(){
      return strategy.nextHand();
   }
   public void win(){
      strategy.study(true);
      wincount++;
      gamecount++;
   }
   public void lose(){
      strategy.study(false);
      losecount++;
      gamecount++;
   }
   public void even(){
      gamecount++;
   }
   @Override
   public String toString() {
      return "Player [name=" + name + ", strategy=" + strategy
            + ", wincount=" + wincount + ", losecount=" + losecount
            + ", gamecount=" + gamecount + "]";
   }
}
   

策略生成器

定义玩家生成方式

@Component
public abstract class StrategyGenerator {

   @Lookup
   protected abstract Player player();
   @Resource
   private List<Strategy> strategyList;

   private Map<StrategyEnum, Strategy> strategyMap = new HashMap<>();

   public Player genPlayer(String username, StrategyEnum strategyEnum, int seed){
      Player player = player();
      Strategy strategy = strategyMap.get(strategyEnum);
      strategy.setRandom(seed);
      player.init(username, strategy);
      return player;
   }

   @PostConstruct
   public void initMap(){
      for (Strategy strategy: strategyList){
         strategyMap.put(strategy.getClass().getAnnotation(StrategyName.class).value(), strategy);
      }
   }
}

测试

定义两个玩家,一个使用赢相同手势策略,一个使用随机手势策略

@RestController
@Slf4j
public class StrategyController {

   @Resource
   private StrategyGenerator strategyGenerator;

   @GetMapping("strategy")
   private String strategy(Integer seed1, Integer seed2){
      if (seed1 == null || seed2 == null) {
         log.info("Useage: java Main randomseed1 randomseed2");
         log.info("Example: java Main 314 15");
         return "ERROR";
      }
      Player player1 = strategyGenerator.genPlayer("Trao", StrategyEnum.WINNING_STRATEGY, seed1);
      Player player2 =strategyGenerator.genPlayer("Hana", StrategyEnum.RANDOM_STRATEGY, seed2);
      for (int i = 0; i < 10000; i++) {
         Hand nextHand1 = player1.nextHand();
         Hand nextHand2 = player2.nextHand();
         if (nextHand1.isStrongerThan(nextHand2)) {
            log.info("Winner:" + player1);
            player1.win();
            player2.lose();
         } else if (nextHand2.isStrongerThan(nextHand1)) {
            log.info("WInner:" + player2);
            player1.lose();
            player2.win();
         } else {
            log.info("Even....");
            player1.even();
            player2.even();
         }
      }
      log.info("Total result:");
      log.info(player1.toString());
      log.info(player2.toString());
      return "SUCCESS";
   }
}

测试输出结果为:

2022-09-03 17:43:24.739  INFO 17864 --- [nio-8080-exec-2] c.e.d.Strategy.StrategyController        : Even....
2022-09-03 17:43:24.739  INFO 17864 --- [nio-8080-exec-2] c.e.d.Strategy.StrategyController        : Even....
2022-09-03 17:43:24.739  INFO 17864 --- [nio-8080-exec-2] c.e.d.Strategy.StrategyController        : WInner:Player [name=Hana, strategy=com.example.designpattern.Strategy.RandomStrategy@632aa1a3, wincount=0, losecount=0, gamecount=2]
2022-09-03 17:43:24.739  INFO 17864 --- [nio-8080-exec-2] c.e.d.Strategy.StrategyController        : Winner:Player [name=Trao, strategy=com.example.designpattern.Strategy.WinningStrategy@20765ed5, wincount=0, losecount=1, gamecount=3]
2022-09-03 17:43:24.739  INFO 17864 --- [nio-8080-exec-2] c.e.d.Strategy.StrategyController        : WInner:Player [name=Hana, strategy=com.example.designpattern.Strategy.RandomStrategy@632aa1a3, wincount=1, losecount=1, gamecount=4]
2022-09-03 17:43:24.739  INFO 17864 --- [nio-8080-exec-2] c.e.d.Strategy.StrategyController        : WInner:Player [name=Hana, strategy=com.example.designpattern.Strategy.RandomStrategy@632aa1a3, wincount=2, losecount=1, gamecount=5]
2022-09-03 17:43:24.739  INFO 17864 --- [nio-8080-exec-2] c.e.d.Strategy.StrategyController        : Winner:Player [name=Trao, strategy=com.example.designpattern.Strategy.WinningStrategy@20765ed5, wincount=1, losecount=3, gamecount=6]
2022-09-03 17:43:24.739  INFO 17864 --- [nio-8080-exec-2] c.e.d.Strategy.StrategyController        : WInner:Player [name=Hana, strategy=com.example.designpattern.Strategy.RandomStrategy@632aa1a3, wincount=3, losecount=2, gamecount=7]
2022-09-03 17:43:24.739  INFO 17864 --- [nio-8080-exec-2] c.e.d.Strategy.StrategyController        : Winner:Player [name=Trao, strategy=com.example.designpattern.Strategy.WinningStrategy@20765ed5, wincount=2, losecount=4, gamecount=8]
2022-09-03 17:43:24.739  INFO 17864 --- [nio-8080-exec-2] c.e.d.Strategy.StrategyController        : WInner:Player [name=Hana, strategy=com.example.designpattern.Strategy.RandomStrategy@632aa1a3, wincount=4, losecount=3, gamecount=9]
2022-09-03 17:43:24.739  INFO 17864 --- [nio-8080-exec-2] c.e.d.Strategy.StrategyController        : Total result:
2022-09-03 17:43:24.739  INFO 17864 --- [nio-8080-exec-2] c.e.d.Strategy.StrategyController        : Player [name=Trao, strategy=com.example.designpattern.Strategy.WinningStrategy@20765ed5, wincount=3, losecount=5, gamecount=10]
2022-09-03 17:43:24.739  INFO 17864 --- [nio-8080-exec-2] c.e.d.Strategy.StrategyController        : Player [name=Hana, strategy=com.example.designpattern.Strategy.RandomStrategy@632aa1a3, wincount=5, losecount=3, gamecount=10]
0

评论区