欢迎访问shiker.tech

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

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

行为型设计模式实战【二】-迭代器、中介者、备忘录
(last modified May 5, 2023, 3:41 PM )
by
侧边栏壁纸
  • 累计撰写 176 篇文章
  • 累计创建 61 个标签
  • 累计收到 4 条评论

目 录CONTENT

文章目录

行为型设计模式实战【二】-迭代器、中介者、备忘录

橙序员
2022-08-28 / 0 评论 / 0 点赞 / 373 阅读 / 4,480 字 / 正在检测百度是否收录... 正在检测必应是否收录...
文章摘要(AI生成)

迭代器模式定义提供一种方法可以顺序访问集合中的各个元素,又不需要暴露该集合的内部表示角色lterator(迭代器):该角色负责定义按顺序逐个遍历元素的接口(API)。Concretelterator(具体的迭代器):该角色负责实现Iterator角色所定义的接口(API)。Aggregate(集合)

迭代器模式

定义

提供一种方法可以顺序访问集合中的各个元素,又不需要暴露该集合的内部表示

角色

  • lterator(迭代器):该角色负责定义按顺序逐个遍历元素的接口(API)。
  • Concretelterator(具体的迭代器):该角色负责实现Iterator角色所定义的接口(API)。
  • Aggregate(集合):该角色负责定义创建Iterator角色的接口(API)。这个接口(API)是一个方法,会创建出“按顺序访问保存在我内部元素的人”。
  • ConcreteAggregate(具体的集合):该角色负责实现Aggregate角色所定义的接口(API)。它会创建出具体的Iterator角色,即ConcreteIterator角色。

image-20220828150346765

应用场景

从JDK 1.2版本开始增加java.util.Iterator这个接口,并逐步把Iterator应用到各个聚集类 (Collection)中,我们来看JDK 1.5的API帮助文件,你会看到有一个叫java.util.Iterable的接 口,看看有多少个接口继承了它: BeanContext,BeanContextServices,BlockingQueue,Collection,List,Queue,Set,SortedSet, 再看看有它多少个实现类: AbstractCollection,AbstractList,AbstractQueue,AbstractSequentialList,AbstractSet,ArrayBlockingQueue,ArrayList,AttributeList,BeanContextServicesSupport,BeanContextSupport,ConcurrentLinkedQueue,CopyOnWriteArrayList,CopyOnWriteArraySet,DelayQueue,EnumSet,HashSet,JobStateReasons,LinkedBlockingQueue,LinkedHashSet,LinkedList,PriorityBlockingQueue,PriorityQueue,RoleList,RoleUnresolvedList,Stack,SynchronousQueue,TreeSet,Vector, 基本上我们经常使用的类都在这个表中了,也正是因为Java把迭代器模式已经融入到基本 API中了,我们才能如此轻松、便捷。

最佳实践

尽量不要自己写迭代器模式,使用Java提供的Iterator一 般就能满足需求

spring实现

实现一个程序,可以将书放到书架中,并将书的名字按顺序显示出来

迭代器

public interface Iterator {

   boolean hasNext();

   Object next();
}

具体迭代器

@Component
public class BookShelfIterator implements Iterator {
   private BookShelf bookShelf;
   private int index;

   public void initBookShelfIterator(BookShelf bookShelf) {
      this.bookShelf = bookShelf;
       this.index = 0;
   }

   @Override
   public boolean hasNext() {
      if (index < bookShelf.getLength()) {
         return true;
      } else {
         return false;
      }
   }

   @Override
   public Object next() {
      Book book = bookShelf.getBookAt(index);
      index++;
      return book;
   }

}

抽象集合

public interface Aggregate {
   Iterator getIterator();
}

具体集合

@Component
public class BookShelf implements Aggregate {

   private ArrayList<Book> books;
   private int last = 0;
   @Resource
   private BookShelfIterator bookShelfIterator;

   public void initBookShelf(int maxsize) {
      this.books = new ArrayList<Book>(maxsize);
   }
   public Book getBookAt(int index){
      return books.get(index);
   }
   public void appendBook(Book book){
      this.books.add(book);
      last++;
   }
   
   public int getLength(){
      return last;
   }
   /**
    * 创建迭代器
    * @return 书架迭代器
    */
   public Iterator getIterator(){
      bookShelfIterator.initBookShelfIterator(this);
      return bookShelfIterator;
   }
}

集合元素

@Component
@Data
@Scope(BeanDefinition.SCOPE_PROTOTYPE)
public class Book {
   private String name;
}

元素生成器

@Component
public abstract class BookGenerator {

   @Lookup
   protected abstract Book book();

   public Book genBooK(String name){
      Book book = book();
      book.setName(name);
      return book;
   }
}

测试

@RestController
public class IteratorController {

   @Resource
   private BookShelf bookShelf;
   @Resource
   private BookGenerator bookGenerator;

   @GetMapping("/iterator")
   public String iterator(){
      bookShelf.initBookShelf(4);
      bookShelf.appendBook(bookGenerator.genBooK("Around the world"));
      bookShelf.appendBook(bookGenerator.genBooK("hello world"));
      bookShelf.appendBook(bookGenerator.genBooK("hello kitty"));
      bookShelf.appendBook(bookGenerator.genBooK("little pig peqi"));
      Iterator it = bookShelf.getIterator();
      String result = "";
      while (it.hasNext()) {
         Book book = (Book) it.next();
         result += "书名:" + (book.getName()) +"\n";
      }
      return result;
   }
}

测试结果为:

http://localhost:8080/iterator

HTTP/1.1 200 
Content-Type: text/plain;charset=UTF-8
Content-Length: 93
Date: Sun, 28 Aug 2022 07:06:20 GMT
Keep-Alive: timeout=60
Connection: keep-alive

书名:Around the world
书名:hello world
书名:hello kitty
书名:little pig peqi

中介者模式

定义

用一个中介对象封装一系列的对象 交互,中介者使各对象不需要显示地相互作用,从而使其耦合松散,而且可以独立地改变它 们之间的交互。

优缺点

优点

中介者模式的优点就是减少类间的依赖,把原有的一对多的依赖变成了一对一的依赖, 同事类只依赖中介者,减少了依赖,当然同时也降低了类间的耦合。

缺点

中介者模式的缺点就是中介者会膨胀得很大,而且逻辑复杂,原本N个对象直接的相互依赖关系转换为中介者和同事类的依赖关系,同事类越多,中介者的逻辑就越复杂。

使用场景

中介者模式简单,但是简单不代表容易使用,很容易被误用。在面向对象的编程中,对 象和对象之间必然会有依赖关系,如果某个类和其他类没有任何相互依赖的关系,那这个类 就是一个“孤岛”,在项目中就没有存在的必要了!就像是某个人如果永远独立生活,与任何 人都没有关系,那这个人基本上就算是野人了——排除在人类这个定义之外。 类之间的依赖关系是必然存在的,一个类依赖多个类的情况也是存在的,存在即合理, 那是否可以说只要有多个依赖关系就考虑使用中介者模式呢?答案是否定的。中介者模式未 必能帮你把原本凌乱的逻辑整理得清清楚楚,而且中介者模式也是有缺点的,这个缺点在使 用不当时会被放大,比如原本就简单的几个对象依赖关系,如果为了使用模式而加入了中介 者,必然导致中介者的逻辑复杂化,因此中介者模式的使用需要“量力而行”!中介者模式适 用于多个对象之间紧密耦合的情况,紧密耦合的标准是:在类图中出现了蜘蛛网状结构。在 这种情况下一定要考虑使用中介者模式,这有利于把蜘蛛网梳理为星型结构,使原本复杂混 乱的关系变得清晰简单

大家都应该使用过Struts,MVC框架,其中的C(Controller)就是一个中介者,叫做前端 控制器(Front Controller),它的作用就是把M(Model,业务逻辑)和V(View,视图)隔离开, 协调M和V协同工作,把M运行的结果和V代表的视图融合成一个前端可以展示的页面,减少 M和V的依赖关系。MVC框架已经成为一个非常流行、成熟的开发框架,这也是中介者模式 的优点的一个体现。

最佳实践

中介者模式是一个非常好的封装模式,也是一个很容易被滥用的模式,一个对象依赖几 个对象是再正常不过的事情,但是纯理论家就会要求使用中介者模式来封装这种依赖关系, 这是非常危险的!使用中介模式就必然会带来中介者的膨胀问题,这在一个项目中是很不恰 当的。大家可以在如下的情况下尝试使用中介者模式:

  • N个对象之间产生了相互的依赖关系(N>2)。
  • 多个对象有依赖关系,但是依赖的行为尚不确定或者有发生改变的可能,在这种情况 下一般建议采用中介者模式,降低变更引起的风险扩散。
  • 产品开发。一个明显的例子就是MVC框架,把中介者模式应用到产品中,可以提升产 品的性能和扩展性,但是对于项目开发就未必,因为项目是以交付投产为目标,而产品则是 以稳定、高效、扩展为宗旨

角色

  • Mediator (仲裁者、中介者):Mediator角色负责定义与Colleague角色进行通信和做出决定的接口(API)。
  • ConcreteMediator(具体的仲裁者、中介者):ConcreteMediator角色负责实现Mediator 角色的接口(API),负责实际做出决定。
  • Colleague (同事):Colleague角色负责定义与Mediator角色进行通信的接口(API)
  • ConcreteColleague(具体的同事):ConcreteColleague角色负责实现Colleague角色的接口(API )。

image-20220828153149318

spring实现

我们通过以下程序来实现由中介者发送消息给同事,并由同事接受消息。

同事角色

定义和中介者角色进行通信的接口

public interface Colleague {

   void setMediator(Mediator mediator);

   String receiveMsg(String msg);

   void send(String msg);
}

具体同事

定义学生和老师来接收由中介者发送的消息

@Component
@Slf4j
public class StudentColleague implements Colleague {

   private Mediator mediator;

   @Override
   public void setMediator(Mediator mediator) {
      this.mediator = mediator;
   }

   public void send(String message) {
      this.mediator.sendChanged(message, this);
   }

   @Override
   public String receiveMsg(String msg) {
      log.info("学生收到消息:" + msg);
      return msg;
   }
}
@Component
@Slf4j
public class TeacherColleague implements Colleague {

   private Mediator mediator;

   @Override
   public void setMediator(Mediator mediator) {
      this.mediator = mediator;
   }

   @Override
   public String receiveMsg(String msg) {
      log.info("老师收到消息:" + msg);
      return msg;
   }

   public void send(String message) {
      mediator.sendChanged(message, this);
   }
}

中介者

定义与同事进行通信的接口

public abstract class Mediator {
   /**
    * 定义一个抽象的发送消息方法,得到同事对象和发送信息
    * @param message
    * @param colleague
    */
   public abstract String sendChanged(String message, Colleague colleague);
}

具体中介者

负责判断发给哪个同事

@Component
public class ConcreteMediator extends Mediator {
   @Setter
   private TeacherColleague teacherColleague;
   @Setter
   private StudentColleague studentColleague;

   @Override
   public String sendChanged(String message, Colleague colleague) {
      if (colleague == teacherColleague) {
         return teacherColleague.receiveMsg(message);
      } else {
         return studentColleague.receiveMsg(message);
      }
   }
}

测试

@RestController
public class MediatorController {

   @Resource
   private ConcreteMediator mediator;
   @Resource
   private TeacherColleague teacherColleague;
   @Resource
   private StudentColleague studentColleague;

   @GetMapping("/mediator")
   public String mediator(String item){
      teacherColleague.setMediator(mediator);
      studentColleague.setMediator(mediator);
      mediator.setTeacherColleague(teacherColleague);
      mediator.setStudentColleague(studentColleague);

      teacherColleague.send("早上好啊!");
      studentColleague.send("早安!");
      return mediator.toString();
   }
}

测试结果如下

2022-08-28 15:15:16.039  INFO 11304 --- [nio-8080-exec-4] c.e.d.Mediator.TeacherColleague          : 老师收到消息:早上好啊!
2022-08-28 15:15:16.040  INFO 11304 --- [nio-8080-exec-4] c.e.d.Mediator.StudentColleague          : 学生收到消息:早安!

备忘录模式

定义

在不破坏封装性的前提下,捕获一个对象的内部状 态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态

使用场景

  • 需要保存和恢复数据的相关状态场景。
  • 提供一个可回滚(rollback)的操作;比如Word中的CTRL+Z组合键,IE浏览器中的后 退按钮,文件管理器上的backspace键等。
  • 需要监控的副本场景中。例如要监控一个对象的属性,但是监控又不应该作为系统的 主业务来调用,它只是边缘应用,即使出现监控不准、错误报警也影响不大,因此一般的做 法是备份一个主线程中的对象,然后由分析程序来分析。
  • 数据库连接的事务管理就是用的备忘录模式

最佳实践

备忘录模式是我们设计上“月光宝盒”,可以让我们回到需要的年代;是程序数据的“后 悔药”,吃了它就可以返回上一个状态;是设计人员的定心丸,确保即使在最坏的情况下也 能获得最近的对象状态。如果大家看懂了的话,请各位在设计的时候就不要使用数据库的临 时表作为缓存备份数据了,虽然是一个简单的办法,但是它加大了数据库操作的频繁度,把 压力下放到数据库了,最好的解决办法就是使用备忘录模式。

角色

  • Originator(生成者):Originator角色会在保存自己的最新状态时生成Memento角色。当把以前保存的Memento角色传递给Originator角色时,它会将自己恢复至生成该Memento角色时的状态。在示例程序中,由Gamer类扮演此角色。

  • Memento (备忘录):Memento角色会将Originator 角色的内部信息整合在一起。在 Memento角色中虽然保存了Originator 角色的信息,但它不会向外部公开这些信息。

    Memento角色有以下两种接口(API )。

    1. wide interface—宽接口(API):Memento角色提供的“宽接口(API)”是指所有用于获取恢复对象状态信息的方法的集合。由于宽接口(API)会暴露所有Memento角色的内部信息,因此能够使用宽接口(API)的只有Originator角色。
    2. narrow interface—窄接口(API):Memento角色为外部的Caretaker 角色提供了“窄接口(API)”。可以通过窄接口(API )获取的Memento角色的内部信息非常有限,因此可以有效地防止信息泄露。

    通过对外提供以上两种接口(API),可以有效地防止对象的封装性被破坏。

    Originator角色和 Memento角色之间有着非常紧密的联系。

  • Caretaker(负责人) :当Caretaker 角色想要保存当前的Originator角色的状态时,会通知Originator角色。Originator角色在接收到通知后会生成Memento角色的实例并将其返回给Caretaker角色。由于以后可能会用Memento实例来将Originator恢复至原来的状态,因此Caretaker角色会一直保存Memento实例。

    不过,Caretaker角色只能使用Memento角色两种接口(API)中的窄接口(API),也就是说它无法访问Memento角色内部的所有信息。它只是将Originator角色生成的Memento角色当作一个黑盒子保存起来。

    虽然Originator角色和 Memento角色之间是强关联关系,但Caretaker角色和Memento角色之间是弱关联关系。Memento角色对Caretaker角色隐藏了自身的内部信息。

image-1683272466792

spring实现

下面我们来看一段使用了Memento模式的示例程序。这是一个收集水果和获取金钱数的掷骰子游戏,游戏规则很简单,具体如下:

  • 游戏是自动进行的
  • 游戏的主人公通过掷骰子来决定下一个状态
  • 当骰子点数为1的时候,主人公的金钱会增加·
  • 当骰子点数为2的时候,主人公的金钱会减少·
  • 当骰子点数为6的时候,主人公会得到水果·
  • 主人公没有钱时游戏就会结束

在程序中,如果金钱增加,为了方便将来恢复状态,我们会生成Memento类的实例,将现在的状态保存起来。所保存的数据为当前持有的金钱和水果。如果不断掷出了会导致金钱减少的点数,为了防止金钱变为О而结束游戏,我们会使用Memento的实例将游戏恢复至之前的状态。

备忘录

将生成的内部信息整合到一起,用来表示游戏状态

@Component
public class Memento {

   int money;
   ArrayList<String> fruits;
   public int getMoney() {
      return money;
   }
   
   public void initMemento(int money) {
      this.money = money;
      this.fruits = new ArrayList<String>();
   }

   void addFruit(String fruit){
      fruits.add(fruit);
   }
   
   public List<String> getFruits() {
      return (List<String>)fruits.clone();
   }
   
}

备忘录生成器

@Component
public abstract class MementoGenerator {

   @Lookup
   public abstract Memento memento();

   public Memento genMemento(int money){
      Memento memento = memento();
      memento.initMemento(money);
      return memento;
   }
}

生成者

用来表示游戏主人公,会生成对应备忘录的实例

@Component
@Slf4j
public class Gamer {

   @Getter
   @Setter
   private int money;
   private List<String> fruits = new ArrayList<String>();
   private Random random = new Random();
   @Resource
   private MementoGenerator mementoGenerator;

   private static String[] fruitsname = { "苹果", "葡萄", "香蕉", "橘子", };
   
   public void bet(){
      int dice = random.nextInt(6)+1;
      if(dice==1){
         money+=100;
         log.info("所持金钱增加了");
      }else if(dice==2){
         money/=2;
         log.info("所持金钱减半了");
      }else if(dice==6){
         String f = getFruit();
         log.info("获得了水果("+f+")。");
         fruits.add(f);
      }else{
         log.info("什么都没发生");
      }
   }

   private String getFruit() {
      // TODO Auto-generated method stub
      String prefix = "";
      if(random.nextBoolean()){
         prefix="好吃的";
      }
      return prefix+fruitsname[random.nextInt(fruitsname.length)];
   }
   public Memento createMemento(){
      Memento m = mementoGenerator.genMemento(money);
      Iterator<String> it = fruits.iterator();
      while(it.hasNext()){
         String f = it.next();
         if(f.startsWith("好吃的")){
            m.addFruit(f);
         }
      }
      return m;
   }
   public void restoreMemento(Memento memento){
      this.money = memento.money;
      this.fruits = memento.getFruits();
   }

   @Override
   public String toString() {
      return "Gamer [money=" + money + ", fruits=" + fruits + ", random="
            + random + "]";
   }
   
}

测试

@RestController
@Slf4j
public class MementoController {

   @Resource
   private Gamer gamer;

   @GetMapping("/memento")
   public String memento(){
      gamer.setMoney(1000);
      Memento memento = gamer.createMemento();
      for(int i=0;i<100;i++){
         log.info("==== "+i);
         log.info("当前状态:"+gamer);
         gamer.bet();
         log.info("所持金钱为"+gamer.getMoney()+"元");
         if(gamer.getMoney()>memento.getMoney()){
            log.info("(所持金钱增加了许多,因此保持游戏当前的状态)");
            memento= gamer.createMemento();
         }else if(gamer.getMoney()<memento.getMoney()/2){
            log.info("(所持金钱减少了许多,因此将游戏恢复至以前的状态)");
            gamer.restoreMemento(memento);
         }
      }
      return gamer.toString();
   }
}

输出结果为:

2022-08-28 15:29:51.566  INFO 11304 --- [nio-8080-exec-6] c.e.d.Memento.MementoController          : ==== 0
2022-08-28 15:29:51.566  INFO 11304 --- [nio-8080-exec-6] c.e.d.Memento.MementoController          : 当前状态:Gamer [money=1000, fruits=[], random=java.util.Random@4d391309]
2022-08-28 15:29:51.566  INFO 11304 --- [nio-8080-exec-6] c.e.designpattern.Memento.game.Gamer     : 什么都没发生
...

...

...
2022-08-28 15:29:51.573  INFO 11304 --- [nio-8080-exec-6] c.e.designpattern.Memento.game.Gamer     : 所持金钱减半了
2022-08-28 15:29:51.573  INFO 11304 --- [nio-8080-exec-6] c.e.d.Memento.MementoController          : 所持金钱为900元
2022-08-28 15:29:51.573  INFO 11304 --- [nio-8080-exec-6] c.e.d.Memento.MementoController          : ==== 99
2022-08-28 15:29:51.573  INFO 11304 --- [nio-8080-exec-6] c.e.d.Memento.MementoController          : 当前状态:Gamer [money=900, fruits=[好吃的苹果], random=java.util.Random@4d391309]
2022-08-28 15:29:51.573  INFO 11304 --- [nio-8080-exec-6] c.e.designpattern.Memento.game.Gamer     : 什么都没发生
2022-08-28 15:29:51.573  INFO 11304 --- [nio-8080-exec-6] c.e.d.Memento.MementoController          : 所持金钱为900元
0

评论区