我是靠谱客的博主 不安小蘑菇,最近开发中收集的这篇文章主要介绍JS中设计模式的深入理解概念构造器模式模块化模式暴露模块模式单例模式观察者模式中介者模式原型模式命令模式外观模式工厂模式Mixin 模式装饰模式亨元(Flyweight)模式,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

概念

    设计模式是可重用的用于解决软件设计中一般问题的方案

构造器模式

    在面向对象编程中,构造器是一个当新建对象的内存被分配后,用来初始化该对象的一个特殊函数。在Javascript中几乎所有的东西都是对象,我们经常会对对象的构造器十分感兴趣。
    对象构造器是被用来创建特殊类型的对象的,首先它要准备使用的对象,其次在对象初次被创建时,通过接收参数,构造器要用来对成员的属性和方法进行赋值。

  • 创建对象的三种基本方式
1. var newObject = {}
2. var newObject = Object.create(null)
3. var newObject = new Object()
// Object 构造器创建了一个针对特殊值的对象包装,这里没有传值给它,它将会返回一个空对象
  • 将一个键值对赋给一个对象的四种方式
// ECMAScript 3 兼容形式
// 1. “点号”法
// 设置属性
newObject.someKey = "Hello World"
// 获取属性
var key = newObject.someKey
// 2. “方括号”法
// 设置属性
newObject["someKey"] = "Hello World"
// 获取属性
var key = newObject["someKey"]
// ECMAScript 5 仅兼容性形式
// For more information see: http://kangax.github.com/es5-compat-table/
// 3. Object.defineProperty方式
// 设置属性
Object.defineProperty(newObject, "someKey", {
value: "for more control of the property's behavior",
writable: true,
enumerable: true,
configurable: true
})
// 如果上面的方式你感到难以阅读,可以简短的写成下面这样:
var defineProp = function(obj, key, value ) {
config.value = value
Object.defineProperty(obj, key, config)
}
// 为了使用它,我们要创建一个“person”对象
var person = Object.create( null )
// 用属性构造对象
defineProp(person, "car",
"Delorean")
defineProp(person, "dateOfBirth", "1981")
defineProp(person, "hasBeard", false)
// 4. Object.defineProperties方式
// 设置属性
Object.defineProperties(newObject, {
"someKey": {
value: "Hello World",
writable: true
},
"anotherKey": {
value: "Foo bar",
writable: false
}
})
// 3和4中的读取属行可用1和2中的任意一种
  • 基础构造器
function Car(model, year, miles) {
this.model = model
this.year = year
this.miles = miles
this.toString = function() {
return this.model + " has done " + this.miles + " miles"
}
}
// 使用:
// 我们可以示例化一个Car
var civic = new Car("Honda Civic", 2009, 20000)
var mondeo = new Car("Ford Mondeo", 2010, 5000)
// 打开浏览器控制台查看这些对象toString()方法的输出值
// output of the toString() method being called on
// these objects
console.log(civic.toString())
console.log(mondeo.toString())
  • 使用 “原型” 的构造器
function Car(model, year, miles) {
this.model = model
this.year = year
this.miles = miles
}
// 注意这里我们使用Note here that we are using Object.prototype.newMethod 而不是
// Object.prototype ,以避免我们重新定义原型对象
Car.prototype.toString = function() {
return this.model + " has done " + this.miles + " miles";
}
// 使用:
var civic = new Car("Honda Civic", 2009, 20000 )
var mondeo = new Car("Ford Mondeo", 2010, 5000 )
console.log(civic.toString())
console.log(mondeo.toString())

模块化模式

    模块化模式最初被定义为一种对传统软件工程中的类提供私有和公共封装的方法。
    在JavaScript中,模块化模式用来进一步模拟类的概念,通过这样一种方式:我们可以在一个单一的对象中包含公共/私有的方法和变量,从而从全局范围中屏蔽特定的部分。这个结果是可以减少我们的函数名称与在页面中其他脚本区域定义的函数名称冲突的可能性。

  • 私有信息

    模块模式使用闭包的方式来将"私有信息",状态和组织结构封装起来。提供了一种将公有和私有方法,变量封装混合在一起的方式,这种方式防止内部信息泄露到全局中,从而避免了和其它开发者接口发生冲图的可能性。在这种模式下只有公有的API 会返回,其它将全部保留在闭包的私有空间中。
    这种方法提供了一个比较清晰的解决方案,在只暴露一个接口供其它部分使用的情况下,将执行繁重任务的逻辑保护起来。这个模式非常类似于立即调用函数式表达式(IIFE-查看命名空间相关章节获取更多信息),但是这种模式返回的是对象,而立即调用函数表达式返回的是一个函数。
    需要注意的是,在javascript事实上没有一个显式的真正意义上的"私有性"概念,因为与传统语言不同,javascript没有访问修饰符。从技术上讲,变量不能被声明为公有的或者私有的,因此我们使用函数域的方式去模拟这个概念。在模块模式中,因为闭包的缘故,声明的变量或者方法只在模块内部有效。在返回对象中定义的变量或者方法可以供任何人使用。

var basketModule = (function () {
// privates
var basket = []
function doSomethingPrivate() {
//...
}
function doSomethingElsePrivate() {
//...
}
// Return an object exposed to the public
return {
// Add items to our basket
addItem: function( values ) {
basket.push(values);
},
// Get the count of items in the basket
getItemCount: function () {
return basket.length;
},
// Public alias to a
private function
doSomething: doSomethingPrivate,
// Get the total value of items in the basket
getTotal: function () {
var q = this.getItemCount(), p = 0
while (q--) {
p += basket[q].price;
}
return p
}
}
}())

    在模块内部,你可能注意到我们返回了应外一个对象。这个自动赋值给了basketModule 因此我们可以这样和这个对象交互。

// basketModule returns an object with a public API we can use
basketModule.addItem({
item: "bread",
price: 0.5
})
basketModule.addItem({
item: "butter",
price: 0.3
})
// Outputs: 2
console.log(basketModule.getItemCount())
// Outputs: 0.8
console.log(basketModule.getTotal())
// However, the following will not work:
// Outputs: undefined
// This is because the basket itself is not exposed as a part of our
// the public API
console.log(basketModule.basket)
// This also won't work as it only exists within the scope of our
// basketModule closure, but not the returned public object
console.log(basket)
  • 优势

    1. 从javascript语言上来讲,模块模式相对于真正的封装概念更清晰
    2. 模块模式支持私有数据-因此,在模块模式中,公共部分代码可以访问私有数据,但是在模块外部,不能访问类的私有部分

  • 缺点

    1. 模块模式的缺点是因为我们采用不同的方式访问公有和私有成员,因此当我们想要改变这些成员的可见性的时候,我们不得不在所有使用这些成员的地方修改代码。
    2. 我们也不能在对象之后添加的方法里面访问这些私有变量。也就是说,很多情况下,模块模式很有用,并且当使用正确的时候,潜在地可以改善我们代码的结构。
    3. 其它缺点包括不能为私有成员创建自动化的单元测试,以及在紧急修复bug时所带来的额外的复杂性。根本没有可能可以对私有成员打补丁。

暴露模块模式

    在这个模式中,我们可以简单地在私有域中定义我们所有的函数和变量,并且返回一个匿名对象,这个对象包含有一些指针,这些指针指向我们想要暴露出来的私有成员,使这些私有成员公有化。
    这个模式可以用于将私有函数和属性以更加规范的命名方式展现出来。

var myRevealingModule = function () {
var privateCounter = 0
function privateFunction() {
privateCounter++
}
function publicFunction() {
publicIncrement()
}
function publicIncrement() {
privateFunction()
}
function publicGetCount(){
return privateCounter
}
// Reveal public pointers to
// private functions and properties

return {
start: publicFunction,
increment: publicIncrement,
count: publicGetCount
}
}()
myRevealingModule.start()
  • 优势

    这个模式是我们脚本的语法更加一致。同样在模块的最后关于那些函数和变量可以被公共访问也变得更加清晰,增强了可读性

  • 缺点

    1. 这个模式的一个缺点是如果私有函数需要使用公有函数,那么这个公有函数在需要打补丁的时候就不能被重载。因为私有函数仍然使用的是私有的实现,并且这个模式不能用于公有成员,只用于函数。
    2. 公有成员使用私有成员也遵循上面不能打补丁的规则。
    3. 使用暴露式模块模式创建的模块相对于原始的模块模式更容易出问题,因此在使用的时候需要小心。

单例模式

    单例模式之所以这么叫,是因为它限制一个类只能有一个实例化对象。经典的实现方式是,创建一个类,这个类包含一个方法,这个方法在没有对象存在的情况下,将会创建一个新的实例对象。如果对象存在,这个方法只是返回这个对象的引用。
    单例和静态类不同,因为我们可以退出单例的初始化时间。通常这样做是因为,在初始化的时候需要一些额外的信息,而这些信息在声明的时候无法得知。对于并不知晓对单例模式引用的代码来讲,单例模式没有为它们提供一种方式可以简单的获取单例模式。这是因为,单例模式既不返回对象也不返回类,它只返回一种结构。可以类比闭包中的变量不是闭包-提供闭包的函数域是闭包(绕进去了)。
    在JavaScript语言中, 单例服务作为一个从全局空间的代码实现中隔离出来共享的资源空间是为了提供一个单独的函数访问指针。

var myBadSingleton = (function() {
// 存储单例实例的引用
var instance
function init() {
// 单例
var privateRandomNumber = Math.random()
return {
getRandomNumber: function() {
return privateRandomNumber
}
}
}
return {
// 总是创建一个新的实例
getInstance: function () {
instance = init();
return instance;
}
}
})()
// 使用:
var singleA = mySingleton.getInstance()
var singleB = mySingleton.getInstance()
console.log( singleA.getRandomNumber() === singleB.getRandomNumber() ) // true
var badSingleA = myBadSingleton.getInstance()
var badSingleB = myBadSingleton.getInstance()
console.log( badSingleA.getRandomNumber() !== badSingleB.getRandomNumber() ) // true
  • 每个类只有一个实例,这个实例必须通过一个广为人知的接口,来被客户访问。
  • 子类如果要扩展这个唯一的实例,客户可以不用修改代码就能使用这个扩展后的实例。

观察者模式

    观察者模式是这样一种设计模式。一个被称作被观察者的对象,维护一组被称为观察者的对象,这些对象依赖于被观察者,被观察者自动将自身的状态的任何变化通知给它们。
    当一个被观察者需要将一些变化通知给观察者的时候,它将采用广播的方式,这条广播可能包含特定于这条通知的一些数据。
    当特定的观察者不再需要接受来自于它所注册的被观察者的通知的时候,被观察者可以将其从所维护的组中删除。 在这里提及一下设计模式现有的定义很有必要。这个定义是与所使用的语言无关的。通过这个定义,最终我们可以更深层次地了解到设计模式如何使用以及其优势。在四人帮的《设计模式:可重用的面向对象软件的元素》这本书中,是这样定义观察者模式的:
    一个或者更多的观察者对一个被观察者的状态感兴趣,将自身的这种兴趣通过附着自身的方式注册在被观察者身上。当被观察者发生变化,而这种便可也是观察者所关心的,就会产生一个通知,这个通知将会被送出去,最后将会调用每个观察者的更新方法。当观察者不在对被观察者的状态感兴趣的时候,它们只需要简单的将自身剥离即可。

var ObserverEvent = (function () {
var clientList = [], listen, trigger, remove
listen = function (key, fn) {
if (!clientList[key]) {
clientList[key] = []
}
clientList[key].push(fn)
}
trigger = function () {
var key = Array.prototype.shift.call(arguments), fns = clientList[key
if (!fns || fns.length === 0) {
return false
}
for (var i = 0, fn; fn = fns[i++];) {
fn.apply(this, arguments)
}
}
remove = function (key, fn) {
var fns = clientList[key];
if (!fns) {
return false
}
if (!fn) {
fns && (fns.length = 0);
} else {
for (var l = fns.length - 1; l >= 0; l--) {
var _fn = fns[l];
if (_fn === fn) {
fns.splice(l, 1)
}
}
}
}
return {
listen:listen,
trigger:trigger,
remove:remove
}
})()
ObserverEvent.listen('squareMeter88', fn1 = function(price) {
console.log('价格=' + price);
})
ObserverEvent.listen('squareMeter100', function(price) {
console.log('价格=' + price);
})
ObserverEvent.trigger('squareMeter88', 200000)
ObserverEvent.trigger('squareMeter100', 300000)
ObserverEvent.remove('squareMeter88', fn1);
ObserverEvent.trigger('squareMeter88', 200000)
  • 优势

    耦合度低,改善代码管理,代码可复用

中介者模式

    中介者模式的作用是解除对象与对象之间的紧耦合关系。增加中介者后,所有的相关对象都通过中介者对象来通信。
    中介者模式是迎合迪米特法则的一种实现。指一个对象应该尽可能的少了解另外的对象。如果对象之间的耦合性太高,一个对象改变后,会影响其他对象。

!(function () {
var playerDirector = (function () {
var players = {}, operations = {}
operations.addPlayer = function (player) {
var teamColor = player.teamColor
players[teamColor] = players[teamColor] || []
players[teamColor].push(player)
}
operations.removePlayer = function (player) {
var teamColor = player.teamColor, teamPlayers = players[teamColor] || []
for (var i = teamPlayers.length - 1; i >= 0; i++) {
if (teamPlayers[i] === player) {
teamPlayers.splice(i, 1)
}
}
}
operations.changeTeam = function (player, newTeamColor) {
operations.removePlayer(player)
player.teamColor = newTeamColor;
operations.addPlayer(player)
}
operations.playerDead = function (player) {
var teamColor = player.teamColor, teamPlays = players[teamColor]
var all_dead = true
for (var i = 0, player; player = teamPlays[i++];) {
if (player.state !== "dead") {
all_dead = false
break
}
}
if (all_dead === true) {
for (var i = 0, player; player = teamPlays[i++];) {
player.lose();
}
for (var color in players) {
if (color != teamColor) {
var teamPlayers = players[color]
for (var i = 0, player; player = teamPlayers[i++];) {
player.win()
}
}
}
}
}
var ReceiveMessage = function () {
var message = Array.prototype.shift.call(arguments)
operations[message].apply(this, arguments)
}
return {
ReceiveMessage:ReceiveMessage
}
})()
function Player(name, teamColor) {
this.name = name;
this.teamColor = teamColor
this.state = "alive"
}
Player.prototype.win = function () {
console.log(this.name + " win ")
}
Player.prototype.lose = function () {
console.log(this.name + " lose ")
}
Player.prototype.die = function () {
this.state = "dead"
playerDirector.ReceiveMessage("playerDead", this);
}
Player.prototype.remove = function () {
playerDirector.ReceiveMessage("removePlayer", this);
}
Player.prototype.changeTeam = function (color) {
playerDirector.ReceiveMessage("changeTeam", this, color)
}
var PlayerFacotry = function (name, teamColor) {
var newPlayer = new Player(name, teamColor)
playerDirector.ReceiveMessage("addPlayer", newPlayer)
return newPlayer
}
//测试
var player1 = PlayerFacotry("皮蛋","red"),
player2 = PlayerFacotry("小乖","red")
var player3 = PlayerFacotry("黑妞","blue"),
player4 = PlayerFacotry("葱头","blue")
player1.die()
player2.die()
})()
  • 优点

    中间人模式最大的好处就是,它节约了对象或者组件之间的通信信道,这些对象或者组件存在于从多对多到多对一的系统之中。由于解耦合水平的因素,添加新的发布或者订阅者是相对容易的。

  • 缺点

    它可以引入一个单点故障。在模块之间放置一个中间人也可能会造成性能损失,因为它们经常是间接地的进行通信的。由于松耦合的特性,仅仅盯着广播很难去确认系统是如何做出反应的。

原型模式

    GoF将原型模式引用为通过克隆的方式基于一个现有对象的模板创建对象的模式。
    我们能够将原型模式认作是基于原型的继承中,我们创建作为其它对象原型的对象.原型对象自身被当做构造器创建的每一个对象的蓝本高效的使用着.如果构造器函数使用的原型包含例如叫做name的属性,那么每一个通过同一个构造器创建的对象都将拥有这个相同的属性。

  • 不直接使用 Object.create 的前提下实现原型模式
var vehiclePrototype = {
init: function ( carModel ) {
this.model = carModel
},
getModel: function () {
console.log( "The model of this vehicle is.." + this.model)
}
}
function vehicle( model ) {
function F() {}
F.prototype = vehiclePrototype;
var f = new F()
f.init( model )
return f
}
var car = vehicle( "Ford Escort" )
car.getModel()

注意: 这种可选的方式不允许用户使用相同的方式定义只读的属性(因为如果不小心的话vehicle原型可能会被改变)

命令模式

    命令模式指的是一个执行某些特定事情的指令。常见的应用场景是:有时候需要向对象发送请求,但不知道接受者是谁,也不知道请求的操作是什么。
    命名模式的目标是将方法的调用,请求或者操作封装到一个单独的对象中,给我们酌情执行同时参数化和传递方法调用的能力.另外,它使得我们能将对象从实现了行为的对象对这些行为的调用进行解耦,为我们带来了换出具体的对象这一更深程度的整体灵活性。
    具体类是对基于类的编程语言的最好解释,并且同抽象类的理念联系紧密.抽象类定义了一个接口,但并不需要提供对它的所有成员函数的实现.它扮演着驱动其它类的基类角色.被驱动类实现了缺失的函数而被称为具体类. 命令模式背后的一般理念是为我们提供了从任何执行中的命令中分离出发出命令的责任,取而代之将这一责任委托给其它的对象。
    实现明智简单的命令对象,将一个行为和对象对调用这个行为的需求都绑定到了一起.它们始终都包含一个执行操作(比如run()或者execute()).所有带有相同接口的命令对象能够被简单地根据需要调换,这被认为是命令模式的更大的好处之一。

(function(){
var CarManager = {
// request information
requestInfo: function(model, id) {
return "The information for " + model + " with ID " + id + " is foobar"
},
// purchase the car
buyVehicle: function(model, id) {
return "You have successfully purchased Item " + id + ", a " + model
},
// arrange a viewing
arrangeViewing: function(model, id) {
return "You have successfully booked a viewing of " + model + " ( " + id + " ) "
}
}
CarManager.execute = function ( name ) {
return CarManager[name] && CarManager[name].apply(CarManager, [].slice.call(arguments, 1))
}
CarManager.execute( "arrangeViewing", "Ferrari", "14523" );
CarManager.execute( "requestInfo", "Ford Mondeo", "54323" );
CarManager.execute( "requestInfo", "Ford Escort", "34232" );
CarManager.execute( "buyVehicle", "Ford Escort", "34232" );
})()

外观模式

    当我们提出一个门面,我们要向这个世界展现的是一个外观,这一外观可能藏匿着一种非常与众不同的真实。这就是我们即将要回顾的模式背后的灵感——门面模式。这一模式提供了面向一种更大型的代码体提供了一个的更高级别的舒适的接口,隐藏了其真正的潜在复杂性。把这一模式想象成要是呈现给开发者简化的API,一些总是会提升使用性能的东西。
    门面是一种经常可以在Javascript库中看到的结构性模式,像在jQuery中,尽管一种实现可能支持带有广泛行为的方法,但仅仅只有这些方法的“门面”或者说被限制住的抽象才会公开展现出来供人们所使用。
    这允许我们直接同门面,而不是同幕后的子系统交互。不论何时我们使用jQuery的 ( e l ) . c s s 或 者 (el).css或者 (el).css(el).animate()方法,我们实际上都是在使用一个门面——更加简单的公共接口让我们避免为了使得行为工作起来而不得不去手动调用jQuery核心的内置方法。这也避免了手动同DOM API交互和维护状态变量的需要。
    为了在我们所学的基础上进行构建,门面模式同时需要简化一个类的接口,和把类同使用它的代码解耦。这给予了我们使用一种方式直接同子系统交互的能力,这一方式有时候会比直接访问子系统更加不容易出错。门面的优势包括易用,还有常常实现起这个模式来只是一小段路,不费力。

var module = (function() {
var _private = {
i: 5,
get : function() {
console.log( "current value:" + this.i)
},
set : function( val ) {
this.i = val;
},
run : function() {
console.log( "running" )
},
jump: function(){
console.log( "jumping" )
}
}
return {
facade : function( args ) {
_private.set(args.val);
_private.get();
if ( args.run ) {
_private.run()
}
}
}
}())
// Outputs: "current value: 10" and "running"
module.facade( {run: true, val:10} )

工厂模式

    工厂模式是另外一种关注对象创建概念的创建模式。它的领域中同其它模式的不同之处在于它并没有明确要求我们使用一个构造器。取而代之,一个工厂能提供一个创建对象的公共接口,我们可以在其中指定我们希望被创建的工厂对象的类型。
    试想一下,在我们被要求创建一种类型的UI组件时,我们就有一个UI工厂。并不是通过直接使用new操作符或者通过另外一个构造器来创建这个组件,我们取而代之的向一个工厂对象索要一个新的组件。我们告知工厂我们需要什么类型的组件(例如:“按钮”,“面板”),而它会将其初始化,然后返回供我们使用。
    如果创建过程相当复杂的话,那这会特别的有用,例如:如果它强烈依赖于动态因素或者应用程序配置的话。

// Types.js - Constructors used behind the scenes
// A constructor for defining new cars
function Car(options) {
// some defaults
this.doors = options.doors || 4
this.state = options.state || "brand new"
this.color = options.color || "silver"
}
// A constructor for defining new trucks
function Truck(options) {
this.state = options.state || "used"
this.wheelSize = options.wheelSize || "large"
this.color = options.color || "blue"
}
// FactoryExample.js
// Define a skeleton vehicle factory
function VehicleFactory() {}
// Define the prototypes and utilities for this factory
// Our default vehicleClass is Car
VehicleFactory.prototype.vehicleClass = Car
// Our Factory method for creating new Vehicle instances
VehicleFactory.prototype.createVehicle = function(options) {
if( options.vehicleType === "car" ){
this.vehicleClass = Car
}else{
this.vehicleClass = Truck
}
return new this.vehicleClass(options)
}
// Create an instance of our factory that makes cars
var carFactory = new VehicleFactory()
var car = carFactory.createVehicle({
vehicleType: "car",
color: "yellow",
doors: 6 })
// Test to confirm our car was created using the vehicleClass/prototype Car
// Outputs: true
console.log(car instanceof Car)
// Outputs: Car object of color "yellow", doors: 6 in a "brand new" state
console.log(car)
  • 何时使用工厂模式

    1. 当我们的对象或者组件设置涉及到高程度级别的复杂度时。
    2. 当我们需要根据我们所在的环境方便的生成不同对象的实体时。
    3. 当我们在许多共享同一个属性的许多小型对象或组件上工作时。
    4. 当带有其它仅仅需要满足一种API约定(又名鸭式类型)的对象的组合对象工作时.这对于解耦来说是有用的。

Mixin 模式

    在诸如C++或者List着这样的传统语言中,织入模式就是一些提供能够被一个或者一组子类简单继承功能的类,意在重用其功能

  • 子类划分

    子类划分是一个参考了为一个新对象继承来自一个基类或者超类对象的属性的术语.在传统的面向对象编程中,类B能够从另外一个类A处扩展.这里我们将A看做是超类,而将B看做是A的子类.如此,所有B的实体都从A处继承了其A的方法.然而B仍然能够定义它自己的方法,包括那些重载的原本在A中的定义的方法。
    B是否应该调用已经被重载的A中的方法,我们将这个引述为方法链.B是否应该调用A(超类)的构造器,我们将这称为构造器链。

// Define a simple Car constructor
var Car = function(settings) {
this.model = settings.model || "no model provided"
this.color = settings.color || "no colour provided"
}
// Mixin
var Mixin = function() {}
Mixin.prototype = {
driveForward: function () {
console.log("drive forward")
},
driveBackward: function () {
console.log("drive backward")
},
driveSideways: function () {
console.log("drive sideways")
}
}
// Extend an existing object with a method from another
function augment( receivingClass, givingClass) {
// only provide certain methods
if(arguments[2] ) {
for(var i = 2, len = arguments.length; i < len; i++) {
receivingClass.prototype[arguments[i]] = givingClass.prototype[arguments[i]]
}
}
// provide all methods
else {
for(var methodName in givingClass.prototype) {
// check to make sure the receiving class doesn't
// have a method of the same name as the one currently
// being processed
if(!Object.hasOwnProperty(receivingClass.prototype, methodName)) {
receivingClass.prototype[methodName] = givingClass.prototype[methodName];
}
// Alternatively:
// if(!receivingClass.prototype[methodName]) {
//
receivingClass.prototype[methodName] = givingClass.prototype[methodName];
// }
}
}
}
// Augment the Car constructor to include "driveForward" and "driveBackward"
augment(Car, Mixin, "driveForward", "driveBackward")
// Create a new Car
var myCar = new Car({
model: "Ford Escort",
color: "blue"
})
// Test to make sure we now have access to the methods
myCar.driveForward()
myCar.driveBackward()
// Outputs:
// drive forward
// drive backward
// We can also augment Car to include all functions from our mixin
// by not explicitly listing a selection of them
augment(Car, Mixin)
var mySportsCar = new Car({
model: "Porsche",
color: "red"
})
mySportsCar.driveSideways()
// Outputs:
// drive sideways
  • 优点

    Mixin支持在一个系统中降解功能的重复性,增加功能的重用性.在一些应用程序也许需要在所有的对象实体共享行为的地方,我们能够通过在一个Mixin中维护这个共享的功能,来很容易的避免任何重复,而因此专注于只实现我们系统中真正彼此不同的功能。

  • 缺点

    Mixin的副作用是值得商榷的.一些开发者感觉将功能注入到对象的原型中是一个坏点子,因为它会同时导致原型污染和一定程度上的对我们原有功能的不确定性.在大型的系统中,很可能是有这种情况的。

装饰模式

    装饰者模式可以动态的给某个对象添加一些额外的职责,而不会影响从这个类中派生的其他对象。
    装饰器是旨在提升重用性能的一种结构性设计模式。同Mixin类似,它可以被看作是应用子类划分的另外一种有价值的可选方案。
    典型的装饰器提供了向一个系统中现有的类动态添加行为的能力。其创意是装饰本身并不关心类的基础功能,而只是将它自身拷贝到超类之中。
    它们能够被用来在不需要深度改变使用它们的对象的依赖代码的前提下,变更我们希望向其中附加功能的现有系统之中。开发者使用它们的一个通常的理由是,它们的应用程序也许包含了需要大量彼此不相干类型对象的特性。想象一下不得不要去定义上百个不同对象的构造器,比方说,一个Javascript游戏。
    对象构造器可以代表不同播放器类型,每一种类型具有不同的功能。一种叫做领主戒指的游戏会需要霍比特人、巫术师,兽人,巨兽,精灵,山岭巨人,乱世陆地等对象的构造器,而这些的数量很容易过百。而我们还要考虑为每一个类型的能力组合创建子类。
    例如,带指环的霍比特人,带剑的霍比特人和插满宝剑的陆地等等。这并不是非常的实用,当我们考虑到不同能力的数量在不断增长这一因素时,最后肯定是不可控的。
    装饰器模式并不去深入依赖于对象是如何创建的,而是专注于扩展它们的功能这一问题上。不同于只依赖于原型继承,我们在一个简单的基础对象上面逐步添加能够提供附加功能的装饰对象。它的想法是,不同于子类划分,我们向一个基础对象添加(装饰)属性或者方法,因此它会是更加轻巧的。

!(function () {
var plance = function () { }
plance.prototype.fire = function () {
console.log("发射普通子弹")
}
var missileDecorator = function () {
console.log("发射导弹")
}
Function.prototype.after = function (afterfn) {
var _self = this
return function () {
var ret = _self.apply(this, arguments)
afterfn.apply(this, arguments)
return ret
}
}
var pl = new plance()
pl.fire = pl.fire.after(missileDecorator)
pl.fire()
})()
  • 优点

    因为它可以被透明的使用,并且也相当的灵活,因此开发者都挺乐意去使用这个模式——如我们所见,对象可以用新的行为封装或者“装饰”起来,而后继续使用,并不用去担心基础的对象被改变。在一个更加广泛的范围内,这一模式也避免了我们去依赖大量子类来实现同样的效果。

  • 缺点

    如果穷于管理,它也会由于引入了许多微小但是相似的对象到我们的命名空间中,从而显著的使得我们的应用程序架构变得复杂起来。这里所担忧的是,除了渐渐变得难于管理,其他不能熟练使用这个模式的开发者也可能会有一段要掌握它被使用的理由的艰难时期。

亨元(Flyweight)模式

    享元模式的核心是运用共享技术来有效支持大量细粒度的对象。
    享元模式是一个优化重复、缓慢和低效数据共享代码的经典结构化解决方案。它的目标是以相关对象尽可能多的共享数据,来减少应用程序中内存的使用(例如:应用程序的配置、状态等)。
    实际应用中,轻量级的数据共享采集被多个对象使用的相似对象或数据结构,并将这些数据放置于单个的扩展对象中。我们可以把它传递给依靠这些数据的对象,而不是在他们每个上面都存储一次。

  • 使用享元

    1. 第一种是数据层,基于存储在内存中的大量相同对象的数据共享的概念。
    2. 第二种是DOM层,享元模式被作为事件管理中心,以避免将事件处理程序关联到我们需要相同行为父容器的所有子节点上。 享元模式通常被更多的用于数据层。

  • 享元和数据共享

    对于这个应用程序而言,围绕经典的享元模式有更多需要我们意识到的概念。享元模式中有一个两种状态的概念——内在和外在。内在信息可能会被我们的对象中的内部方法所需要,它们绝对不可以作为功能被带出。外在信息则可以被移除或者放在外部存储。
    带有相同内在数据的对象可以被一个单独的共享对象所代替,它通过一个工厂方法被创建出来。这允许我们去显著降低隐式数据的存储数量。
    个中的好处是我们能够留心于已经被初始化的对象,让只有不同于我们已经拥有的对象的内在状态时,新的拷贝才会被创建。
    们使用一个管理器来处理外在状态。如何实现可以有所不同,但针对此的一种方法就是让管理器对象包含一个存储外在状态以及它们所属的享元对象的中心数据库。

  • 享元组件的三种类型

    享元对应的是一个接口,通过此接口能够接受和控制外在状态。
    构造享元来实际的实际的实现接口,并存储内在状态。构造享元须是能够被共享的,并且具有操作外在状态的能力。
    享元工厂负责管理享元对象,并且也创建它们。它确保了我们的享元对象是共享的,并且可以对其作为一组对象进行管理,这一组对象可以在我们需要的时候查询其中的单个实体。如果一个对象已经在一个组里面创建好了,那它就会返回该对象,否则它会在对象池中新创建一个,并且返回之。

!(function() {
var Model = function(sex) {
this.sex = sex
}
Model.prototype.takePhoto = function() {
console.log("sex=" + this.sex + " underwear=" + this.underwear)
}
var maleModel = new Model("male")
var femaleModel = new Model("female")
for(var i = 1; i <= 50; i++){
maleModel.underwear = "underwear" + i
maleModel.takePhoto()
}
})()

享元模式的使用取决于: 一个程序中使用了大量相似对象。造成很大的内存开销。大多数状态是外部状态。可以用较少的功效对象取代大量对象。

最后

以上就是不安小蘑菇为你收集整理的JS中设计模式的深入理解概念构造器模式模块化模式暴露模块模式单例模式观察者模式中介者模式原型模式命令模式外观模式工厂模式Mixin 模式装饰模式亨元(Flyweight)模式的全部内容,希望文章能够帮你解决JS中设计模式的深入理解概念构造器模式模块化模式暴露模块模式单例模式观察者模式中介者模式原型模式命令模式外观模式工厂模式Mixin 模式装饰模式亨元(Flyweight)模式所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(46)

评论列表共有 0 条评论

立即
投稿
返回
顶部