观察者模式-单例模式-Promise 实现TodoList

59 篇文章 1 订阅
40 篇文章 1 订阅
本文介绍了单例模式的概念、作用和实现方式,并通过数据库连接类的例子进行了说明。同时,文章探讨了单例模式的优缺点及其在多线程环境下的注意事项。接着,文章转向观察者模式,以TodoList为例展示了如何使用Promise实现事件处理。TodoList的实现包括添加、删除和切换任务的操作,通过事件队列确保操作顺序。最后,给出了完整的TodoList应用代码,包括TodoEvent和TodoDom类。
摘要由CSDN通过智能技术生成

观察者模式-单例模式-Promise 实现TodoList

在这里插入图片描述

单例模式

单例是一种创建型设计模式, 让你能够保证一个类只有一个实例, 并提供一个访问该实例的全局节点。
在这里插入图片描述

问题

单例模式同时解决了两个问题, 所以违反了_单一职责原则_:

  1. 保证一个类只有一个实例。 为什么会有人想要控制一个类所拥有的实例数量? 最常见的原因是控制某些共享资源 (例如数据库或文件) 的访问权限。

    它的运作方式是这样的: 如果你创建了一个对象, 同时过一会儿后你决定再创建一个新对象, 此时你会获得之前已创建的对象, 而不是一个新对象。

    注意, 普通构造函数无法实现上述行为, 因为构造函数的设计决定了它必须总是返回一个新对象。

一个对象的全局访问节点

客户端甚至可能没有意识到它们一直都在使用同一个对象。

  1. 为该实例提供一个全局访问节点。 还记得你 (好吧, 其实是我自己) 用过的那些存储重要对象的全局变量吗? 它们在使用上十分方便, 但同时也非常不安全, 因为任何代码都有可能覆盖掉那些变量的内容, 从而引发程序崩溃。

    和全局变量一样, 单例模式也允许在程序的任何地方访问特定对象。 但是它可以保护该实例不被其他代码覆盖。

    还有一点: 你不会希望解决同一个问题的代码分散在程序各处的。 因此更好的方式是将其放在同一个类中, 特别是当其他代码已经依赖这个类时更应该如此。

如今, 单例模式已经变得非常流行, 以至于人们会将只解决上文描述中任意一个问题的东西称为单例

解决方案

所有单例的实现都包含以下两个相同的步骤:

  • 将默认构造函数设为私有, 防止其他对象使用单例类的 new运算符。
  • 新建一个静态构建方法作为构造函数。 该函数会 “偷偷” 调用私有构造函数来创建对象, 并将其保存在一个静态成员变量中。 此后所有对于该函数的调用都将返回这一缓存对象。

如果你的代码能够访问单例类, 那它就能调用单例类的静态方法。 无论何时调用该方法, 它总是会返回相同的对象。

真实世界类比

政府是单例模式的一个很好的示例。 一个国家只有一个官方政府。 不管组成政府的每个人的身份是什么, “某政府” 这一称谓总是鉴别那些掌权者的全局访问节点。

单例模式结构

在这里插入图片描述

  1. 单例 (Singleton) 类声明了一个名为 get­Instance获取实例的静态方法来返回其所属类的一个相同实例。

    单例的构造函数必须对客户端 (Client) 代码隐藏。 调用 获取实例方法必须是获取单例对象的唯一方式。

伪代码

在本例中, 数据库连接类即是一个单例

该类不提供公有构造函数, 因此获取该对象的唯一方式是调用 获取实例方法。 该方法将缓存首次生成的对象, 并为所有后续调用返回该对象。

// 数据库类会对`getInstance(获取实例)`方法进行定义以让客户端在程序各处
// 都能访问相同的数据库连接实例。
class Database is
    // 保存单例实例的成员变量必须被声明为静态类型。
    private static field instance: Database

    // 单例的构造函数必须永远是私有类型,以防止使用`new`运算符直接调用构
    // 造方法。
    private constructor Database() is
        // 部分初始化代码(例如到数据库服务器的实际连接)。
        // ...

    // 用于控制对单例实例的访问权限的静态方法。
    public static method getInstance() is
        if (Database.instance == null) then
            acquireThreadLock() and then
                // 确保在该线程等待解锁时,其他线程没有初始化该实例。
                if (Database.instance == null) then
                    Database.instance = new Database()
        return Database.instance

    // 最后,任何单例都必须定义一些可在其实例上执行的业务逻辑。
    public method query(sql) is
        // 比如应用的所有数据库查询请求都需要通过该方法进行。因此,你可以
        // 在这里添加限流或缓冲逻辑。
        // ...

class Application is
    method main() is
        Database foo = Database.getInstance()
        foo.query("SELECT ...")
        // ...
        Database bar = Database.getInstance()
        bar.query("SELECT ...")
        // 变量 `bar` 和 `foo` 中将包含同一个对象。

单例模式适合应用场景

如果程序中的某个类对于所有客户端只有一个可用的实例, 可以使用单例模式。

单例模式禁止通过除特殊构建方法以外的任何方式来创建自身类的对象。 该方法可以创建一个新对象, 但如果该对象已经被创建, 则返回已有的对象。

如果你需要更加严格地控制全局变量, 可以使用单例模式。

单例模式与全局变量不同, 它保证类只存在一个实例。 除了单例类自己以外, 无法通过任何方式替换缓存的实例。

请注意, 你可以随时调整限制并设定生成单例实例的数量, 只需修改 获取实例方法, 即 getInstance 中的代码即可实现。

实现方式

  1. 在类中添加一个私有静态成员变量用于保存单例实例。
  2. 声明一个公有静态构建方法用于获取单例实例。
  3. 在静态方法中实现"延迟初始化"。 该方法会在首次被调用时创建一个新对象, 并将其存储在静态成员变量中。 此后该方法每次被调用时都返回该实例。
  4. 将类的构造函数设为私有。 类的静态方法仍能调用构造函数, 但是其他对象不能调用。
  5. 检查客户端代码, 将对单例的构造函数的调用替换为对其静态构建方法的调用。

单例模式优缺点

  • 你可以保证一个类只有一个实例。

  • 你获得了一个指向该实例的全局访问节点。

  • 仅在首次请求单例对象时对其进行初始化。

  • 违反了_单一职责原则_。 该模式同时解决了两个问题。

  • 单例模式可能掩盖不良设计, 比如程序各组件之间相互了解过多等。

  • 该模式在多线程环境下需要进行特殊处理, 避免多个线程多次创建单例对象。

  • 单例的客户端代码单元测试可能会比较困难, 因为许多测试框架以基于继承的方式创建模拟对象。 由于单例类的构造函数是私有的, 而且绝大部分语言无法重写静态方法, 所以你需要想出仔细考虑模拟单例的方法。 要么干脆不编写测试代码, 或者不使用单例模式。

与其他模式的关系

  • 外观模式类通常可以转换为单例模式类, 因为在大部分情况下一个外观对象就足够了。
  • 如果你能将对象的所有共享状态简化为一个享元对象, 那么享元模式就和单例类似了。 但这两个模式有两个根本性的不同。
  1. 只会有一个单例实体, 但是享元类可以有多个实体, 各实体的内在状态也可以不同。
  2. 单例对象可以是可变的。 享元对象是不可变的。
    -> 抽象工厂模式生成器模式原型模式都可以用单例来实现。
    传统的单例模式可以用来解决所有代码必须写到 class 中的问题:
class Singleton {
  private static instance: Singleton;
  private constructor() {
    // ..
  }

  public static getInstance() {
    if (!Singleton.instance) {
      Singleton.instance = new Singleton();
    }

    return Singleton.instance;
  }

  someMethod() {}
}

let someThing = new Singleton(); // Error: constructor of 'singleton' is private

let instacne = Singleton.getInstance(); // do some thing with the instance

然而,如果你不想延迟初始化,你可以使用 namespace 替代:

namespace Singleton {
  // .. 其他初始化的代码

  export function someMethod() {}
}

// 使用
Singleton.someMethod();

对大部分使用者来说,namespace 可以用模块来替代。

// someFile.ts
// ... any one time initialization goes here ...
export function someMethod() {}

// Usage
import { someMethod } from './someFile';

观察者模式

观察者模式

在这里插入图片描述
在这里插入图片描述


DOM操作   数据操作  ->  事件出来函数的绑定

DOM操作   数据操作 -> 之间没有任何联系

TodoList ->

add -> addTodo   addItem

function addTodo () {
  操作数据
  addItem(todo);
}

addTodo -> todo -> addItem(todo) -> 操作DOM

function test (todo) {
  Promise -> addTodo(todo);  resolve(todo);  then todo -> addItem
  addItem(todo);
}
## 将事件添加到 事件队列中,循环执行

add     addTodo addItem
remove  removeTodo removeItem
toggle  toggleTodo  toggleItem

[addTodo, addItem, setLocalStorage]
[removeTodo, removeItem]
[toggleTodo, toggleItem]

## 通过Promise执行函数
Promise.then -->

源代码

index.html

<!--
 * @Author: your name
 * @Date: 2021-02-20 20:01:26
 * @LastEditTime: 2021-02-21 16:32:19
 * @LastEditors: Please set LastEditors
 * @Description: In User Settings Edit
 * @FilePath: \observer-ts\index.html
-->
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <link
      rel="stylesheet"
      href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.0/dist/css/bootstrap.min.css"
      integrity="sha384-B0vP5xmATw1+K9KRQjQERJvTumQW0nPEzvF6L/Z6nronJ3oUOFUFpCjEUQouq2+l"
      crossorigin="anonymous"
    />
    <title>observer-ts</title>
    <style>
      .todo {
        margin-top: 40px;
      }
      .todo-edit {
        display: flex;
      }
      .todo-input {
        flex: 1;
      }
      h1 {
        text-align: center;
      }
    </style>
  </head>
  <body class="container">
    <div class="todo container">
      <h1>☠︎TODOList⛱</h1>
      <div class="todo-edit">
        <div class="todo-input form-group mx-sm-3 mb-2">
          <label for="inputPassword2" class="sr-only">Password</label>
          <input type="text" class="form-control" placeholder="请输入代办" />
        </div>
        <button class="add-btn btn btn-primary mb-2">添加</button>
      </div>

      <ul class="todo-list"></ul>
    </div>
    <script type="module" src="./src/app.ts"></script>
  </body>
</html>

TodoEvent.ts

处理事件函数

/*
 * @Author: your name
 * @Date: 2021-02-20 20:30:39
 * @LastEditTime: 2021-02-20 20:36:54
 * @LastEditors: your name
 * @Description: In User Settings Edit
 * @FilePath: \observer-ts\src\TodoList\TodoEvent.ts
 */
import { ITodo } from ".";

class TodoEvent {
  private static instance: TodoEvent;
  private todoData: ITodo[] = [];

  public static create() {
    if (!TodoEvent.instance) {
      TodoEvent.instance = new TodoEvent();
    }

    return TodoEvent.instance;
  }

  public addTodo(todo: ITodo): Promise<ITodo> {
    return new Promise((resolve, reject) => {
      const _todo: ITodo = this.todoData.find(
        (t) => t.content === todo.content
      );

      if (_todo) {
        alert("该项已存在");
        return;
      }

      this.todoData.push(todo);
      console.log(this.todoData);
      resolve(todo);
    });
  }

  public removeTodo(id: number): Promise<number> {
    return new Promise((resolve, reject) => {
      this.todoData = this.todoData.filter((t) => t.id !== id);
      console.log(this.todoData);
      resolve(id);
    });
  }

  public toggleTodo(id: number): Promise<number> {
    return new Promise((resolve, reject) => {
      this.todoData = this.todoData.map((t) => {
        if (t.id === id) {
          t.completed = !t.completed;
          console.log(this.todoData);
          resolve(id);
        }
        return t;
      });
    });
  }
}

// addTodo(todo).then((todo) => {
//   addItem(todo);
// })

export default TodoEvent;

TodoDom.ts

处理DOM元素

/*
 * @Author: your name
 * @Date: 2021-02-20 20:32:18
 * @LastEditTime: 2021-02-21 16:18:32
 * @LastEditors: Please set LastEditors
 * @Description: In User Settings Edit
 * @FilePath: \observer-ts\src\TodoList\TodoDom.ts
 */
import { ITodo } from ".";

class TodoDom {
  private static instance: TodoDom;
  private oTodoList: HTMLElement;

  constructor(oTodoList: HTMLElement) {
    this.oTodoList = oTodoList;
  }

  public static create(oTodoList: HTMLElement) {
    if (!TodoDom.instance) {
      TodoDom.instance = new TodoDom(oTodoList);
    }

    return TodoDom.instance;
  }

  public addItem(todo: ITodo): Promise<void> {
    return new Promise((resolve, reject) => {
      const oItem: HTMLElement = document.createElement("div");
      oItem.className = "todo-item";
      oItem.innerHTML = this.todoView(todo);
      this.oTodoList.appendChild(oItem);
      resolve();
    });
  }

  public removeItem(id: number): Promise<void> {
    return new Promise((resolve, reject) => {
      const oItems: HTMLCollection = document.getElementsByClassName(
        "todo-item"
      );

      Array.from(oItems).forEach((oItem) => {
        const _id = parseInt(oItem.querySelector("button").dataset.id);

        if (_id === id) {
          oItem.remove();
          resolve();
        }
      });
    });
  }

  public toggleItem(id: number): Promise<void> {
    return new Promise((resolve, reject) => {
      const oItems: HTMLCollection = document.getElementsByClassName(
        "todo-item"
      );

      Array.from(oItems).forEach((oItem) => {
        const oCheckbox: HTMLInputElement = oItem.querySelector("input");
        const _id = parseInt(oCheckbox.dataset.id);

        if (_id === id) {
          const oContent: HTMLElement = oItem.querySelector("span");
          oContent.style.textDecoration = oCheckbox.checked
            ? "line-through"
            : "none";
          resolve();
        }
      });
    });
  }

  private todoView({ id, content, completed }: ITodo): string {
    return `
    <li class="list-group-item d-flex mt-2 ml-0 align-items-center" style="border-radius:10px">
      <input type="checkbox" ${completed ? "checked" : ""} data-id="${id}" />
      <h3 style="text-decoration: ${
        completed ? "line-through" : "none"
      } ; margin-left:20px">☃${content}</h3>
      <button style="margin-left:auto" type="button" class="btn btn-danger" data-id="${id}">删除</button>
    </li>
     
    `;
  }
}

export default TodoDom;

index.ts

结合TodoDomTodoEvent 将两个事件都添加到以下队列中

// 增加操做事件队列
 private addHandlers: any[] = [];
 // 删除操做事件队列
 private removeHandlers: any[] = [];
 // 切换操做事件队列
  private toggleHandlers: any[] = []

将事件中队列的时间循环执行

/*
 * @Author: your name
 * @Date: 2021-02-20 20:32:18
 * @LastEditTime: 2021-02-21 16:35:39
 * @LastEditors: Please set LastEditors
 * @Description: In User Settings Edit
 * @FilePath: \observer-ts\src\TodoList\index.ts
 */
import TodoEvent from "./TodoEvent";
import TodoDom from "./TodoDom";

export interface ITodo {
  id: number;
  content: string;
  completed: boolean;
}

enum EVENT_TYPE {
  ADD = "add",
  REMOVE = "remove",
  TOGGLE = "toggle",
}

class TodoList {
  private static instance: TodoList;
  private oTodoList: HTMLElement;
  private todoEvent: TodoEvent;
  private todoDom: TodoDom;
  private addHandlers: any[] = [];
  private removeHandlers: any[] = [];
  private toggleHandlers: any[] = [];

  constructor(oTodoList: HTMLElement) {
    this.oTodoList = oTodoList;

    this.initTodo();
  }

  public static create(oTodoList: HTMLElement) {
    if (!TodoList.instance) {
      TodoList.instance = new TodoList(oTodoList);
    }

    return TodoList.instance;
  }

  private initTodo() {
    this.todoEvent = TodoEvent.create();
    this.todoDom = TodoDom.create(this.oTodoList);

    for (let k in EVENT_TYPE) {
      this.initHandlers(EVENT_TYPE[k]);
    }
  }

  private initHandlers(type: EVENT_TYPE) {
    switch (type) {
      case EVENT_TYPE.ADD:
        this.addHandlers.push(this.todoEvent.addTodo.bind(this.todoEvent));
        this.addHandlers.push(this.todoDom.addItem.bind(this.todoDom));
        break;
      case EVENT_TYPE.REMOVE:
        this.removeHandlers.push(
          this.todoEvent.removeTodo.bind(this.todoEvent)
        );
        this.removeHandlers.push(this.todoDom.removeItem.bind(this.todoDom));
        break;
      case EVENT_TYPE.TOGGLE:
        this.toggleHandlers.push(
          this.todoEvent.toggleTodo.bind(this.todoEvent)
        );
        this.toggleHandlers.push(this.todoDom.toggleItem.bind(this.todoDom));
        break;
      default:
        break;
    }
  }

  //观察者
  public notify<T>(type: string, param: T) {
    let i: number = 0;
    let handlers: any[];
    let res: any;

    switch (type) {
      case EVENT_TYPE.ADD:
        handlers = this.addHandlers;
        break;
      case EVENT_TYPE.REMOVE:
        handlers = this.removeHandlers;
        break;
      case EVENT_TYPE.TOGGLE:
        handlers = this.toggleHandlers;
        break;
      default:
        break;
    }

    res = handlers[i](param);
    // 循环执行Promise
    while (i < handlers.length - 1) {
      i++;
      res = res.then((param) => {
        return handlers[i](param);
      });
    }
  }
}

export default TodoList;

app.js

import TodoList, { ITodo } from "./TodoList";

((doc) => {
  const oTodoList: HTMLElement = doc.querySelector(".todo-list");
  const oAddBtn: HTMLElement = doc.querySelector(".add-btn");
  const oInput: HTMLInputElement = doc.querySelector("input");

  const todoList: TodoList = TodoList.create(oTodoList);

  const init = (): void => {
    bindEvent();
  };

  function bindEvent() {
    oAddBtn.addEventListener("click", handleAddBtnClick, false);
    oTodoList.addEventListener("click", handleListClick, false);
  }

  function handleAddBtnClick() {
    const val: string = oInput.value.trim();

    if (!val.length) {
      return;
    }

    todoList.notify<ITodo>("add", {
      id: new Date().getTime(),
      content: val,
      completed: false,
    });

    oInput.value = "";
  }

  function handleListClick(e: MouseEvent) {
    const tar = e.target as HTMLElement;
    const tagName = tar.tagName.toLowerCase();

    if (tagName === "input" || tagName === "button") {
      const id: number = parseInt(tar.dataset.id);

      switch (tagName) {
        case "input":
          todoList.notify<number>("toggle", id);
          break;
        case "button":
          todoList.notify<number>("remove", id);
          break;
        default:
          break;
      }
    }
  }

  init();
})(document);

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Go微服务开发是利用Go语言进行微服务架构的开发方式。在这个问题中,使用了gin、grpc和etcd进行重构grpc-todolist项目。 Gin是一个轻量级的Web框架,使用它可以快速构建高性能的Web应用程序。它具有简单易用、性能出色和灵活的特点。在微服务开发中,Gin可以作为HTTP服务器框架,处理和响应客户端的HTTP请求。 gRPC是一种高性能、开源的远程过程调用(RPC)框架。它支持多种编程语言,并使用带有协议缓冲区的Google Protocol Buffers进行数据交换。在微服务架构中,gRPC可以用于服务之间的通信,通过定义接口和消息格式,实现服务间的数据传输和调用。 Etcd是一个高可靠、分布式的键值存储系统。它使用Raft一致性算法来保证数据的可靠性和一致性。在微服务开发中,Etcd可以作为服务发现和配置管理的工具,用于注册和发现各个微服务的信息。 对于重构grpc-todolist项目来说,使用gin可以将原有的HTTP接口改写为更加高性能的接口,提高整个系统的性能。通过使用gRPC,可以将原有的接口定义为gRPC接口,实现服务间的高效通信,并且易于扩展和维护。同时,借助Etcd实现服务注册和发现,提高系统的可用性和灵活性。 总而言之,通过使用gin、grpc和etcd对grpc-todolist项目进行重构,可以提高系统性能、扩展性和可维护性。这种微服务开发方式能够更好地适应大规模分布式系统的需求,使得系统更加稳定和可靠。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值