你不知道的观察者模式和发布订阅模式

154 阅读9分钟

引言

许多人会觉得观察者模式与发布订阅模式是没有区别的。笔者曾经也常常混淆它们之间的不同。最后总结出了,观察者模式和发布订阅模式之间的差异在于观察者和被观察者之间是之间有关联的,而发布订阅模式是观察者和被观察者不存在相互知道的存在,而是由另外一个中介,间接联系的。换句话说,观察者模式是二者之间的联系,而发布订阅模式,是超过两者之间的联系。本文将以二次元风格,展示两种模式不同之处。

观察者模式

现在有一大群冒险者,为了赚钱佣金,等待雇主发布任务,只有被雇主(被观察者,发布者)承认有资格的冒险者们(观察者),才能有接收任务的资本。我们可以发现,雇主和冒险者是一对多的关系,并且双方都知道是谁,(雇主知道有资格的冒险者们,冒险者知道雇主)。雇主一旦发布任务,就会通知有资格的冒险者,冒险者将根据雇主发布的任务,进行判断是否接收该任务。

观察者模式.png

代码

创建被观察者(雇主)

首先创建一个被观察者类,里面有待发布的所有任务(tasks,tasks是对象,因为task有任务的类型,以及类型里面的任务),以及有资格的冒险者(observersList)

// 被观察者
class Publish {
  tasks;
  observersList;
  constructor() {
    this.tasks = {};
    this.observersList = [];
  }
}

为所有任务添加任务类型,以及该类型下的一些任务

接下来我们就需要添加所有任务的类型,以及该类型下的一些任务

   //type是任务类型,fun是任务
  addTask([type, fun]) {
  // 首先判断types这个对象是否有这个type类型,没有的话就添加这个属性,并且赋予初值[],用于存储该类型的所有任务
    !Object.keys(this.tasks).includes(type) ? (this.tasks[type] = []) : "";
  //添加该类型的任务
    this.tasks[type].push(fun);
  }

添加有资格的冒险者们

既然有了任务,那就肯定需要招募冒险者

  addObserver(observer) {
    this.observersList.push(observer);
  }

发布任务

//一旦雇主调用了这个方法,就会通知冒险者们,冒险者自行判断,要不要接收此任务!
publishAddTask() {
    for (var type in this.tasks) {
      if (Object.keys(this.tasks).includes(type)) {
        this.tasks[type].forEach((toDoItem) => {
          console.log(
            `Alert adventurers! We publsh ${type} Task that is ${toDoItem()}`
          );
          this.observersList.forEach((observer) => {
            observer.showKnowNewPublishTask(toDoItem());
          });
        });
      }
    }
  }

雇主删除任务

雇主可以自己删除任务

  deleteTask([type, fun]) {
    this.tasks[type].forEach((deLeteItem, index) => {
      deLeteItem == fun ? this.tasks[type].splice(index, 1) : "";
      console.log(`delete ${fun()} is successful`);
    });
  }

发布删除的任务,通知冒险者们

假如冒险者们已经接收了待删除的任务,那么雇主删除了这个任务,自然就要通知冒险者们

  publishDeleteTask([type, fun]) {
    console.log(
      `Alert adventurers! We delete ${type}  Task that is ${fun()}`
    );
    this.observersList.forEach((observer) => {
      observer.showKnowDeleteTask(fun());
    });
  }
}

雇主完整的代码如下

class Publish {
  tasks;
  observersList;
  constructor() {
    this.tasks = {};
    this.observersList = [];
  }
  addObserver(observer) {
    this.observersList.push(observer);
  }
  addTask([type, fun]) {
    !Object.keys(this.tasks).includes(type) ? (this.tasks[type] = []) : "";
    this.tasks[type].push(fun);
  }
  publishAddTask() {
    for (var type in this.tasks) {
      if (Object.keys(this.tasks).includes(type)) {
        this.tasks[type].forEach((toDoItem) => {
          console.log(
            `Alert adventurers! We publsh ${type} Task that is ${toDoItem()}`
          );
          this.observersList.forEach((observer) => {
            observer.showKnowNewPublishTask(toDoItem());
          });
        });
      }
    }
  }
  deleteTask([type, fun]) {
    this.tasks[type].forEach((deLeteItem, index) => {
      deLeteItem == fun ? this.tasks[type].splice(index, 1) : "";
      console.log(`delete ${fun()} is successful`);
    });
  }
  publishDeleteTask([type, fun]) {
    console.log(
      `Alert adventurers! We delete ${type}  Task that is ${fun()}`
    );
    this.observersList.forEach((observer) => {
      observer.showKnowDeleteTask(fun());
    });
  }
}

既然有了被观察者(雇主),那么自然就需要观察者(冒险者)这个类了

创建观察者

创建冒险者这个类

  class Observer {
    adventurer;
    constructor(adventurer) {
      this.adventurer = adventurer;
    }
  }

显示知道雇主发布的任务

   showKnowNewPublishTask(task = undefined) {
    console.log(`${this.adventurer} knows new published ${task} task`);
  }

显示知道雇主删除的任务

  showKnowDeleteTask(task = undefined) {
    console.log(`${this.adventurer} knows new deleted ${task} task`);
  }

冒险者完整代码

class Observer {
  adventurer;
  constructor(adventurer) {
    this.adventurer = adventurer;
  }
  showKnowNewPublishTask(task = undefined) {
    console.log(`${this.adventurer} knows new published ${task} task`);
  }
  showKnowDeleteTask(task = undefined) {
    console.log(`${this.adventurer} knows new deleted ${task} task`);
  }
}

创建雇主和冒险者实例,以及添加冒险者和任务等

const publish = new Publish();
const adventurer1 = new Observer("欧力.金");
const adventurer2 = new Observer("米莲.辛德拉");
const adventurer3 = new Observer("雪莱.克莱尔");
const adventurer4 = new Observer("彼得.帕卡");
//将雇主认为有资格的冒险者们添加到里面,雇主认为冒险者彼得.帕卡没有资格,就不将他添加到其中。雇主发布的任务,自然冒险者彼得.帕卡不知道
const adventurersList = [adventurer1, adventurer2, adventurer3];
adventurersList.forEach((adventurer) => {
  publish.addObserver(adventurer);
});
//创建任务
const fun1 = () => {
  return "任务描述:击杀10只史莱姆";
};
const fun2 = () => {
  return "任务描述:击杀10只哥布林";
};
const fun3 = () => {
  return "任务描述:采集多种回复药草";
};
const fun4 = () => {
  return "任务描述:寻找迷失的小狐狸";
};
const fun5 = () => {
  return "任务描述:最近城内盗贼猖獗,请找出盗贼,并且歼灭他们";
};
//添加任务
const arrList = [
  ["击杀魔物", fun1],
  ["击杀魔物", fun2],
  ["采集药草", fun3],
  ["寻物启事", fun4],
  ["治安", fun5],
];
arrList.forEach((item) => {
  publish.addTask(item);
});

雇主发布任务

publish.publishAddTask();
//输出如下
/*
Alert adventurers! We publsh 击杀魔物 Task that is 任务描述:击杀10只史莱姆
欧力.金 knows new published 任务描述:击杀10只史莱姆 task
米莲.辛德拉 knows new published 任务描述:击杀10只史莱姆 task
雪莱.克莱尔 knows new published 任务描述:击杀10只史莱姆 task
Alert adventurers! We publsh 击杀魔物 Task that is 任务描述:击杀10只哥布林
欧力.金 knows new published 任务描述:击杀10只哥布林 task
米莲.辛德拉 knows new published 任务描述:击杀10只哥布林 task
雪莱.克莱尔 knows new published 任务描述:击杀10只哥布林 task
Alert adventurers! We publsh 采集药草 Task that is 任务描述:采集多种回复药草
欧力.金 knows new published 任务描述:采集多种回复药草 task
米莲.辛德拉 knows new published 任务描述:采集多种回复药草 task
雪莱.克莱尔 knows new published 任务描述:采集多种回复药草 task
Alert adventurers! We publsh 寻物启事 Task that is 任务描述:寻找迷失的小狐狸
欧力.金 knows new published 任务描述:寻找迷失的小狐狸 task
米莲.辛德拉 knows new published 任务描述:寻找迷失的小狐狸 task
雪莱.克莱尔 knows new published 任务描述:寻找迷失的小狐狸 task
Alert adventurers! We publsh 治安 Task that is 任务描述:最近城内盗贼猖獗,请找出盗贼,并且歼灭他们
欧力.金 knows new published 任务描述:最近城内盗贼猖獗,请找出盗贼,并且歼灭他们 task
米莲.辛德拉 knows new published 任务描述:最近城内盗贼猖獗,请找出盗贼,并且歼灭他们 task
雪莱.克莱尔 knows new published 任务描述:最近城内盗贼猖獗,请找出盗贼,并且歼灭他们 task
*/

雇主删除任务

publish.publishAddTask();
//输出如下
//delete 任务描述:击杀10只史莱姆 is successful

雇主发布删除的任务,通知冒险者们

publish.publishDeleteTask([
  "击杀魔物",
  () => {
    return "任务描述:击杀10只史莱姆";
  },
]);
//输出如下
/*
Alert adventurers! We delete 击杀魔物  Task that is 任务描述:击杀10只史莱姆
欧力.金 knows new deleted 任务描述:击杀10只史莱姆 task
米莲.辛德拉 knows new deleted 任务描述:击杀10只史莱姆 task
雪莱.克莱尔 knows new deleted 任务描述:击杀10只史莱姆 task
*/

发布订阅模式

我们通过观察者模式可以看出来,雇主和冒险者息息相关,紧密联系。而很多情况下,雇主和冒险者并不想要知道彼此的存在。一方面,雇主面对这么多冒险者,或多或少会精力疲惫;另一方面,冒险者并不关心雇主是谁,只是想要赚取佣金,这时候,我们就需要中介,冒险者公会这个平台就自然的出现了。平台需要处理好雇主发布的任务,以及应对冒险者。我们可以明确的知道,冒险者,雇主,冒险者公会是三方面的关系。通过冒险者公会这个平台,雇主和冒险者可以不关注彼此,解决了双方的不便之处。

发布订阅模式.png

创建雇主类以及待发布的方法

观察代码,我们可以由观察者模式可知道其中的意思,这里就不详细讲解了。

class Publisher {
  //发布人
  publisher;
  //任务类型
  type;
  //发布的任务
  fun;
  //任务队列
  tesks;
  constructor(publisher) {
     // 发布者的名字
    this.publisher = publisher;
    // 创建所有任务
    this.tesks = {};
  }
  //添加任务类型,以及任务
  publish([type, fun]) {
    !Object.keys(this.tesks).includes(type) ? (this.tesks[type] = []) : "";
    this.tesks[type].push(fun);
  }
}

创建冒险者这个类,以及相关方法

class Observer {
  observer;
  constructor(observer) {
    this.observer = observer;
  }
  toDo(type, fun) {
    switch (type) {
      case "击杀魔物":
        console.log(`${this.observer}接收${type}任务,确认委托任务内容${fun()}`);
        break;
      case "采集药草":
        console.log(`${this.observer}放弃${type}任务`);
        break;
      case "维持治安":
        console.log(`${this.observer}接收${type}任务,确认委托任务内容${fun()}`);
        break;
      default:
        return;
    }
  }
}

创建冒险者公会这个类

class Platform {
   //雇主名单
  publishersList;
  //观察者名单
  observersList;
  constructor() {
    this.publishersList = [];
    this.observersList = [];
  }
}

添加雇主

//没有这个雇主则添加,否者不做处理
  addPublisher(publisher) {
    !this.publishersList.includes(publisher)
      ? this.publishersList.push(publisher)
      : "";
  }

添加冒险者

//没有这个冒险者则添加,否者不做处理
  addObserver(observer) {
    !this.observersList.includes(observer)
      ? this.observersList.push(observer)
      : "";
  }

冒险者公会发布任务

  publish() {
    this.publishersList.forEach((publish) => {
      Object.keys(publish.tesks).forEach((type) => {
        publish.tesks[type].forEach((toDoItem) => {
         console.log(
            `冒险者共会代转发任务,委托人${
              publish.publisher
            },发布的任务类型是${type},任务内容是 ${toDoItem()}`
          );
          this.observersList.forEach((observer) => {
            observer.toDo(type, toDoItem);
          });
        });
      });
    });
  }

冒险者公会完整代码

class Platform {
  publishersList;
  observersList;
  constructor() {
    this.publishersList = [];
    this.observersList = [];
  }
  addPublisher(publisher) {
    !this.publishersList.includes(publisher)
      ? this.publishersList.push(publisher)
      : "";
  }
  addObserver(observer) {
    !this.observersList.includes(observer)
      ? this.observersList.push(observer)
      : "";
  }
  publish() {
    this.publishersList.forEach((publish) => {
      Object.keys(publish.tesks).forEach((type) => {
        publish.tesks[type].forEach((toDoItem) => {
          console.log(
            `委托人${
              publish.publisher
            },发布的任务类型是${type},任务内容是 ${toDoItem()}`
          );
          this.observersList.forEach((observer) => {
            observer.toDo(type, toDoItem);
          });
        });
      });
    });
  }
}

创建冒险者,发布者,冒险者公会实例

//创建雇主欧力.金和莉莉娅.亚美
const publish1 = new Publisher("欧力.金");
const publish2 = new Publisher("莉莉娅.亚美");
//创建冒险者米莲.辛德拉
const observer1 = new Observer("米莲.辛德拉");
//创建冒险者雪莱.克莱尔
const observer2 = new Observer("雪莱.克莱尔");
//创建冒险者公会
const platform = new Platform();
//雇主欧力.金发布任务
publish1.publish(["击杀魔物", () => "击杀十只史莱姆!"]);
publish1.publish(["维持治安", () => "逮捕强盗!"]);
publish1.publish(["采集药草", () => "采集回复药草!"]);
//添加雇主欧力.金
platform.addPublisher(publish1);
//雇主莉莉娅.亚美发布任务
publish2.publish(["击杀魔物", () => "击杀十只地龙"]);
//虽然创建了雇主莉莉娅.亚美,并且雇主莉莉娅.亚美,但是冒险者公会并没有添加到雇主队列,若是想要添加,请将下列注释取消
//添加雇主莉莉娅.亚美,platform.addPublisher(publish2);
//添加冒险者米莲.辛德拉和雪莱.克莱尔
platform.addObserver(observer1);
platform.addObserver(observer2);
//冒险者公会发布任务
platform.publish();
//输出如下
/*
冒险者共会代转发任务,委托人欧力.金,发布的任务类型是击杀魔物,任务内容是 击杀十只史莱姆!
米莲.辛德拉接收击杀魔物任务,确认委托任务内容击杀十只史莱姆!
雪莱.克莱尔接收击杀魔物任务,确认委托任务内容击杀十只史莱姆!
冒险者共会代转发任务,委托人欧力.金,发布的任务类型是维持治安,任务内容是 逮捕强盗!
米莲.辛德拉接收维持治安任务,确认委托任务内容逮捕强盗!
雪莱.克莱尔接收维持治安任务,确认委托任务内容逮捕强盗!
冒险者共会代转发任务,委托人欧力.金,发布的任务类型是采集药草,任务内容是 采集回复药草!
米莲.辛德拉放弃采集药草任务
雪莱.克莱尔放弃采集药草任务
*/

结束语

本文以偏二次元风格讲解了观察者模式和发布订阅模式,感谢大家观看,希望能有所收获

cartoon25.jpg