js基础设计模式总结
设计模式种类
- 创建型
- 帮助我们更好的创建对象
- 结构型
- 帮助我们优化代码结构
- 行为型
- 帮我们更好的进行逻辑交互
Module 模式
优点: 相对正真封装的思想,对于很多拥有面向对象背景的开发人员来说更加整洁。
支持私有属性,因此,在Module模式中,代码的公有部分能够接触私有部分,然而无法接触类的私有部分。
缺点: 无法修改可见性,无法访问在方法中的私有成员,不能未私有成员创建自动化单元测试,开发人员无法轻易的扩展私有方法。
单例模式(singleton)
单例模式它限制了类的实例化次数只能一次。单例不同于静态类,因为我们可以推迟他们初始化,这通常是因为他们需要一些信息,而这些信息在初始化期间可能无法获得,对于没有察觉到之前的引用代码,他们不会提供方便检索方法。在js中Singleton充当共享资源命名空间,从全局命名空间中隔离出代码是实现,从而为函数提供单一访问点。
实现:
var Singleton = (function (){
var instance;
function init(){
// 私有方法和变量
var privateVar = "var";
function privateFun(){
}
// 公有方法和变量
return {
publicFun: function (){
}
}
};
return {
getInstance: function (){
if(!instance){
instance = init();
}
return instance;
}
}
})();
Observer观察者模式
观察者是一种设计模式,其中,一个对象(称为subject)维持一系列依赖于它(观察者)的对象,将有关状态的任何变更自动通知给他们,当一个目标需要告诉观察者发生了什么事情的时候,它会向观察者广播一个通知,当观察者不希望获取到目标的消息,可以取消与目标关联。
实现:
html
<div>
<input type="text" id="active"/> // 触发更新
<input type="button" value="add" id="add"/> // 添加input
<div id="obs"> // 显示更新内容
</div>
</div>
js 实现
class ObserverList {
constructor(){
this.ObserverList=[];
//super()
};
Add(obj){
return this.ObserverList.push(obj)
};
Remove(index){
this.ObserverList.splice(index,1)
};
Empty(){
this.ObserverList=[]
};
Count(){
return this.ObserverList.length;
}
getObs(index){
return this.ObserverList[index];
}
GetObj(obj){
let i=0;
let len = this.ObserverList.length;
while(i<len){
if(JSON.stringify(obj)===JSON.stringify(this.ObserverList[i])){
return i;
}else{
i++
}
}
return len
}
}
class Subject {
constructor(){
this.observers = new ObserverList();
}
AddObserver(obj){
this.observers.Add(obj)
}
RemoverObserver(obj){
this.observers.Remove(this.observers.GetObj(obj))
}
Notify(context){
let i=0;
let count = this.observers.Count();
while(i<count){
console.log(this.observers.getObs(i))
this.observers.getObs(i).update(context)
i++
}
}
}
let sub = new Subject()
class CreateObj {
constructor(){
this.obsObj= document.getElementById("obs");
}
Click(){
let btn = document.getElementById("add");
btn.onclick= ()=>{
this.createInput()
}
}
createInput () {
let input = document.createElement("input");
input.type="text";
input.update=function(context){
this.value=context
}
this.obsObj.appendChild(input)
sub.AddObserver(input)
console.log(sub)
}
update(){
let value = document.getElementById("active").value;
sub.Notify(value)
}
change(){
let obj = document.getElementById("active");
obj.onchange=()=>{
this.update()
}
}
}
let cobj = new CreateObj();
cobj.Click();
cobj.change();
Public/Subscribe(发布/订阅) 模式
发布订阅模式使用了一个主题/事件通道,该通道介于订阅者个发布者之间,该事件系统允许代码定义应用程序的特定事件,这些事件可以传递自定义参数,自定义参数包含订阅者所需的值。其目的是避免订阅号者和发布者之间产生依赖关系。
实现:
// 发布者Public
class Public {
constructor(){
this.topics= {};
this.subUid = -1;
}
// 发布广播事件
publish(topic,args){
if(!this.topics[topic]){
return false
}
let subscribers = this.topics[topic];
let len = subscribers?subscribers.length:0;
while (len--) {
subscribers[len].func(topic,args)
}
}
// 通过特定的和回调函数订阅事件,topic/event触发时及时执行
subscribe(topic,func){
if(!this.topics[topic]){
this.topics[topic] = [];
}
let token = (++this.subUid).toString();
this.topics[topic].push({
token:token,
func:func
});
return token;
}
// 基于订阅上的标记引用,通过特定topic取消订阅
unsubscribe(token){
let topics = this.topics
for(let key in topics){
if(topics[key]){
for(let i=0;i<topics[key].length;i++){
topics[key].splice(i,1)
return token
}
}
}
return this;
}
}
Public的基础使用
// Public 基础的使用
let public = new Public();
// 用于通过订阅者接收到的主题(topic)和数据
let messageLogger = (topics,data)=>{
console.log("Logging:",topics,data);
};
// 订阅一个自定义topic
public.subscribe("newMessage",messageLogger);
// 发布者负责发布topic
public.publish("newMessage","hello world!");
public.publish("newMessage",["test","a","b"]);
public.publish("newMessage",{
name:"test newMessage"
});
// 取消订阅主题(之后的publish将不再执行)
public.unsubscribe("newMessage");
public.publish("newMessage","hello unsubscribe");
Public的web界面使用
// html
<div>
<label>name:</label><input type="text" id="checkMessage" />
<div id="check">
name: <span id="checkName"></span>
</div>
</div>
// js
// web界面实现
let checkMessage = (topics,data)=>{
let checkName=document.getElementById("checkName");
checkName.innerHTML=data
}
// 订阅一个checkName 的topic
public.subscribe("checkName",checkMessage);
let getcheckMessage=()=>{
let checkName=document.getElementById("checkMessage");
console.log(checkName)
checkName.onchange=function(){
public.publish("checkName", this.value);
}
}
getcheckMessage();
Mediator (中介者)模式
中介者模式它允许我们公开一个统一的接口,系统不同的部分可以通过该接口进行通信。
如果一个系统的各个组件之间看起来有太多的直接关系,也许需要一个中心控制点,以便各个组件可以通过这个中心控制点进行通信。Mediator模式促进宋耦合的方式是: 确保组件的交互是通过这个中心点来处理的,而不是显示地引用彼此。这种模式可以帮助我们解耦系统,提高组件可重用性。
基本实现:
class Subscriber {
constructor(fn,options,context) {
this.id=this.guidGenerator()
this.options = options;
this.fn = fn;
this.context = context
this.topic = null;
}
guidGenerator(){
return Math.random(10);
}
}
let aa = new Subscriber("aa","bb","tt");
console.log(aa);
class Topic {
constructor(namespace){
this.namespace = namespace;
this._callbacks = [];
this._topics = [];
this.stopped = false;
}
AddSubscriber(){
let callback = new Subscriber(fn,options,context);
this._callbacks.push(callback);
callback.topic = this;
return callback;
}
StopProoagation(){
this.stopped = true;
}
GetSubscriber(idel){
let len = this._callbacks.length;
for(let i=0;i<len;i++){
if(idel === this._callbacks[i].id){
return this._callbacks[i]
}
}
for(let z in this._topics){
if(this._topics.hasOwnProperty(z)){
let sub = this._topics[z].GetSubscriber(idel);
if(sub!==undefined){
return sub;
}
}
}
}
AddTopic(topic){
this._topics[topic] = new Topic(this.namespace+"_"+topic)
}
RemoveSubscriber(idel){
if(!idel){
this._callbacks = [];
for(let z in this._topics){
if(this._topics.hasOwnProperty(z)){
this._topics[z].RemoveSubscriber(idel);
}
}
}
let len = this._callbacks.length;
this._callbacks=this._callbacks.filter((item)=>{
item.id!==idel
})
}
Publish(){
let len = this._callbacks.length;
for(let i=0;i<len;i++){
let callback = this._callbacks[i];
callback.fn.apply(callback.context,data);
}
for(let key in this._topics){
if(!this.stopped){
if(this._topics.hasOwnProperty(key)){
this._topics[key].Publish(data);
}
}
}
this.stopped = false;
}
}
class Mediator {
constructor(){
this._topics = new Topic("");
}
GetTopic(namespace){
let topic = this._topics;
let namespaceHier = namespace.split("_");
if(namespace === ""){
return topic;
}
if(namespaceHier.length>0){
return this._topics(namespaceHier[1])
}
return topic;
}
Subscriber(topicName,fn,options,context){
topic = this.GetTopic(topicName);
sub = topic.AddSubscriber( fn, options, context);
return sub
}
GetSubscriber(idel,topic){
return this.GetTopic(topic).GetSubscriber(idel)
}
Remove(topicName,idel){
this.GetTopic(topicName).RemoveSubscriber(idel);
}
Publish(topicName){
let args = Array.prototype.slice.call("inbox:message:new",[args]),
topic = this.GetTopic(topicName);
args.push(topic);
this.GetTopic(topicName).Publish(args);
}
}
优点:它能够将系统中对象或组件之间所需的通信渠道从多对多减少到多对一。
缺点:它会引入单一故障点,将mediator放置于模块之间可以导致性能下降。因为他们总是间接的进行通信。
Prototype(原型) 模式
称Prototype模式为一种基于现有对象模板,通过克隆方式创建对象的模式。
我们可以认为Prototype模式是基于原型链的,可以在其中创建对象,作为其他对象的原型。
使用Prototype模式的其中一个好处是,我们获得的是javascript其本身所具有的原型优势,而不是其他语言的特性。
基本实现:
var myCar = {
name:"Ford Escort",
drive: function () {
console.log("I'm driving !");
}
}
// 使用Object.create实例化一个新car
var yourCar = Object.create(myCar);
其中Object.create允许使用第二个参数来初始化对象属性。
var vehicle = {
getModel:function (){
console.log("model");
}
}
var car = Object.create(vehicle,{
id:{
value:"idvalue",
enumerable: true
},
model:{
value:"Ford",
enumerable: true
}
})
Command(命令)模式
Command模式旨在将方法调用,请求或操作封装到单一对象中,从而根据我们不同的请求对客户进行参数化和传递可供执行的方法调用。此外,这种模式将调用操作的对象与知道如何实现该操作的对象解耦,并在交换出具体类(对象)方面提供更大的整体灵活性。
基本实现:
(function(){
var CarManager = {
buyInfo: function(model,id){
return "Model:"+model+"id:"+id;
},
addInfo: function(model,id){
return "Model:"+model+"id:"+id;
},
execute: function(name){
return this[name].apply(this,[].slice.call(arguments,1));
}
}
var Car={
execute:CarManager.execute
}
return Car
})()
console.log(Car.execute("addInfo","model111","111"));
外观模式(Facade)
Facade 模式为更大的代码体提供了一个方便的高层次接口,能够隐藏其底层的真实复杂性。
Facade 是一种结构型模式,支持实现可能支持具有广泛行为的方法,但却只有一个“外观”或这些方法的有限抽象能够提供给公众使用。
因此Facade模式既能简化类的接口,也能将这个类从使用它的代码中解耦,这使我们能够间接与子系统交互,这种方式相比直接访问子系统时不易犯错误,Facade的优点包括易使用和代码量小
基本实现
var addMyEvent = function (el,ev,fn) {
if(el.addEventListener) {
el.addEventListener(ev,fn,false);
}else if(el.attachEvent) {
el.attachEvent("on"+ev,fn);
}else {
el["on"+ev] = fn;
}
}
外观模式的缺点,有一个值得注意的问题就是性能,我们必须确定外观模式进行抽象是否包含隐形成本这种成本是否合理。例如jquery中的选择器和直接使用document.getElementById()之间的性能差别。还应考虑该封装的抽象程度,是否过度抽象给维护造成困难。
Factory 工厂模式
factory模式是另一种创建模式,涉及创建对象的概念。其分类不同于其他模式的地方在于它不显示地要求使用一个构造函数。而Factory可以提供一个通用的接口来创建对象,我们可以指定我们所希望创建的工厂对象的类型。
function Car(options) {
this.doors = options.doors || 4;
this.state = options.state || "brand new";
this.color = options.color || "silver";
}
function Truck(options) {
this.state = options.state || "used";
this.wheelSize = options.wheelSize || "large";
this.color = options.color || "blue";
}
VehicleFactory.prototype.vehicleClass = Car;
VehicleFactory.prototype.createVehicle = function (options) {
if (options.vehicleType === "car") {
this.vehicleType = Car;
}elae {
this.vehicleClass = Truck;
}
return new this.vehicleClass (options);
}
// 创建生成汽车实例
var carFactory = new VehicleFactory();
var car = carFactory.createVehicle({
vehicleType: "car",
color: "yellow",
doors:6
})
何时使用
- 当对象或组件涉及高复杂性时
- 当需要根据所在的不同环境轻松生成对象的不同实例
- 当处理很多共享相同属性的小型对象组件时
- 在编写只需要满足一个API契约的其他对象的实例对象时,对解耦是很有用的
何时不应该使用
- 对象的创建实际时藏身接口之后抽象出来的,因此会对单元测试可能带来问题。