ES6程序员javascript设计模式

js设计模式[es6]

2018-05-04  本文已影响878人  扬子拉虫

先说说名字

创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。
结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
行为型模式,共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。

这么多枯燥的名字 在我们JS中使用的我先介绍个10种吧。

再说说原则

总原则:开闭原则(Open Close Principle)

大家都用过USB外设吧,外设有很多种比如鼠标,充电器,小风扇,U盘,那么对于USB内部的实现其实我们是不知道的,但是我们只要按照他提供的抽象的USB接口就可以去生产我们不同的设备。这就是对外您随便,对内不好意思不让您看。

1、单一职责原则

这个和现在的函数式编程 和模块化编程的思想有些像,也就是说一个函数 现在ES6中可以说是Class 只做单独的一类事情 ,这个力度可以自己把控,只要不影响其他模块那就是对的。

2、里氏替换原则(Liskov Substitution Principle)

JS中没有方法重载这一说(也就是方法名称一样 参数不同可以视为不同方法)JS中实现呢基本是更具参数的类型不同 然后 走不同的逻辑实现分支。所以JS中这个原则其实不是那么的强

这里我也不会描述了,引用别人的一句话 历史替换原则中,子类对父类的方法尽量不要重写和重载。因为父类代表了定义好的结构,通过这个规范的接口与外界交互,子类不应该随便破坏它。

总结一句话 —— 就是尽量不要从可实例化的父类中继承,而是要使用基于抽象类和接口的继承。

其他原则

写到这里其实大家还有总结了一些 依赖倒转原则(Dependence Inversion Principle),接口隔离原则(Interface Segregation Principle),迪米特法则(最少知道原则)(Demeter Principle),合成复用原则(Composite Reuse Principle),总结下这些原则其实还是总原则的一些完善,主要就是面向接口编程和面向抽象编程的思想。这里就不引导大家学习这些了,有兴趣的同学可以百度一下这些原则。

💐闲话到这里为止,我们来看看实际的代码吧。


创建型模式

简单工厂模式(Simple Factory)4
抽象工厂模式(Abstract Factory)5
建造者模式(Builder)2
单例模式(Singleton)3

1 单例模式

var single = (function() {
    var temp;
    function getInstance(name) {
        if (temp === undefined) {
            temp = new Temp(name);
        }
        return temp;
    }
    function Temp(name) {
        this.name = name;
    }
    return {
        getInstance: getInstance
    }
})();
single.getInstance("kobe")
//输出Temp {name: "kobe"}
single.getInstance("jay")
//输出Temp {name: "kobe"}

单例模式也就是说一个对象的实例在系统中只能存在一个,这样做的好处就是可以控制核心对象在系统中的唯一性,但是由于闭包的特性,他的GC回收不及时。

1 简单工厂模式

让我们先看看一段代码

class ButtonFactory{
    constructor(name,color){
        this.name = name;
        this.color = color;
    }
    getButton(){
        let btn = document.createElement("button");
        btn.innerText  = this.name;
        btn.style.backgroundColor=this.color;
        btn.style.minWidth="100px"
        return btn;
    }
}

然后在看看进阶版本

class Buttons {
    constructor(name, color, cf) {
        this.name = name;
        this.color = color;
        
    }
    createButton(cf) {
        let btn = document.createElement("button");
        btn.style.backgroundColor = this.color;
        btn.style.minWidth = "100px"
        btn.onclick=cf;
        btn.innerText=this.name;
        return btn;
    }
}
class ButtonBlue extends Buttons {
    constructor(cf) {
        super();
        this.name="我是蓝色按钮";
        this.color="blue";
        
    }
}

class ButtonRed extends Buttons {
    constructor(cf) {
        super();
        this.name="我是红色按钮";
        this.color="red";
        
    }
}

class ButtonFactory {
    constructor(type) {
        this.type = type;
    }
    getButton(cf) {
       let btn;
        switch(this.type) {
            case "blue":
                btn= new ButtonBlue().createButton(cf);
                break;
            case "red":
                btn= new ButtonRed().createButton(cf);
                break;
        }

        return btn;
    }
}

第一种写法就是单纯的产生一个按钮的实例,既然是工厂就应该生产多元化的按钮,那么我们经过改进以后的代码灵活性和实用性就要高与第一种写法,但是这种生产按钮的工厂还是不能满足我们多元的需求,那么我们就需要下一种模式

抽象工厂模式

还是老样子 先撸一段代码

const CXMONKEY = {
    MONKEY: function(name) {
        return new Monkey(name)
    }
}
const CXSHIP = {
    MONKEY: function(name) {
        return new Ship(name)
    }
}
const FA = {
    MF: function() {
        return new MonkeyFactory()
    },
    SP: function() {
        return new ShipFactory()
    }
}
//抽象方法
class IAnmial {
    say() {
        throw "我是抽象方法"
    }
}
//抽象类
class Anmial extends IAnmial {
    constructor(name) {
        super();
        this.name = name;
    }
    eat() {
        console.log(this.name + "吃东西")
    }
    anmial(type, name, factory) {
        return FA[factory]().getAnmial(type, name)
    }

}
//抽象工厂
class AbsAnmialFactory {
    cerateAnmial(type, name, factory) {
        return new Anmial().anmial(type, name, factory);
    }
}
//实例工厂
class MonkeyFactory {
    getAnmial(type, name) {
        return CXMONKEY[type](name);
    }
}
//实例工厂
class ShipFactory {
    getAnmial(type, name) {
        return CXSHIP[type](name);
    }
}
//实例产品
class Monkey extends Anmial {
    constructor(name) {
        super(name);
    }
    say() {
        console.log("我是CX猴工厂出来的:" + this.name)
    }

}
//实例产品
class Ship extends Anmial {
    constructor(name) {
        super(name);
    }
    say() {
        console.log("我是CX羊工厂出来的:" + this.name)
    }

}
//调用方式
new AbsAnmialFactory().cerateAnmial('MONKEY', "猴子", "SP").say();

这段代码解释起来还是有点绕口,简单说抽象工厂就是生产抽象的产品的 在实例化的过程中可以指定 是有哪个实例工厂 生产那种产品。那么同样是汽车,可能是德国制造也可以是 中国制造。这样的好处就是可以多种组合但是不用改变代码。
看看这个实际的定义 结合代码理解一下
多个抽象产品类,每个抽象产品类可以派生出多个具体产品类。
一个抽象工厂类,可以派生出多个具体工厂类。
每个具体工厂类可以创建多个具体产品类的实例,也就是创建的是一个产品线下的多个产品。

建造者模式

来瞅瞅代码


class Car {
     constructor(engine,tyre){
        this.engine = engine;
            this.tyre = tyre;
     }
}

class Engineer {
     makeCar(factory){
        return new Car(factory.getEngine(), factory.getTyre());
     }
}

class Factory { //这个工厂不是工厂模式
    getEngine() {
        console.log("引擎组装完毕");
        return "我是动力"
    }
    getTyre() {
        console.log("轮胎组装完毕");
        return "我是轮胎"
    }
    
};

var engineer = new Engineer();
var car = engineer.makeCar(new Factory())
console.log(car)

建造者模式是一步一步创建一个复杂的对象,它允许用户只通过指定复杂对象的类型和内容就可以构建它们,用户不需要知道内部的具体构建细节。建造者模式属于对象创建型模式。根据中文翻译的不同,建造者模式又可以称为生成器模式
结合代码我们看看,我们的汽车由轮胎和引擎组成但是呢我们需要一个工程师来组装他 ,但是他怎么组装的我们并不知道,最后返回一个汽车出来就好了。


结构型模式

适配器模式
桥接模式
装饰模式
外观模式
享元模式
代理模式

适配器模式

将一个接口转换成客户希望的另一个接口,适配器模式使接口不兼容的那些类可以一起工作,其别名为包装器(Wrapper)。适配器模式既可以作为类结构型模式,也可以作为对象结构型模式。

class MediaPlayer{
    play(audioType,fileName){
        throw "我是抽象的播放方法 没有具体实现"
    }
}
class AdvancedMediaPlayer{
    playVlc(fileName){}
    playMp4(fileName){}
}
class VlcPlayer extends AdvancedMediaPlayer{
    playVlc(fileName){
        console.log(`我是Vlc播放器文件名称是${fileName}`)
    }
    playMp4(fileName){}
}
class Mp4Player extends AdvancedMediaPlayer{
    playVlc(fileName){}
    playMp4(fileName){
        console.log(`我是Mp4播放器文件名称是${fileName}`)
    }
}
class MediaAdapter extends MediaPlayer{
    play(audioType,fileName){
        if(audioType=="vlc")new VlcPlayer().playVlc(fileName);
        if(audioType=="mp4")new Mp4Player().playMp4(fileName);
    }
}
class AudioPlayer extends MediaPlayer{
    play(audioType,fileName){
    if(audioType==="mp3"){
        console.log(`我是mp3播放器文件名称是${fileName}`)
    }else{
        return new MediaAdapter().play(audioType,fileName);
    }
    }
}
 var audioPlayer = new AudioPlayer();
      audioPlayer.play("mp3", "beyond the horizon.mp3");
      audioPlayer.play("mp4", "alone.mp4");
      audioPlayer.play("vlc", "far far away.vlc");
      

我们有一个播放器,他有基本的播放功能。我们不想他只能播放MP3 所以我们可以用一个适配器转换一下,让他拥有更多的功能。

桥接模式(Bridge Pattern)

将抽象部分与它的实现部分分离,使它们都可以独立地变化。它是一种对象结构型模式,又称为柄体(Handle and Body)模式或接口(Interface)模式

class DrawAPI {
   drawCircle(radius,x, y){
    throw "抽象方法"
   }
}

 class RedCircle extends DrawAPI {
    drawCircle( radius,  x,  y) {
     console.log("Drawing Circle[ color: red, radius: " + radius + ", x: " + x + ", " + y + "]");
   }
}
  class GreenCircle extends DrawAPI {
    drawCircle( radius,  x,  y) {
      console.log("Drawing Circle[ color: green, radius: " + radius + ", x: " + x + ", " + y + "]");
   }
}
  
  class Shape {
    constructor(drawAPI) {
        this.drawAPI = drawAPI;
    }
    draw(){
         this.drawAPI.drawCircle(this.x,this.y,this.radius)
    }
}
  class Circle extends Shape{
    constructor(x, y, radius,drawAPI){
        super(drawAPI);
        this.x = x;
        this.y = y;
        this.radius = radius;
        
    }
  
  }
      var redCircle = new Circle(100,1000, 10, new RedCircle());
      var greenCircle = new Circle(100,100, 10, new GreenCircle());
      redCircle.draw();
      greenCircle.draw();

我们现在有一个画图形的类这个类可以生成一个圈,为了可以画不同种类的圈我们可以吧画圈的实现类传递进去,这样就实现了我们的多样化可扩展了。如果我们想画一个其他类型的圈我们可以继承抽象方法,然后去实现他 这样就可以在实例化图形类的时候 把我们新的实现类传递进去了,这有点LSP的味道哦。

装饰模式 Decorator Pattern

动态地给一个对象增加一些额外的职责(Responsibility),就增加对象功能来说,装饰模式比生成子类实现更为灵活。其别名也可以称为包装器(Wrapper),与适配器模式的别名相同,但它们适用于不同的场合。

function decorateArmour(target, key, descriptor) {  
  const method = descriptor.value;
  let moreDef = 100;
  let ret;
  descriptor.value = (...args)=>{
    args[0] += moreDef;
    ret = method.apply(target, args);
    return ret;
  }
  return descriptor;
}

class Man{  
  constructor(def = 2,atk = 3,hp = 3){
    this.init(def,atk,hp);
  }
//ES7的装饰器
  @decorateArmour
  init(def,atk,hp){
    this.def = def; // 防御值
    this.atk = atk;  // 攻击力
    this.hp = hp;  // 血量
  }
  toString(){
    return `防御力:${this.def},攻击力:${this.atk},血量:${this.hp}`;
  }
}

var tony = new Man();

console.log(`当前状态 ===> ${tony}`);  
// 输出:当前状态 ===> 防御力:102,攻击力:3,血量:3

看看我们的超人,本来的基础防御力是2 我们可以通过装饰器方法让他防御变成102

下面我们来说说ES7的装饰器

它的主要作用与装饰者模式类似,是给一个已有的方法或类扩展一些新的行为,而不是去直接修改它本身

外观模式(Facade Pattern)

外部与一个子系统的通信必须通过一个统一的外观对象进行,为子系统中的一组接口提供一个一致的界面,外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用

class FaceShape {
    draw() {
        throw "定义抽象方法"
    }
}
class Rectangle extends FaceShape {
    draw() {
        console.log("Rectangle::draw()")
    }

}
class Square extends FaceShape {
    draw() {
        console.log("Square::draw()")
    }

}
class FCircle extends FaceShape {
    draw() {
        console.log("Circle::draw()")
    }

}
class ShapeMaker {
    constructor() {
        this.circle = new FCircle();
        this.rectangle = new Rectangle();
        this.square = new Square();
    }

    drawCircle() {
        this.circle.draw();
    }
    drawRectangle() {
        this.rectangle.draw();
    }
    drawSquare() {
        this.square.draw();
    }
}
var shapeMaker = new ShapeMaker();
shapeMaker.drawCircle();
shapeMaker.drawRectangle();
shapeMaker.drawSquare();

首先是有一个抽象接口,所有的实现类都需要实现这个抽象接口,然后通过一个大对象暴露出来,让开发人员也很容易记住通过某个对象就可以完成很多事情。

享元模式(Flyweight Pattern)

运用共享技术有效地支持大量细粒度对象的复用。系统只使用少量的对象,而这些对象都很相似,状态变化很小,可以实现对象的多次复用。由于享元模式要求能够共享的对象必须是细粒度对象,因此它又称为轻量级模式,它是一种对象结构型模式。

class Monster{
    constructor(name) {
        this.name = name;
    }
    sayName(){
        console.log(`我的名字是:${this.name}`)
        return this;
    }
}
class MonsterFactory extends Monster{
    createMonster(name){
        return new Monster(name)
    }
}

class GetMonster{
    getM(num){
        let monsters=[];
        let names=["a","b","2","1","e","f","g"];
        let name="";
        for (let i = num; i>=0;i--) {
            name=names[parseInt(Math.random()*7,10)].repeat(2)+names[parseInt(Math.random()*7,10)];
            monsters.push(new MonsterFactory().createMonster(name).sayName() )
        }
        return monsters;
    }
}
console.log(new GetMonster().getM(10))

现在需要做一个小游戏,游戏里面有10个或者100个怪物,我们可以在使用的时候去生成怪物,也可以在游戏加载的初始化中实例化100个怪物,并且把怪物保存在数组中,但是其实屏幕上只存在3个怪物,总计有100个怪物那么 我们产生100个怪物就有些浪费了,所以我们修改下上述代码 让他变成享元模式

class GetMonster{
    getM(num){
        let monsters={};
        let names=["a","b","2","1","e","f","g"];
        let name="";
        for (let i = num; i>=0;i--) {
            name=names[parseInt(Math.random()*7,10)].repeat(2)+names[parseInt(Math.random()*7,10)];
            if(!monsters[name]){
                monsters[name]=new MonsterFactory().createMonster(name).sayName() 
            }
            
        }
        return monsters;
    }
}
console.log(new GetMonster().getM(20))

我们通过对象的key来确定已经有的key不用添加使用的时候就可以复用这个对象了

代理模式(Proxy Pattern)

通过引入一个新的对象(如小图片和远程代理 对象)来实现对真实对象的操作或者将新的对 象作为真实对象的一个替身,这种实现机制即 为代理模式,通过引入代理对象来间接访问一 个对象,这就是代理模式的模式动机


class Images{
    addendImage(src){
    let imgNode = document.createElement('img');
     document.body.appendChild(imgNode);    
     imgNode.src = src;
     return imgNode;
    }
}

class proxyImage {
    addendImage(src){
        let imgNode = new Images().addendImage("./img/test.gif");
        let image = new Image();
        image.src = src;
        image.onload=function(){
            imgNode.src =src;
        }
        
    }
}

new proxyImage().addendImage('http://ww3.sinaimg.cn/mw690/0064cTs2jw1ey2ptjq6aoj30sg0lc12j.jpg');
new proxyImage().addendImage('http://ww1.sinaimg.cn/mw690/0064cTs2jw1ey2pthhfiyj30p00dwgtt.jpg');
new proxyImage().addendImage('http://ww4.sinaimg.cn/mw690/0064cTs2jw1ey2ptmcfa6j30sg0lcan2.jpg');

我们可以先展示本地的一个默认图片,等远程图片返回了在显示真实的图片。

先写到这里 下一期接着写观察者模式和中介者模式 以及一些源码分析

上一篇 下一篇

猜你喜欢

热点阅读