百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 优雅编程 > 正文

结合TypeScript前端人用的设计模式

sinye56 2024-10-22 16:26 8 浏览 0 评论

前言

程序编程的历史由来已久,一个优秀的编程思想可以让我们写出更好的逻辑代码。面向对象的程序应该具有可维护性,代码可复用性,扩展性以及灵活性。为了实现以上的功能,大佬们总结出一套可用的内功心法,那就是设计模式。设计模式(Design pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式,不仅方便写出更复杂的业务逻辑,更增加了业务代码的可读性,和维护性

设计原则

设计原则是设计模式所遵循的规则,设计模式就是实现了这些原则,从而达到了代码复用、增加可维护性的目的。

单一职责(Single Responsibility Principle - SRP)

一个类只负责一个功能领域中的相应职责,或者可以定义为:就一个类而言,应该只有一个引起它变化的原因。

一个函数,只用来干一件事,承担了过多的责任也就意味着多个功能的耦合。多个函数共同组成复杂的逻辑

开放封闭(Open Closed Principle - OCP)

对外开放,对内封闭

对扩展开放,意味着有新的需求或变化时,可以对现有代码进行扩展,以适应新的情况。对修改封闭,意味着类一旦设计完成,就可以独立完成其工作,而不要对类进行任何修改。

里氏替换(Liskov Substitution Principle - LSP)

子类可以扩展父类的功能,但不能改变父类原有的功能


子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法。

  • 子类中可以增加自己特有的方法。
  • 当子类的方法重载父类的方法时,方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松。
  • 当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格。

  • 接口隔离(Interface Segregation Principle - ISP)

    建立单一接口,不要建立庞大臃肿的接口,尽量细化接口,接口中的方法尽量少

    使用多个专门的接口,而不使用单一的总接口,即客户端不应该依赖那些它不需要的接口,根据接口隔离原则,当一个接口太大时,我们需要将它分割成一些更细小的接口,使用该接口的客户端仅需知道与之相关的方法即可,接口隔离根本在于不要强迫客户端程序依赖他们不需要使用的方法



    依赖倒置(Dependence Inversion Principle - DIP)

    依赖倒置原则的核心思想是面向接口编程,不应该面向实现类编程。

    也就是使用接口和抽象类进行变量类型声明、参数类型声明、方法返回类型声明,以及数据类型的转换等,而不是使用具体的类

    Tip: 设计原则简称SOLID原则,方便记忆。同时还有一些不常用的设计原则。如:组合/聚合复用原则,无环依赖原则,共同重用原则

    设计模式的分类

    总体来说设计模式分为三大类

    创建型 (5种)

    • 单例模式(Singleton Pattern)
    • 工厂方法模式(Factory Method Pattern)
    • 抽象工厂模式(Abstract Factory Pattern)
    • 建造者模式(Builder Pattern)
    • 原型模式(Prototype Pattern)


    结构型(7种)

    • 适配器模式(Adapter Pattern)
    • 装饰者模式(Decorator Pattern)
    • 代理模式(Proxy Pattern)
    • 外观模式(Facade Pattern)
    • 桥接模式(Bridge Pattern)
    • 组合模式(Composite Pattern)
    • 享元模式(Flyweight Pattern)

    行为型(11种)

    • 策略模式(Strategy Pattern)
    • 模板方法模式(Template Method Pattern)
    • 观察者模式(Observer Pattern)
    • 迭代器模式(Iterator Pattern)
    • 责任链模式(Chain of Responsibility Pattern)
    • 命令模式(Command Pattern)
    • 备忘录模式(Memento Pattern)
    • 状态模式(State Pattern)
    • 访问者模式(Visitor Pattern)
    • 中介者模式(Mediator Pattern)
    • 解释器模式(Interpreter Pattern)

    Tip:图解



    其实还有两类:并发型模式和线程池模式。用一个图片来整体描述一下:


    设计模式具体说明

    单例模式(Singleton Pattern)

    通过单例模式可以保证系统中,应用该模式的类一个类只有一个实例。即一个类只有一个对象实例。单例对象的类只能允许一个实例(instance)存在


    使用场景

    小编之前在项目中,使用element-ui的Message组件,Message默认是可以展示多条的,但是如果客户是个急性子,多点了几次,就会出现满屏的Message,这个时候我们只需要一个Message来通知即可

    大家都知道,在写后端的时候,数据库连接池对象也会被设计成单例模式

    优缺点

    优点:

    1. 在内存中只有一个对象,节省内存空间;

    2. 避免频繁的创建销毁对象,可以提高性能;

    缺点:

    1.不适用于变化频繁的对象;

    单例模式的实现

    饿汉式


    懒汉式


    javascript是单线程运行,所有没有多线程的加锁概念

    js闭包实现单例模式


    实现单一Message


    • 当前以上的方式是为了演示单例模式,并不是最好的处理方法

    工厂方法模式(Factory Method Pattern)

    工厂方法模式,又称工厂模式,通过定义工厂父类负责定义创建对象的公共接口,而子类则负责生成具体的对象。



    由上图可以看出,工厂方法模式需要四个角色


    • 抽象工厂(Abstract Factory):提供了创建产品的接口,调用者通过它访问具体工厂的工厂方法 newProduct() 来创建产品。
    • 具体工厂(ConcreteFactory):主要是实现抽象工厂中的抽象方法,完成具体产品的创建。
    • 抽象产品(Product):定义了产品的规范,描述了产品的主要特性和功能。
    • 具体产品(ConcreteProduct):实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂之间一一对应。

    使用场景


    建造者模式(Builder Pattern)

    建造者模式又叫创建者模式,是将一个复杂的对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。创建者模式隐藏了复杂对象的创建过程,它把复杂对象的创建过程加以抽象,通过子类继承或者重载的方式,动态的创建具有复合属性的对象。


    如上图所示,建造者模式一共有4个角色

    • Product: 最终要生成的对象。
    • Builder: 构建者的抽象基类(有时会使用接口代替)。其定义了构建Product的抽象步骤,其实体类需要实现这些步骤。其会包含一个用来返回最终产品的方法Product getProduct()。
    • ConcreteBuilder: Builder的实现类。
    • Director: 决定如何构建最终产品的算法. 其会包含一个负责组装的方法Construct(Builder builder), 在这个方法中通过调用builder的方法,就可以设置builder,等设置完成后,就可以通过builder的 getProduct() 方法获得最终的实例。

    使用场景

    ```

    abstract class PhoneBuilder {

    public abstract setBrand(): void;

    public abstract setSystem(): void;

    public abstract getPhone(): Phone;

    }

    class Phone {

    private brand: string = '';

    private cpu: string = '';

    private screen: number = 0;

    private system: string = '';

    constructor(cpu: string, screen: number) {

    this.cpu = cpu;

    this.screen = screen;

    }

    setBrand(brand: string) {

    this.brand = brand;

    }

    setCPU(cpu: string) {

    this.cpu = cpu;

    }

    setScreen(screen: number) {

    this.screen = screen;

    }

    setSystem(system: string) {

    this.system = system;

    }

    }

    class HuaWei extends PhoneBuilder {

    private phone: Phone;

    constructor(cpu: string, screen: number) {

    super();

    this.phone = new Phone(cpu, screen);

    }

    public setBrand() {

    this.phone.setBrand('huawei');

    }

    public setSystem() {

    this.phone.setSystem('hongmeng');

    }

    public getPhone() {

    return this.phone;

    }

    }

    class PhoneDirector {

    makeComputer(builder: PhoneBuilder){

    builder.setBrand();

    builder.setSystem();

    }

    }

    var director = new PhoneDirector()

    var builder = new HuaWei('八核', 5.8)

    director.makeComputer(builder)

    var huawei = builder.getPhone()

    // Phone { brand: 'huawei', cpu: '八核', screen: 5.8, system: 'hongmeng' }

    ```

    原型模式(Prototype Pattern)

    用于创建重复的对象,同时又能保证性能。提供了一种创建对象的最佳方法。


    适配器模式(Adapter Pattern)

    将一个类的接口转换成客户希望的另外一个接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。


    使用场景


    装饰者模式(Decorator Pattern)

    装饰者模式又名包装(Wrapper)模式。装饰者模式动态地将责任附加到对象身上。若要扩展功能,装饰者提供了比继承更有弹性的替代方案。


    使用场景

    ```

    interface HuaWei {

    getMemory(): string

    getPrice(): number

    }

    class HuaWeiPro30 implements HuaWei {

    getMemory(){

    return '16G'

    }

    getPrice(){

    return 6888

    }

    }

    class HuaWeiPro40 implements HuaWei {

    getMemory(){

    return '16G'

    }

    getPrice(){

    return 8888

    }

    }

    abstract class ScreenHuaWei implements HuaWei {

    abstract huawei: HuaWei;

    abstract getScreen(): number;

    getPhone(){

    return this.huawei

    }

    setPhone(phone: HuaWei){

    this.huawei = phone

    }

    getMemory(){

    return this.huawei.getMemory()

    }

    getPrice(){

    return this.huawei.getPrice()

    }

    }

    class AddScreen extends ScreenHuaWei {

    huawei: HuaWei;

    constructor(phone:HuaWei){

    super()

    this.huawei = phone

    }

    getScreen(){

    return 5.5

    }

    }

    var phoneWithoutScreen = new HuaWeiPro40()

    var phoneScreen5 = new AddScreen(phoneWithoutScreen)

    console.log(phoneScreen5.getScreen()) // 5.5

    ```

    代理模式(Proxy Pattern)

    可以为其他对象提供一种代理以控制对这个对象的访问。所谓代理,是指具有与被代理对象相同的接口的类,客户端必须通过代理与被代理的目标类进行交互,而代理一般在交互的过程中(交互前后),进行某些特定的处理。


    使用场景


    策略模式(Strategy Pattern)

    策略模式定义了一系列算法,并将每一个算法封装起来,而且使他们还可以相互替换,策略模式让算法独立于使用它的客户而独立变化。


    Tip:

    • Context :用来操作策略的上下文环境
    • Stragety: 策略的抽象
    • ConcreteStragetyA, ConcreteStragetyB 具体策略实现。

    使用场景


    观察者模式(Observer Pattern)

    有时被称作发布/订阅模式,观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态发生变化时,会通知所有观察者对象,使它们能够自动更新自己。


    使用场景

    ```

    class Subject {

    private state: number;

    private observers: Array<Observer>;

    constructor() {

    this.state = 0;

    this.observers = [];

    }

    getState() {

    return this.state;

    }

    setState(state: number) {

    this.state = state;

    this.notifyAllObservers();

    }

    notifyAllObservers() {

    this.observers.forEach((observer) => {

    observer.update();

    });

    }

    attach(observer: Observer) {

    this.observers.push(observer);

    }

    }

    //观察者类

    class Observer {

    name: string;

    subject: Subject;

    constructor(name: string, subject: Subject) {

    this.name = name;

    this.subject = subject;

    this.subject.attach(this); //把观察者添加到主题中观察者列表上来

    }

    update() {

    console.log(`${this.name} update, state: ${this.subject.getState()}`);

    }

    }

    let sub = new Subject();

    let obs1 = new Observer('obs1', sub);

    let obs2 = new Observer('obs2', sub);

    let obs3 = new Observer('obs3', sub);

    sub.setState(1);

    sub.setState(2);

    sub.setState(3);

    ```

    状态模式(State Pattern)

    状态(State)模式,当一个对象的内在状态改变时允许改变其行为,这个对象看起来就像是改变了其类。状态模式主要解决的是当控制一个对象状态转换条件表示式过于复杂时的情况。把状态的判断逻辑转移到表示不同状态的一系列类当中,可以把复杂的判断逻辑简化。


    使用场景

    ```

    abstract class State {

    protected context: Context | undefined;

    setContext(context: Context): void {

    this.context = context;

    }

    public abstract handle1(): void;

    }

    class ConcreteState1 extends State {

    handle1(): void {

    //...

    console.log('ConcreteState1 的 handle1 方法');

    }

    }

    class Context {

    static STATE1: State = new ConcreteState1();

    currentState: State | undefined;

    getCurrentState(): State | undefined {

    return this.currentState;

    }

    setCurrentState(currentState: State) {

    this.currentState = currentState;

    this.currentState.setContext(this);

    }

    handle1(): void {

    this.currentState && this.currentState.handle1();

    }

    }

    let context = new Context();

    context.setCurrentState(new ConcreteState1());

    context.handle1();

    ```

    结束语

    以上介绍了几种常用的设计模式,在javascript中,设计模式用的不多,但是要写出来很需要一份内功力,需要能帮助到小伙伴们~

    相关推荐

    Linux两种光驱自动挂载的方法

    环境:CentOS6.4西昆云服务器方式一修改fstab文件/etc/fstab是系统保存文件系统信息?静态文件,每一行描述一个文件系统;系统每次启动会读取此文件信息以确定需要挂载哪些文件系统。参...

    linux系统运维,挂载和分区概念太难?在虚机下操作一次全掌握

    虚拟机的好处就是可以模拟和学习生产环境的一切操作,假如我们还不熟悉磁盘操作,那先在虚机环境下多操作几次。这次来练习下硬盘扩容操作。虚拟机环境:centos8vm11linux设备命名规则在linux中...

    Linux 挂载 NFS 外部存储 (mount 和 /etc/fstab)

    mount:手工挂载,下次重启需再重新挂载,操作命令:mount-tnfs-ooptionsserver:/remote/export/local/directory上面命令中,本地目录...

    在Linux中如何设置自动挂载特定文件系统(示例)

    Linux...

    Linux环境中的绑定挂载(bind mount)

    简介:Linux中的mount命令是一个特殊的指令,主要用于挂载文件目录。而绑定挂载(bindmount)命令更为特别。mount的bind选项将第一个目录克隆到第二个。一个目录中的改变将会在...

    Linux挂载CIFS共享 临时挂载 1. 首先

    如何解决服务器存储空间不足的问题?大家好,欢迎回来。在上一期视频中,我为大家介绍了如何利用Linux挂载来扩容服务器存储空间。这一期视频,我将以Linux为例,教大家如何进行扩容。群辉使用的是Linu...

    Linux 硬盘挂载(服务器重启自动挂载)

    1、先查看目前机器上有几块硬盘,及已挂载磁盘:fdisk-l能够查看到当前主机上已连接上的磁盘,以及已经分割的磁盘分区。(下面以/dev/vdb磁盘进行分区、挂载为例,挂载点设置为/data)df...

    linux 挂载磁盘

    在Linux中挂载硬盘的步骤如下:...

    笨小猪教您Linux磁盘挂载

    本教程针对Linux系统比较熟悉或者想学习Linux基础的用户朋友,本教程操作起来比较傻瓜式,跟着步骤就会操作,本文使用的工具是XShell同时多多注意空格(文中会有提示)。【问答】什么是磁盘挂载?答...

    Linux 磁盘挂载和docker安装命令

    本篇给大家介绍Linux磁盘挂载和docker安装的相关内容,Linux服务器的操作是一个手熟的过程,一些不常用的命令隔断时间就忘记了,熟话说好记性不如烂笔头,还需在平时的工作中多练习记录。...

    Linux设置开机自动挂载分区

    有时候,我们在安装完Linux系统之后,可能在使用过程中添加硬盘或者分区进行使用,这时候就需要手动把磁盘分区挂载到某个路径,但是开机之后就会消失,需要重新挂载,非常麻烦,那么我们应该如何设置开机自动挂...

    在linux挂载一个新硬盘的完整步骤

    以下是在Linux中挂载新原始磁盘的完整步骤,包括分区、创建文件系统以及使用UUID在/etc/fstab中启动时挂载磁盘:将新的原始磁盘连接到Linux系统并打开电源。运行以下命令,...

    Linux系统如何挂载exFAT分区

    简介:Linux系统中不能像Windows系统那样自动识别加载新设备,需要手动识别,手动加载。Linux中一切皆文件。文件通过一个很大的文件树来组织,文件树的根目录是:/,从根目开始录逐级展开。这些文...

    Linux系统挂载硬盘

    fdisk-l查看可挂载的磁盘都有哪些df-h查看已经挂载的磁盘...

    WSL2发布,如何在Win10中挂载Linux文件系统

    WSL2是最新版本的架构,它为Windows子系统提供支持,使其能够在Windows上运行ELF64Linux二进制文件。通过最近的更新,它允许使用Linux文件系统访问存储在硬盘中的文件。如果你...

    取消回复欢迎 发表评论: