JavaScript设计模式 --- 封装和信息隐藏

封装和信息隐藏

先说几个概念:

私有方法

  • 可以访问类的所有属性,包括公有属性和私有属性。
  • 但是不可以在类的外部调用。

公有方法

  • 可以在类的外部调用
  • 不可以访问类的私有属性
  • 公有方法必须在类的内部或者外部通过 prototype属性添加

特权方法

  • 可以在类的外部调用
  • 可以访问私有属性和公有属性,可以认为是一张特殊的公有方法
  • 特权方法必须在类的内容声明定义。

静态方法

  • 属于类本身的方法
  • 对象实例不能调用对象的静态方法


信息隐藏原则


封装与信息隐藏

封装 可以被定义为对对象的内部数据表现形式和实现细节进行隐藏。

创建对象的基本模式


JavaScript中创建对象的基本模式有3种:
第一种 门户大开型最简单的一种方式,但只提供公共成员。
第二种 使用下划线来表示方法和属性的私用性。
第三种 使用闭包来创建真正的私用成员,这些成员只能通过一些特权方法。

以Book类为例,假设你接到一项任务,用来储存关于一本书的数据的类,并为其实现一个以HTML形式显示这些数据的方法。

// Book(isbn, title, author)
var theHobbit = new Book("0-395-07122-4", "The Hobbit", "J.R.R.Tolkien ");
theHobbit.display();        // Outputs the data by creating and populating an HTML element. 


门户大开型对象

实现 Book类最简单的做法是按传统方式创建一个类,用一个类来做其构造器。我们称其为门户大开型对象,因为所有属性和方法都是公开的、可访问的。这些公有属性使用 this关键字创建:

var Book = function(isbn, title, author){
    if(isbn == undefined) throw new Error("Book constructor requires an isbn.");
    this.isbn = isbn;
    this.title = title || "No title specified";
    this.author = author || "No author specified";
}

Book.prototype.display = function(){
    ...
};

以上这段代码,最大的问题就是无法检验 ISBN数据的完整性,而不完整的 ISBN数据有可能导致 display方法失灵,下面版本强化了对 ISBN的检查:

var Book = function(isbn, title, author){
    if(isbn == undefined) throw new Error("Book constructor requires an isbn.");
    this.isbn = isbn;
    this.title = title || "No title specified";
    this.author = author || "No author specified";
}

Book.prototype = {
    checkIsbn : function(isbn){
        if(isbn == undefined || typeof isbn != "string"){
            return false;
        }

        isbn = isbn.replace(/-/, "");   // Remove dashes.
        if(isbn.length != 10 && isbn.length != 13){
            return false;
        }

        var sum = 0;
        if(isbn.length === 10){
            if(!isbn.match(/^\d{9}/)){     // Ensure characters 1 through 9 are digits 确保是数字
                return false;
            }

            for(var i = 0; i < 9; i++){
                sum += isbn.charAt(i) * (10 - i);
            }
            var checksum = sum % 11;
            if(checksum === 10) checksum = "X";
            if(isbn.charAt(9) != checksum){
                return false;
            }
        } else {    // 13 digit ISBN
            ...
        }

        return true;     // All tests passed;
    },

    display : function(){
        ...
    }
};



下面是加入取值器和赋值器的新版Book对象,方便控制 isbn属性。

var Publication = new Interface("Publiccation", ["getIsbn", "setIsbn", "getTitle", "setTitle", "getAuthor", "setAuthor", "display"]);

var Book = function(isbn, title, author){
    this.setIsbn(isbn);
    this.setTitle(title);
    this.setAuthor(author);
}

Book.prototype = {
    checkIsbn : function(isbn){
        ...
    },
    getIsbn : function(){
        return this.isbn;
    },
    setIsbn : function(){
        if(!this.checkIsbn(isbn)) throw new Error("Book : Invalid ISBN");
        this.isbn = isbn;
    },

    getTitle : function(){
        return this.title;
    },
    setTitle : function(title){
        this.title = title || "No title specified";
    },

    getAuthor : function(){
        return this.author;
    },
    setAuthor : function(){
        this.author = author || "No author specified";
    },

    display : function(){
        ...
    }
};

这种门户大开型创建对象的方式,
优点:易于使用,方法和属性都是公开的,派生子类和进行单元测试很方便;
缺点:无法保护内部数据。

用命名规范区别私有成员

使用命名规范来模仿私用成员的模式,与门户大开型类似,只是在一些方法前加“_”来表示私用性。

var Book = function(isbn, title, author){
    if(isbn == undefined) throw new Error("Book constructor requires an isbn.");
    this.isbn = isbn;
    this.title = title || "No title specified";
    this.author = author || "No author specified";
}

var Publication = new Interface("Publiccation", ["getIsbn", "setIsbn", "getTitle", "setTitle", "getAuthor", "setAuthor", "display"]);

var Book = function(isbn, title, author){
    this.setIsbn(isbn);
    this.setTitle(title);
    this.setAuthor(author);
}

Book.prototype = {
    _checkIsbn : function(isbn){
        ...
    },
    getIsbn : function(){
        return this._isbn;
    },
    setIsbn : function(){
        if(!this._checkIsbn(isbn)) throw new Error("Book : Invalid ISBN");
        this._isbn = isbn;
    },

    getTitle : function(){
        return this._title;
    },
    setTitle : function(title){
        this._title = title || "No title specified";
    },

    getAuthor : function(){
        return this._author;
    },
    setAuthor : function(){
        this._author = author || "No author specified";
    },

    display : function(){
        ...
    }
};

下划线表明,一个属性(或方法)仅供对象内部使用,直接访问它或者设置它可能会导致意想不到的后果。

作用域、嵌套函数和闭包

在JavaScript中,只有函数具有作用域,在函数内容声明的变量在函数外部无法访问。私用属性就其本质而言在对象外部无法访问。定义在函数中的变量在函数内嵌函数中可以访问。比如:

function foo(){
    var a = 10;

    function bar(){
        a *= 2;
    }
    bar();

    return a;
}

var baz = foo();    // baz现在引用 bar;
baz();      // 返回20
baz();      // 返回40
baz();      // 返回80

var blat = foo();   // blat是 bar的另一个引用
blaz();     // 返回20, 因为这是 bar的一个新的复制


用闭包实现私用成员

借助闭包可以创建只允许特定函数访问的变量,并且这些变量在这些函数的各次调用之间仍然存在。
为了创建私用变量,你需要在构造器函数的作用域中定义相关变量,这些变量可以被定义于该作用域中的所有函数访问,包括特权方法:

var Book = function(newIsbn, newTitle, new Author){
    // Private attributes.
    var isbn, title, author;

    // Private methods.
    function checkIsbn(isbn){
        ...
    }

    // Privileged methods.
    this.getIsbn = function(){
        return isbn;
    };
    this,setIsbn = function(newIsbn){
        if(!checkIsbn(newIsbn)) throw new Error("Book : Invalid ISBN");
        isbn = newIsbn;
    };

    this.getTitle = function(){
        return title;
    };
    this.setTitle = function(newTitle){
        title = newTitle || "No title specified";
    };

    this.getAuthor = function(){
        return author;
    };
    this.setAuthor = function(newAuthor){
        author = newAuthor || "No author specified";
    };

    // Constructor code.
    this.setIsbn(newIsbn);
    this.setTitle(newTitle);
    this.setAuthor(newAuthor);
};

// Public, non-privileged methods.
Book.prototype = {
    display : function(){
        ...
    }
};


更多高级对象创建模式


静态方法和属性

作用域和闭包的概念可用于创建静态成员,包括公用的和私用的。大多数方法和属性所关联的是类的实例,而静态成员所关联的是类本身。静态成员是直接通过类对象访问。

下面是添加了静态属性和方法的Book类:

var Book = (function(){

    // Private static attributes.
    var numOfBooks = 0;

    // Private static method.
    function checkIsbn(isbn){
        ...
    }

    // Return the constructor.
    return function(newIsbn, newTitle, newAuthor){

        // Private attributes.
        var isbn, title, author;

        // Privileged methods.
        this.getIsbn = function(){
            return isbn;
        };
        this.setIsbn = function(newIsbn){
            if(!checkIsbn(newIsbn)) throw new Error("Book : Invalid ISBN");
            isbn = newIsbn;
        };

        this.getTitle = function(){
            return title;
        };
        this.setTitle = function(newTitle){
            title = newTitle || "No title specified";
        };

        this.getAuthor = function(){
            return author;
        };
        this.setAuthor = function(newAuthor){
            author = newAuthor || "No author specified";
        };

        // Constructor code.
        numOfBooks++;      // 通过公有静态属性来监听 Book被实例化的数量
        if(numOfBooks > 50) throw new Error("Book : Only 50 instances of Book can be " + " created.");

        this.setIsbn(newIsbn);
        this.setTitle(newTitle);
        this.setAuthor(newAuthor);
    }
})();

// Public static method.
Book.convertToTitleCase = function(){
    ...
};

// Public, non-privileged methods.
Book.prototype = {
    display : function(){
        ...
    }
};


常量

常量是一些不能被修改的变量,在JavaScript中,可以通过创建只有取值器没有赋值器的私用变量来模仿常量。

假设Class对象有一个名为 UPPER_BOUND的常量,为获取这个常量而进项的方法调用如下:

Class.getUPPER_BOUND();

// 为实现这个取值器,需要使用特权静态方法
var Class = (function(){
    // 常量(把它当成私有静态属性创建)
    var UPPER_BOUND = 100;
    // Constructor.
    var ctor = function(constructorArgument){
        ..
    }

    // Privileged static method.
    ctor.getUPPER_BOUND = function(){
        return UPPER_BOUND;
    };

    ...
    // Return the constructor
    return ctor;
})();

如果需要很多常量,可以创建一个通用的取值器方法:

var Class = (function(){
    // Private static attribute.
    var constants = {
        UPPER_BOUND : 100,
        LOWER_BOUND : -100
    };

    // Constructor
    var ctor = function(){
        ...
    };
    ctor.getConstants = function(name){]
        return constants[name];
    };

    ...
    // Return the constructor
    return ctor;
})();

// 然后通过使用这个取值器以或得一个常量
Class.getConstant("UPPER_BOUND"); 


单体和工厂模式

单体模式 使用一个由外部函数返回的对象字面来公开特权成员,私用成员封装在外层函数的作用域中。前面的例子都返回的是一个函数,单体模式外层函数返回的是一个对象字面量

工厂模式 使用闭包来创建具有私用成员的对象。最简形式就是一个类构造器。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值