设计模式相关

114 阅读8分钟

工厂模式

工厂模式是我们最常用的实例化对象模式了,是用工厂方法代替new操作的一种模式。 工厂模式 可以简单理解为在函数内部创建了一个新的空对象,并为其添加属性和方法。

function createObject(name, age, profession) {//集中实例化的函数\
    var obj = new Object();
    obj.name = name;
    obj.age = age;
    obj.profession = profession;
    obj.move = function () {
        return this.name + ' at ' + this.age + ' engaged in ' + this.profession;
    };
    return obj;
}
var test1 = createObject('trigkit4', 22, 'programmer');//第一个实例
var test2 = createObject('mike', 25, 'engineer');//第二个实例
console.log(test1.move());
console.log(test2.move());
console.log(test1.move() === test2.move()) //false

缺点: 虽然new的是共同的一个函数,但是其中的方法做对比,返回false,也就意思说,new的函数,中的方法,不是共享(不是同一个地址).

构造函数模式

构造函数模式和工厂模式具有共同的 缺点,都是 虽然new的是共同的一个函数,但是其中的方法做对比,返回false,也就意思说,new的函数,中的方法,不是共享(不是同一个地址). ,但是他们还是有些不同的地方。

function CreateObject(name, age, profession) {//集中实例化的函数\
    this.name = name;
    this.age = age;
    this.profession = profession;
    this.move = function () {
        return this.name + ' at ' + this.age + ' engaged in ' + this.profession;
    };
}
var test3 = new CreateObject('trigkit4', 22, 'programmer');//第一个实例
var test4 = new CreateObject('mike', 25, 'engineer');//第二个实例
console.log(test3.move());
console.log(test4.move());
console.log(test3.move() === test4.move()) //false
class CreateObject {
    constructor(name, age, profession){
        this.name = name;
        this.age = age;
        this.profession = profession;
    }
    move = ()=>{
        return this.name + ' at ' + this.age + ' engaged in ' + this.profession;
    }
}
var test3 = new CreateObject('trigkit4', 22, 'programmer');//第一个实例
var test4 = new CreateObject('mike', 25, 'engineer');//第二个实例
console.log(test3.move());
console.log(test4.move());
console.log(test3.move() === test4.move()) //false

(1):构造函数模式不需要在内部创建对象。

(2):构造函数模式 函数的首字母要大写。

(3):构造函数模式 内写的方法和属性前用 this

原型模式

每当我们创建一个函数,都有一个prototype 属性

class CreateObject {
    constructor(name, age, profession){
        this.name = name;
        this.age = age;
        this.profession = profession;
    }
}
CreateObject.prototype.move = ()=>{
    return this.name + ' at ' + this.age + ' engaged in ' + this.profession;
}
var test3 = new CreateObject('trigkit4', 22, 'programmer');//第一个实例
var test4 = new CreateObject('mike', 25, 'engineer');//第二个实例
console.log(test3.move());
console.log(test4.move());
console.log(test3.move() === test4.move()) //true

单例模式

单例模式的意思是,保证一个类只有一个实例,并且有一个接口供全局访问。它的作用就是防止频繁创建实例,浪费不必要的内存空间和资源消耗,那它有什么实用场景呢,假如我们在页面中有一个点击跳出一个弹窗操作,弹窗应该是唯一的,无论点击多少次它都应该被构建一次,那么这个弹窗就适合用单例模式来创建。

如何才能保证一个类仅有一个实例?

需要构造函数具备判断自己是否已经创建过一个实例的能力

class Dog {
   show() {
       console.log('我是一个单例对象')
   }
   static getInstance() {
       // 判断是否已经new过1个实例
       if (!Dog.instance) {
           // 若这个唯一的实例不存在,那么先创建它
           Dog.instance = new SingleDog()
       }
       // 如果这个唯一的实例已经存在,则直接返回
       return Dog.instance
   }
}

const s1 = Dog.getInstance()
const s2 = Dog.getInstance()

// true
s1 === s2

实现思想:将第一次创建的实例进行保存,之后再次创建前判断是否已经创建,如果之前创建过则返回已经保存的实例,否则创建一个实例,将实例创建和判断封装到了一个 getInstance 函数中,这种方式相对简单,但增加了类的“不透明性”,用一个函数来获取一个实例,而不是以往通过 new 来创建。

其他创建单例模式方法:blog.csdn.net/SK_study/ar…

// 先实现一个基础的StorageBase类,把getItem和setItem方法放在它的原型链上
function StorageBase () {}

// 以闭包的形式创建一个引用自由变量的构造函数
const Storage = (function(){
    let instance = null
    return function(){
        // 判断自由变量是否为null
        if(!instance) {
            // 如果为null则new出唯一实例
            instance = new StorageBase()
        }
        return instance
    }
})()

// 这里其实不用 new Storage 的形式调用,直接 Storage() 也会有一样的效果 
const storage1 = new Storage()
storage1.name = 123
const storage2 = new Storage()
console.log(storage2);

// 返回true
console.log(storage1 === storage2);

装饰器模式

在开发过程中,很多时候我们不想要类的功能一开始就很庞大,一次性包含很多职责。这个时候我们可以使用装饰器模式。动态的给某个对象添加一些职责,并且不会影响从这个类派生的其他对象。

在传统的面向对象开发中,给对象添加功能时,我们通常会采用继承的方式,继承的方式目的是为了复用,但是随之而来也带来一些问题:

(1)父类和子类存在强耦合的关系,当父类改变时,子类也需要改变;

(2)子类需要知道父类中的细节,至少需要知道接口名,从而进行复用或复写,这样其实破坏了封装性;

(3)继承的方式,可能会创建出大量子类。比如现在有BBA三种类型的汽车,构造了一个汽车基类,三个三种类型的汽车。现在需要给汽车装上雾灯、前大灯、导航仪、刮雨器,如果采用继承的方式,那么就要构建3*4个类。但是如果把雾灯、前大灯、导航仪、刮雨器动态地添加到汽车上,那么只需要增加4个类。这种采用动态添加职责的方式就是装饰器。

装饰器的目的就是在不改变原来类的基础上,为其在运行期间动态的添加职责。

装饰器用法

类装饰器

// 装饰器函数,它的第一个参数是目标类
function classDecorator(target) {
    target.hasDecorator = true
  	return target
}

// 将装饰器“安装”到Button类上
@classDecorator
class Button {
    // Button类的相关逻辑
}

// 验证装饰器是否生效
console.log('Button 是否被装饰了:', Button.hasDecorator)

函数装饰器

function funcDecorator(target, name, descriptor) {
// target: Button.prototype  name: 修饰的目标属性属性名  descriptor:JavaScript提供的一个内部数据结构、一个对象,专门用来描述对象的属性
    let originalMethod = descriptor.value
    descriptor.value = function() {
    console.log('我是Func的装饰器逻辑')
    return originalMethod.apply(this, arguments)
  }
  return descriptor
}

class Button {
    @funcDecorator
    onClick() { 
        console.log('我是Func的原有逻辑')
    }
}

// 验证装饰器是否生效
const button = new Button()
button.onClick()

参数装饰器,属性装饰器

属性修饰器 > 参数修饰器 > 方法修饰器 > 类修饰器

修饰器对类行为的改变,是代码编译时发生的,而不是在运行时。修饰器能在编译阶段运行代码。也就是说,修饰器本质就是编译时执行的函数

适配器模式

适配器模式 可以用来在现有接口和不兼容的类之间进行适配。

很多常见的包中都会有适配器模式;

代理模式

代理模式是非常常见的模式,比如我们使用的VPN工具,明星的经纪人,都是代理模式的例子。但是,有人会疑问,明明可以直接访问对象,为什么中间还要加一个壳呢?这也就说到了代理模式的好处。在我看来,代理模式最大的好处,就是在不动原有对象的同时,可以给原有对象增加一些新的特性或者行为。

事件代理:根据事件冒泡的特性实现代理;

虚拟代理

缓存代理

保护代理

策略模式

策略模式的定义:定义一系列的算法,将他们一个个封装起来,使他们直接可以相互替换。

观察者模式

观察者和被观察者,是松耦合的关系

// 定义发布者类
class Publisher {
    constructor() {
      this.observers = []
      console.log('Publisher created')
    }
    // 增加订阅者
    add(observer) {
      console.log('Publisher.add invoked')
      this.observers.push(observer)
    }
    // 移除订阅者
    remove(observer) {
      console.log('Publisher.remove invoked')
      this.observers.forEach((item, i) => {
        if (item === observer) {
          this.observers.splice(i, 1)
        }
      })
    }
    // 通知所有订阅者
    notify() {
      console.log('Publisher.notify invoked')
      this.observers.forEach((observer) => {
        observer.update(this)
      })
    }
  }

  // 定义订阅者类
class Observer {
    constructor() {
        console.log('Observer created')
    }

    update() {
        console.log('Observer.update invoked')
    }
}

const use1 = new Observer();
const use2 = new Observer();
const use3 = new Observer();
const Person = new Publisher();
Person.add(use1);
Person.add(use2);
Person.add(use3);
Person.notify();

订阅-发布模式

发布者和订阅者,则完全不存在耦合

//发布订阅模式
class EventEmiter {
    constructor() {
        //维护一个对象
        this._events = {

        }
    }
    on(eventName, callback) {
        if (this._events[eventName]) {
            //如果有就放一个新的
            this._events[eventName].push(callback);
        } else {
            //如果没有就创建一个数组
            this._events[eventName] = [callback]
        }
    }
    emit(eventName, ...rest) {
        console.log(...rest + 'rest的写法')
        // alert(...rest)
        if (this._events[eventName]) { //循环一次执行
            this._events[eventName].forEach((item) => {
                item.apply(this, rest)
            });
        }
    }
    removeListener(eventName, callback) {
        alert(callback)
        if (this._events[eventName]) {
            //当前数组和传递过来的callback相等则移除掉
            this._events[eventName] =
                this._events[eventName].filter(item => item !== callback);
        }
    }
    once(eventName, callback) {
        function one() {
            //在one函数运行原来的函数,只有将one清空
            callback.apply(this, arguments);
            //先绑定 执行后再删除
            this.removeListener(eventName, one);
        }
        this.on(eventName, one);
        //此时emit触发会执行此函数,会给这个函数传递rest参数
    }
}
class Man extends EventEmiter { }
let man = new Man()
function chifan() {
    console.log('我吃了' + JSON.stringify(...arguments));
}
man.on('ele', chifan)//ele ,绑定一个函数方法
// man.removeListener('ele', chifan); //移除一个函数方法
man.emit('ele', ['米饭', '面条']);
//绑定一次,触发多次,也只执行一次。触发后一次将数组中的哪一项删除掉下次触发就不会执行

迭代器模式

迭代器模式是设计模式中少有的目的性极强的模式。所谓“目的性极强”就是说它不操心别的,它就解决这一个问题——遍历。

在ES6中,针对Array、Map、Set、String、TypedArray、函数的 arguments 对象、NodeList 对象这些原生的数据结构都可以通过for...of...进行遍历。

之所以能够按顺序一次一次地拿到数组里的每一个成员,是因为我们借助数组的Symbol.iterator生成了它对应的迭代器对象,通过反复调用迭代器对象的next方法访问了数组成员,像这样: