js设计模式[es6]

作者: 扬子拉虫 | 来源:发表于2018-05-04 21:18 被阅读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');
    

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

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

    相关文章

      网友评论

        本文标题:js设计模式[es6]

        本文链接:https://www.haomeiwen.com/subject/jfjjrftx.html