细谈JavaScript中的一些设计模式

@(书籍阅读)[JavaScript, 设计模式]

常见设计模式

一直对设计模式不太懂,花了一下午加一晚上的时间,好好的看了看各种设计模式,并总结了一下。

设计模式简介

  • 设计模式概念解读

  • 设计模式的发展与在JavaScript中的应用

  • 设计原则

设计模式概念解读

设计概念文字解读

设计模式(Design pattern):

>是一套被反复使用,思想成熟,经过分类和无数实战设计经验的总结。使用设计模式是为了让系统代码可重用,可扩展,可解耦,更容易被人理解且能保证代码可靠性。设计模式使代码开发真正工程化;设计模式是软件工程的基石脉络,如同大厦的结构一样。只有夯实地基搭好结构,才能盖出坚壮的大楼。也是我们迈向高级开发人员必经的一步。
设计模式拟物化解读

一层书柜–》两层书柜–》各种书柜分门别类

设计模式的发展与在JavaScript中的运用

设计模式的发展

四人帮的书,设计模式的圣经Gof

JavaScript中的设计模式

编程语言之间是想通的;
很多本该有的东西JavaScript都有,但是并没有作为正式的部分。这些年人们利用自己对计算机编程的思想,利用了很多晦涩的技巧实现了很多JavaScript设计者都未曾预计到的任务,比如各种设计模式的实现,比如面向对象的编程。

设计原则

设计原则详解

设计模式的根本原因是为了代码复用,增加可维护性。有如下原则:

  • 【开闭原则】:对扩展开放,对修改关闭;

  • 【里氏转换原则】:子类继承父类,单独调用完全可以运行;

  • 【依赖倒转原则】:引用一个对象,如果这个对象有底层类型,直接引用底层;

  • 【结构隔离原则】:每一个接口应该是一种角色;

  • 【合成/聚合复用原则】:新对象应该使用一些已有的对象,使之成为新对象的一部分;

  • 【迪米特原则】:一个对象应该对其它对象有尽可能少的了解;
    站在巨人的肩膀上整体HOLD系统架构。

JavaScript设计模式之单例模式

单例模式概念解读

单例模式的文字解读

单例就是保证一个类只有一个实例,实现的方法一般是先判断实例存在与否,如果存在则直接返回,如果不存在就创建了再返回,这就确保了一个类只有一个实例对象。
在JavaScript中,单例作为一个命名空间提供者,从全局命名空间里提供一个唯一的访问点来访问该对象。

单例模式的拟物化解读

比如说一座房子只有一扇门,有一扇门就不用再开门了,如果没有门则需要创建一个门。每个门都只属于一个户主(命名空间),门是户与户之间的唯一接口,你要来我家,只能从这个门进来。
命名空间:

通常来说,命名空间是唯一识别的一套名字,这样当对象来自不同的地方但是名字相同的时候就不会含糊不清了。

单例模式作用和注意事项

模式作用
  1. 模块间通信;

  2. 系统中某个类的对象只能存在一个;

  3. 保护自己的属性和方法(不受外面的干扰);

注意事项:
  1. 注意this的使用:随着调用不断变化,谁调用指向谁;

  2. 闭包容易造成内存泄露,不需要的赶快干掉:闭包就是拿到不该拿到的东西,return;

  3. 注意new的成本。(继承)

单例模式代码实战和总结

var single = (function() {
    var unique;
    function getInstance() {
        if (unique === undefined) {
            unique = new Construct();
        }
        return unique;
    }
    function Construct() {
        // ... 生成单例的构造函数的代码
    }
    return {
        getInstance: getInstance
    }

})();

构造函数模式

构造模式概念解读

文字解读

构造函数用于创建特定类型的对象-不仅声明了使用的对象,构造函数还可以接收参数以便第一次创建对象的时候设置对象的成员值。可以自定义构造函数,然后在里面声明自定义类型对象的属性和方法;
在JavaScript里,构造函数通常是认为用来实现实例的,JavaScript没有类的概念,但是有特殊的构造函数。通过new关键字来调用自定义的构造函数,在构造函数内部,this关键字引用的是新创建的对象。

拟物化解读

每家都有一个门,但是每个门都有自己的特点,可以根据自己的需要添加不同的特点(传入不同的值)。

构造模式作用和注意事项

模式作用:
  1. 用于创建特点类型的对象:给某一个人

  2. 第一次声明的时候给对象赋值;

  3. 自己声明构造函数,赋予属性和方法;(告诉别人自己想要怎么样的门。)

注意事项
  1. 声明函数时候处理业务逻辑;

  2. 区分和单例的区别,配合单例实现初始化;

  3. 构造函数大写字母开头(推荐~)

  4. 注意new的成本。(继承)(相同的尽量放在原型链上)

构造模式实战与总结

最简单的构造函数:

function Car(model, year, miles) {
    this.model = model;
    this.year = year;
    this.miles = miles;
    this.output= function () {
        return this.model + "走了" + this.miles + "公里";
    };
}

var tom= new Car("大叔", 2009, 20000);
var dudu= new Car("Dudu", 2010, 5000);

console.log(tom.output());
console.log(dudu.output());

强制使用new的方法:

function Car(model, year, miles) {
    if (!(this instanceof Car)) {
        return new Car(model, year, miles);
    }
    this.model = model;
    this.year = year;
    this.miles = miles;
    this.output = function () {
        return this.model + "走了" + this.miles + "公里";
    }
}

var tom = new Car("大叔", 2009, 20000);
var dudu = Car("Dudu", 2010, 5000);

console.log(typeof tom); // "object"
console.log(tom.output()); // "大叔走了20000公里"
console.log(typeof dudu); // "object"
console.log(dudu.output()); // "Dudu走了5000公里"

建造者模式

建造者模式概念解读

建造者模式文字解读

建造者模式可以将一个复杂对象的构建与其表示相分离,使得同样的构建过程可以创建不同的表示。也就是说,如果我们用了建造者模式,那么用户就需要指定需要建造的类型就可以得到它们,而具体建造的过程和细节就不需要知道了。建造者模式实际,就是一个指挥者,一个建造者,一个使用指挥者调用具体建造者工作得出结果给客户。
建造者模式主要用于“分步骤构建一个复杂的对象”,“分步骤”是一个稳定的算法,而复杂对象的各个部分则经常变化。

建造者模式拟物化解读

《细谈JavaScript中的一些设计模式》
说一户家人要建房子,但房子主人或家里其他人是不懂得如何去建房子的,所以他得去请几个工人,这个建房子的队伍还得有个工头,来按房主人的想法来建一套房子,工头按房主人的要求设计要求工人如何如何做;

工头说,第一步先把房整体骨架搭起来,第二步睡房建造好,第三步把厨房装饰好,第四步把客厅建造装饰完毕,第五步…

工头是不做事的,但具体建造者必须按照工头的要求来做,第一步,第二步的这样步骤来建造,直至整个房子完成;

创建者必须要有创建这个房屋的所有技能,即建骨架,装饰睡房等…,即建造者所做的事,或所具有的能力,必须大于或等于指挥者要求要做的事,或具有的能力;

即指挥者是个组织者,而建造者提供技能;

建造者模式作用和注意事项

模式作用:

  1. 分布构建一个复杂的对象(包工头对工人,工人对房子);

  2. 解耦封装过程和具体构建的组件;

  3. 无需关心组件如何组装;

  4. 各司其职,拆解流程;

注意事项:

  1. 一定要有一个稳定的算法进行支持(合同);

  2. 加工工艺是暴露的(可以直接找工人,但是包工头更熟悉);

建造者模式代码实战和总结

  1. 工人建造者X:

function workerBuilder() {
    this.workOne = function() {
         //建房子骨架
    }
    this.workTwo=function() {
         //建睡房
    }
    this.workThree=function() {
         //建厨房
    }
    this.workFour=function() {
         //建客厅
    }
    //....
    
    this.getResult = function() {
         //建成房子
     var house = new House();
     //house.HouseFrame ...
     return house; 
    }
}

workBuilder 是具体建造者类,workOne, Two是要做的事情,建骨架等;

当然 workBuilder 可以多建几个,用于表示 工人 对于每个工作执行的方法不一样;但工作内容是一样的;

  1. 指挥者类

function Director() {
     this.construct = function(builder) {
          builder.workOne();
          builder.workTwo();
          builder.workThree();
          builder.workFour();
          //...
          //上面的内容,顺序可设置,并且工作项也可以设定
     }
}
 

指挥者类下的 指导 方法,有对 建造者 的回调引用,内容包括建者工作内容几项或全部; 指挥者对建造者工人要做的事情进行组织跟安排;

  1. 产品房子

function House() {
    this.HouseFrame = '';
    this.Room = '';
    this.Kitchen = '';
    this.LivingRoom = '';
    //...
}
 
  1. 使用方法

var builder = new workBuilder();
var director = new Director();
director.construct(builder);
var house = builder.getResult();

第四步,整个使用相当于客户:房主人,房主人请 Director 工头来建房子,但是工头是不做事的,所以他指挥 builder 工个来建子,最后房主人从工人那里取得建好的房子;

工厂模式

工厂模式概念解读

文字解读

工厂模式定义了一个用于创建对象的接口,这个接口决定了实例化哪一个类。该模式使一个类的实例化延迟到了子类。而子类可以重写接口方法以便创建的时候指定自己的对象类型(抽象工厂)。(简单工厂:能找到具体细节);抽象工厂只留口,不做事,留给外界覆盖;
这个模式十分有用,尤其是创建对象的流程赋值的时候,比如依赖于很多设置文件等。并且,会经常在程序里看到工厂方法,用于让子类定义需要创建的对象类型。
简单工厂模式:使用一个类(通常为单体)来生成实例。
复杂工厂模式:使用子类来决定一个成员变量应该是哪个具体的类的实例。

拟物化解读

厂里可以做衣服可以做鞋,按订单来生产那种东西,通过厂长安排那条产品线,外界可以修改产品线工艺(抽象工厂)。厂长只是告诉做什么事,

《细谈JavaScript中的一些设计模式》

工厂模式的作用和注意事项

作用:

  1. 对象的构建十分复杂;

  2. 需要依赖具体的环境创建不同实例;

  3. 处理大量具有相同属性的小对象;

注意事项:

  1. 不能滥用工厂,有时候仅仅是给代码增加复杂度;

工厂模式代码实战

简单工厂模式:

var BicycleFactory = {
    createBicycle : function( model ){
        var bicycle;
        switch( model ){
            case "The Speedster":
                bicycle = new Speedster();
                break;
            case "The Lowrider":
                bicycle = new Lowrider();
                break;
            case "The Cruiser":
            default:
                bicycle = new Cruiser();
                break;
        }
        return bycicle;
    }
}

BicycleFactory 是一个脱离于BicycleShop的单体。降低耦合度的效果显而易见。当需要添加新的类型的时候,不需要动 BicycleShop 只需修改工厂单体对象就可以。

var BicycleShop = function(){};

BicycleShop.prototype = {
    sellBicycle : function( model ){
        var bicycle = BicycleFactory.createBicycle(model);     
        return bicycle;
    }
}

工厂模式:
真正的工厂模式与简单工厂模式相比,主要区别就是它不是另外使用一个对象或者类来创建实例(自行车),而是使用一个子类。工厂是一个将其成员对象的实例化推迟到子类中进行的类。

比如加入BicycleShop可以决定从那一家厂商进行进货,那么简单的一个BicycleFactory是不够了的,因为各个厂商会各自生产不同的Speedster,Lowrider,Cruiser等型号自行车,所以首先需要生成各自厂商的shop实例,不同厂商的shop实例拥有不同的生成几个型号自行车的方法。

也就是相当于将自行车对象的实例化推迟到了shop实例中产生。

基础:
var BicycleShop = function(){}
BicycleShop.prototype={
    sellBicycle: function( model ){
        var bicycle = this.createBicycle( model );
        return bicycle;
    },
    createBicycle: function( model ){
        throw new Error( " Unsupported " );
    }
}

各自厂商:

var AcmeBicycleShop = function(){};

extend( AcmeBicycleShop , BicycleShop );
AcmeBicycleShop.prototype.createBicycle = function( model ){
    var bicycle;
    switch( model ){
        case "The Speedster":
            bicycle = new AcmeSpeedster();
            break;
        case "The Lowrider":
            bicycle = new AcmeLowrider();
            break;
        case "The Cruiser":
        default:
            bicycle = new AcmeCruiser();
            break;
    }
    return bicycle;
}

var GeneralBicycleShop = function(){};

extend( GeneralBicycleShop , BicycleShop );
GeneralBicycleShop.prototype.createBicycle = function( model ){
   ...
}

那么接下来就很简单 对于来自 Acme 进货的

    var acmeShop = new AcmeBicycleShop();
    var newBicycle = acmeShop.sellBicycle("The Speedster");

当然,你也可以对于外层生成的子类实例在使用简单工厂模式进行包装一下~对于添加其他厂商也很简单,在创建一个Bicycle的子类重新定义其createBicycle的工厂方法即可。

工厂模式使用场合

    1. 动态实现
      例如自行车的例子,创建一些用不同方式实现统一接口的对象,那么可以使用一个工厂方法或者简单工厂对象来简化实现过程。选择可以是明确进行的也可以是隐含的。

    1. 节省设置开销
      如果对象要进行复杂的并且彼此相关的设置的时候,那么工厂模式可以很显著的减少每种对象的代码量。将特定的设置代码提取出来会使得代码有极大地提升。并且能优化结构便于维护。

    2. 用于许多小型对象组成一个大对象。

    3. 工厂模式之利
      主要好处就是可以消除对象间的耦合,通过使用工程方法而不是new关键字。将所有实例化的代码集中在一个位子防止代码重复。

    4. 工厂模式之弊
      大多数类最好使用new关键字和构造函数,可以让代码更加简单易读。而不必去查看工厂方法来知道。

    代理模式

    代理模式概念解读

    代理模式文字解读

    代理:顾名思义就是帮组别人做事,GoF对代理模式的定义如下:
    代理模式(Proxy),为其它对象提供一种代理以控制对这个对象的访问。代理模式是的代理对象控制具体对象的引用。代理几乎可以是任何对象:文件,资源,内存中的对象,或者是一些难以复制的东西。

    代理模式拟物化解读

    《细谈JavaScript中的一些设计模式》

    • 中介

    • 经理把收集日报周报反馈给大老板;

    • 卡扣通过中间的黄色东西连接在一起;

    代理模式作用和注意点

    模式作用:

    1. 远程代理(一个对象将不同空间的对象进行局部代理);

    2. 虚拟代理(根据需要创建开销很大的对象如渲染网页暂时用占位符替代真图);

    3. 安全代理(控制真实对象的访问权限);

    4. 智能指引(调用对象代理处理另外一些事情如垃圾回收机制);

    注意事项:

    1. 不能滥用代理,有时候仅仅是给代码增加复杂度;

    代理模式实战

    代理模式需要三方:
    小v和狼猫有点误会,由大熊君帮着缓和一下误会慢慢解除了。

    function XiaoV(){
         this.talk = function(){
             console.log("狼猫老弟,不好意思上次的事,请多多谅解。") ;
         } ;
     } ;
     function LangMao(){
         this.bb = new BigBear() ;
         this.talk = function(){
             console.log("大熊君好啊,最忌忙什么那?") ;
             this.bb.talk() ;
         } ;
     } ;
     function BigBear(){
         this.xiaov = new XiaoV() ;
         this.talk = function(){
            console.log("狼猫兄弟,中午没事一起吃顿饭聊聊天,那天我见到小v了,他优化和你说。。。。。。") ;
             this.xiaov.talk() ;
         } ;
     } ;
     function go(){
     new LangMao().talk() ;
     } ;

    抽象角色:声明真实对象和代理对象的共同接口。

    代理角色:代理对象角色内部含有对真实对象的引用,从而可以操作真实对象,同时代理对象提供与真实对象相同的接口以便在任何时刻都能代替真实对象。同时,代理对象可以在执行真实对象操作时,附加其他的操作,相当于对真实对象进行封装。

    真实角色:代理角色所代表的真实对象,是我们最终要引用的对象。

    代理模式的一个好处就是对外部提供统一的接口方法,而代理类在接口中实现对真实类的附加操作行为,从而可以在不影响外部调用情况下,进行系统扩展。也就是说,我要修改真实角色的操作的时候,尽量不要修改他,而是在外部在“包”一层进行附加行为,即代理类。

    代理模式使用场景
    1. 当我们需要使用的对象很复杂或者需要很长时间去构造,这时就可以使用代理模式(Proxy)。例如:如果构建一个对象很耗费时间和计算机资源,代理模式(Proxy)允许我们控制这种情况,直到我们需要使用实际的对象。一个代理(Proxy)通常包含和将要使用的对象同样的方法,一旦开始使用这个对象,这些方法将通过代理(Proxy)传递给实际的对象。 一些可以使用代理模式(Proxy)的情况:

    2. 一个对象,比如一幅很大的图像,需要载入的时间很长。

    3. 一个需要很长时间才可以完成的计算结果,并且需要在它计算过程中显示中间结果

    4. 一个存在于远程计算机上的对象,需要通过网络载入这个远程对象则需要很长时间,特别是在网络传输高峰期。

    5. 一个对象只有有限的访问权限,代理模式(Proxy)可以验证用户的权限

    命令模式

    概念解读

    文字解读

    命令模式(Command):
    用来对方法调用进行参数化处理和传送,经过这样处理过的方法调用可以在任何时候执行。也就是说该模式旨在将函数的调用,请求和操作封装成一个单一的对象,然后对这个对象进行一系列的处理。它也可以用来消除调用操作的对象和实现操作的对象之间的耦合。这为各种具体的类的更换带来了极大的灵活性。

    代理模式作用于注意事项

    模式作用:

    1. 将函数的封装,请求,调用结合为一体;

    2. 调用具体的函数解耦命令对象和接收对象;

    3. 提高程序模块的灵活性;

    注意事项:

    1. 不需要借口一致,直接调用函数即可,以免造成浪费;

    实战

    // 命令

    var CreateCommand = function( receiver ){  
              this.receiver = receiver;  
            }  
            CreateCommand.prototype.execute = function() {  
              this.receiver.action();  
            } 

    // 接收者 电视

    var TVOn = function() {}  
       TVOn.prototype.action = function() {  
           alert("TVOn");  
       }  

    // 接收者 电视

    var TVOff = function() {}  
            TVOff.prototype.action = function() {  
                alert("TVOff");  
            }  

    // 调用者 遥控器

    var Invoker = function( tvOnCommand, tvOffCommand ) {  
               this.tvOnCommand = tvOnCommand;  
               this.tvOffCommand = tvOffCommand;  
             }  
             Invoker.prototype.tvOn = function() {  
               this.tvOnCommand.execute();  
             }  
             Invoker.prototype.tvOff = function() {  
               this.tvOffCommand.execute();  
             }  

    执行Client

    var tvOnCommand = new CreateCommand( new TVOn() );  
            var tvOffCommand = new CreateCommand( new TVOff() );  
            var invoker = new Invoker( tvOnCommand, tvOffCommand );  
            invoker.tvOn();  
            invoker.tvOff();  

    参考文献

    1. 深入理解JavaScript系列(26):设计模式之构造函数模式

    2. [设计模式] javascript 之 建造者模式

    3. JS设计模式二:工厂模式

    4. 大熊君说说JS与设计模式之——代理模式Proxy

    5. 【JS设计模式】命令模式

    6. 极客学院《JS设计模式》系列视频

        原文作者:设计模式
        原文地址: https://segmentfault.com/a/1190000004568177
        本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
    点赞