JavaScript 設計模式之建立型設計模式

簡單工廠模式

工廠模式是用來建立對象的一種最經常使用的設計模式。咱們不須要暴露建立對象的具體邏輯,而是將邏輯都封裝在一個函數中,那麼這個函數將視爲一個工廠。工廠模式可分爲簡單工廠工廠方法抽象工廠css


簡單工廠模式 又稱爲 靜態工廠方法工廠函數,是由一個工廠對象(函數)用來建立一組類似的產品對象實例。面試

把須要的方法都封裝在一個函數內,經過調用該函數返回我對應的對象。設計模式

實例化對象建立的 例如體育商品店購買體育器材,當咱們購買一個籃球和及其介紹時,只需告訴售貨員,售貨員就會幫咱們找到須要的東西。ide

function Bask(){
	this.name = "籃球"
}
Bask.prototype.getBask = function(){
	console.log("籃球隊員須要5個");
}
function Football(){
	this.name = "足球";
}

Football.prototype.getFootball = function(){
	console.log("足球隊員須要11個");
}
function Sort(type){
	switch(type){
        case "bask":
			return new Bask();
		break;
        case "foot":
			return new Football();
		break;
		default :
		console.log("只有籃球和足球");
	}
}
let f = Sort("bask");
f.name; // "籃球"
f.getBask() // 籃球隊員須要5個
複製代碼

Sort() 就是這樣一個工廠函數,給 Sort() 傳入對應的參數就能夠獲得咱們須要的對象了。
一個對象有時也能夠代替許多類函數

簡單工廠模式 中將相似的功能提取出來,不類似的針對處理,這點很像咱們的繼承的 寄生模式,但這裏沒有父類,因此不須要繼承。只須要建立一個對象,而後經過對這個對象大量擴展屬性和方法,並在最終的時候返回來。oop

"好比,想建立一些書,那麼書都是有一些類似的地方發,好比目錄、頁碼等。不類似的地方,好比書名、出版時間,書的類型等。對於建立的對象類似屬性好處理,對於不一樣的屬性就要針對性的處理了。好比咱們將不一樣的屬性做爲參數傳遞進來處理。"post

經過建立一個新對象而後加強其屬性和功能實現性能

function createBook(name,time,type){
	let obj = {};
	obj.name = name;
	obj.time = time;
	if(type == "js"){
		obj.type = "js 書籍";
	}else if (type == "css"){
		obj.type = "css 書籍"
	}else {
		obj.type = type;
	}
	obj.getName = function(){
		console.log(obj.name);
	}
	obj.getType = function(){
		console.log(obj.type)
	}
	return obj;
}
let c = createBook("JavaScript 高級程序第三版","2019","js");
c.getType(); // js 書籍
複製代碼

實例化建立的和自建立對象的區別 經過類實例化建立的對象,若是這些類都繼承同一個父類,那麼他們父類的原型的方法時能夠共用的。自建立方式建立的對象都是一個新的個體,所以他們的方法是不能共用的。ui

對於一些重複性的需求咱們均可以採用工廠模式來建立一些對象,可讓這些對象共用一些資源也有私有的資源。不過對於簡單工廠模式,它的使用場合一般也就是限制在建立單一的對象。this


工廠方法模式


工廠方法模式的本意是說`將實際建立的對象工做推遲到子類中`。這樣核心類就成了抽象類,可是在 Javascript 中沒有傳統建立抽象類那樣的方式去實現抽象類,因此在 JavaScript 中實現工廠方法模式咱們只須要參考它的核心思想就能夠了。把`工廠方法看做是一個實例化對象的工廠類`。

在簡單工廠模式中每次添加構造函數方法的時候都須要修改 2 個地方 ( 添加構造函數類和修改工廠函數 ),如今改用 工廠方法模式 ,把工廠方法看做是一個實例化對象的工廠,只負責把要添加的類添加到這個工廠就能夠了。

以上面的 書籍 代碼爲例

function CreateBook(type,name,time){
	if(this instanceof CreateBook){
		return new this[type](name,time)
	}else{
		return new CreateBook(type,name,time)
	}
}
CreateBook.prototype = {
    constructor: CreateBook,
    getName:function(name,time){
        console.log(name,time);
    },
    getType: function(name,time){
        console.log(name,time);
    }
}
複製代碼

直接把要添加的類寫在 CreateBook 這個工廠類的原型裏面就能夠避免修改不少操做了。

對於建立不少類對象,簡單工廠模式就不適合用了,這是簡單工廠模式的應用侷限,當前這也正是 工廠方法 的價值所在,經過 工廠方法模式 能夠輕鬆建立多個類的實例對象,這樣工廠方法在建立對象的方式也避免了使用者和對象類之間的耦合度,用戶不須要關心建立該對象的具體類,只須要調用工廠方法便可。

在瞭解抽象工廠模式以前,咱們先了解一下什麼是抽象類。 ** 抽象類** 在 js 中還保留着一個 abstract 保留字,目前來講還不能像傳統面向對象那樣去建立一個抽象類。抽象類是一種聲明但不能使用的類,當使用就會報錯。在 js 中,咱們能夠用類來模擬一個抽象類,而且在類的方法中手動的拋出錯誤。

function Car(){};
Car.prototype = {
	constructor : Car,
	getPrice:function(){
		return new Error("抽象類的方法不能被調用!");
	}
	getSeed:function(){
		return new Error("抽象類的方法不能被調用!");
	}
}
複製代碼

Car 類其實什麼也沒作,建立的時候沒有任何屬性,原型中的方法也不能使用。可是在繼承上頗有用,由於定義了一種類,而且定義了該類所必備的方法,若是子類中沒有重寫這些方法就會報錯,這點很重要,由於在一些大型的應用中,總有一些子類繼承另外一些父類,這些父類常常會定義一些必要的方法,卻沒有具體實現,好比 Car 類同樣,繼承的子類若是沒有本身定義所必備的方法就會調用父類的,這時若是父類可以提供一個友好的提示,那麼對於忘記重寫子類方法的這些錯誤遺漏是頗有必要的, 這也是抽象類的一個做用,定義一個產品簇,並聲明一些所必備的方法,若是子類沒有聲明一些必備的方法重寫就會報錯

在 ES6 中也沒有實現abstract , 但要比咱們上面那種寫法簡約不少。ES6 中採用 new.target 來模擬出抽象類,new.target 指向直接被 new 執行的函數,咱們對 new.target 進行判斷,若是指向了該類則拋出錯誤表示這是一個抽象類。

class User{
	constructor(){
		if(new.target === User){
			console.log("抽象類不能被實例化");
		}
	}
}
class U extends User{
	constructor(name,age){
		super();
	}
	getname(){
		console.log(this.name)
	}
}
let a = new User(); // 抽象類不能被實例化
let a1 = new U("kk",2);
a1.name // "kk"
複製代碼

抽象工廠模式


在 js 中抽象工廠模式不用來建立具體對象,由於抽象類中定義的方法都是顯性地定義了一些功能,但沒有具體的實現,而一個對象是要有具體的一套完整的功能,因此用抽象類建立的對象也是抽象類,所以不能使用它來建立一個真實的對象。

因此,通常用它做爲父類來建立一些子類。

function VehicleFactory(subType, superType) {
     // VehicleFactory[superType] 對象獲取屬性
     if (typeof VehicleFactory[superType] === "function") {
         function F() { };
         F.prototype = new VehicleFactory[superType]();
         subType.prototype = new F();
         subType.constructor = subType;
     } else {
         throw new Error("未建立該抽象類")
     }
 }
 VehicleFactory.Car = function () {
     this.type = "car";
 }
 VehicleFactory.Car.prototype = {
     getPrice: function () {
         return new Error("抽象方法不能使用!")
     }
 }

 //汽車子類
 function BMW(price) {
     this.price = price;
 }
 VehicleFactory(BMW, 'Car');
 BMW.prototype = {
     getPrice: function () {
         console.log(this.price);
     }
 }
 let b = new BMW("小汽車滴滴滴...");
 b.getPrice(); //小汽車滴滴滴...
複製代碼

抽象工廠 VehicleFactory 實際上是一個實現子類繼承父類(建立子類)的方法,在該方法中須要經過傳遞 子類和要繼承的父類(抽象類) 的名稱,而且在抽象工廠方法中增長了對抽象類是否存在的判斷,存在則將子類繼承父類,繼承父類的過程當中須要對 過渡類的原型繼承時,咱們不是繼承父類的原型,而是經過 new 關鍵字複製父類的一個實例,這是由於 過渡類不該該僅僅是繼承父類的原型方法,還要繼承父類的對象屬性,因此要經過 new 關鍵字將父類的構造函數執行一遍來複制構造函數中的屬性和方法

對抽象工廠添加抽象類也很特殊,由於抽象工廠是個方法不須要實例化,因此只有一份, 所以直接給抽象工廠添加類的屬性就能夠了。因而咱們就能夠經過點(.)語法在抽象工廠上添加了小汽車簇抽象類(Car)。

抽象工廠模式建立出的不是一個真實的對象實例,而是一個類簇,制定了類的結構,這也就是區別簡單工廠模式建立一個單一對象,工廠方法模式建立多類對象。


建立者模式

工廠模式 主要就是爲建立實例對象或類簇(抽象工廠),不在意過程是啥,只關心最後返回的對象。

建立者模式概念
在建立對象時比較複雜,但更關心的是建立對象的過程,根據需求分解成多個對象,最後在拼接到一個對象返回。

好比下面是一個專門負責招聘的需求功能

// 建立一個 人類方法
function Human(param) {
    this.skill = param && param.skill || "保密";
    this.hobby = param && param.hobby || "保密";
}
// 提供原型方法
Human.prototype = {
    getSkill: function () {
        return this.skill;
    },
    getHobby: function () {
        return this.hobby;
    }
}
// 建立名字
function Named(name) {
    this.name = name;
}
// 建立工做職位
function Work(work) {
    let that = this;
    switch (work) {
        case 'Java':
            that.work = "Java工程師";
            that.workTxt = "天天沉迷 Java 不可自拔!";
            break;
        case 'JavaScript':
            that.work = "JS 工程師";
            that.workTxt = "天天沉迷 JS 不可自拔!";
            break;
        case 'UI':
            that.work = "UI 設計師";
            that.workTxt = "設計是一種藝術!";
            break;
        case 'PM':
            that.work = "產品經理";
            that.workTxt = "天天都在作需求!";
            break;
        default:
            that.work = work;
            that.workTxt = "對不起,不清楚你的分類";
    }
}
// 修改工做職位
Work.prototype.changeWork = function(work){
    this.work = work;
}
// 修改工做描述
Work.prototype.changeWorkTxt = function(txt){
    this.workTxt = txt;
}
// 重點,建立一個面試者,在這個階段進行拼接
function Person (name,work){
    let p = new Human();
    p.w = new Work(work);
    p.name = new Named(name);
    return p;
}
let p1 = newPerson("xiaoming", "Java");
console.log(p1.w.work); // "Java工程師"
console.log(p1.name.name);// "xiaoming"
p1.w.changeWork("UI");
console.log(p1.w.work);// "UI"
console.log(p1.getSkill()); //保密
複製代碼

Person 就是一個建立者函數,在該函數內咱們把 3 個類組合調用,就能夠建立出一個完整的應聘者對象了。

建造者模式中,咱們更關心建立對象的過程。把功能拆分在拼接獲得一個完整去對象。主要針對複雜業務的解耦。

原型模式

原型模式 將原型對象指向建立對象的類,使這些類共享原型對象的屬性和方法。

這是基於 JS 原型鏈實現對象之間的繼承,這種繼承是一種屬性或方法的共享,而不是對屬性和方法的複製。

在建立的類中,存在基類,其定義的屬性和方法能被子類繼承。

原型模式將可複用的、可共享的、耗時較長的從基類中提出來放在基類的原型中,而後子類經過組合繼承或寄生組合式繼承把屬性和方法繼承下來,對於子類中那些須要重寫的方法進行重寫,這樣子類建立的對象既具備子類的屬性和方法同時也共享了基類的原型方法。

例子
好比頁面中常常見到的焦點圖,焦點圖的切換效果都是多變的,有左右切換的,有上下切換的還有漸隱切換的等等。所以咱們應該抽象出一個基類,根據不一樣需求來重寫繼承的屬性和方法。

代碼

function LoopImage(imgArr,container){
	this.imgArr = imgArr;
	this.container = container;
	this.createImg = function(){} // 建立輪播圖片
	this.changeImg = function(){} // 切換下一張圖片
}
// 上下切換效果
function SlideLoopImg(imgArr,container){
	LoopImage.call(this,imgArr,container);
	this.changeImg = function(){
		console.log("SlideLoopImg changeImg");
	}
}
// 漸隱切換效果
function FadeLoopImg(imgArr,container,arrow){
	LoopImage.call(this,imgArr,container);
	// 切換箭頭私有變量
	this.arrow = arrow;
	this.changeImg = function(){
		console.log("FadeLoopImg changeImg");
	}
}

// 實例化一個漸隱切換效果圖片類
let fadeImg = new FadeLoopImg(["01.jpg","02.jpg"],"slide",["left.jpg","right.jpg"]);
fadeImg.changeImg(); // FadeLoopImg changeImg
複製代碼

如上代碼還存在一些問題,首先看咱們的基類 LoopImage,做爲基類是要被子類繼承的,那麼此時將屬性和方法都寫在基類的構造函數裏就會有一些問題,好比每次子類繼承父類都要從新建立一次,又或者父類中的構造函數中存在不少耗時長的邏輯,亦或者每次初始化都是一些重複性的東西,對性能的消耗不少。

因此咱們須要一種共享機制,這樣每當建立基類的時候,對於一些簡單或者差別化的東西放在構造函數內,對於可重複,耗時長的邏輯放在基類的原型中。這樣就能夠避免損耗性能。

function LoopImage(imgArr,container){
	this.imgArr = imgArr;
	this.container = container;
}
LoopImage.prototype.createImg = function(){} // 建立輪播圖片
LoopImage.prototype.changeImg = function(){} // 切換下一張圖片
// 上下切換效果
function SlideLoopImg(imgArr,container){
	LoopImage.call(this,imgArr,container);
}
SlideLoopImg.prototype = new LoopImage();
SlideLoopImg.prototype.changeImg = function(){
	console.log("SlideLoopImg changeImg");
}
// 漸隱切換效果
function FadeLoopImg(imgArr,container,arrow){
	LoopImage.call(this,imgArr,container);
	// 切換箭頭私有變量
	this.arrow = arrow;
}
FadeLoopImg.prototype = new LoopImage();
FadeLoopImg.prototype.changeImg = function(){
	console.log("FadeLoopImg changeImg");
}

// 實例化一個漸隱切換效果圖片類
let fadeImg = new FadeLoopImg(["01.jpg","02.jpg"],"slide",["left.jpg","right.jpg"]);
fadeImg.changeImg(); // FadeLoopImg changeImg
複製代碼

原型對象是一個共享的對象,不論是父類的實例對象仍是子類的繼承,都是靠一個指針引用的。因此在任什麼時候候對基類或者子類的原型進行拓展,全部實例化的對象或者類都能獲取到這些方法。

單例模式

又稱爲單體模式,只容許實例化一次的對象類,有時候咱們也用一個對象來規劃一個命名空間,以便管理對象上的屬性和方法。

命名空間 也有人稱爲名稱空間,用來約束每一個人定義的變量以免全部不一樣的人定義的變量存在重複致使衝突的。

單例模式例子

var Ming = {
	g:function(id){
		return document.getElementById(id);
	},
	c:function(id,key,value){
		this.g(id).style[key] = value;
	}
}
複製代碼

在單例模式中想要使用定義的方法必定要加上命名空間 Ming,因此在上面代碼中的 c 方法中的 g 函數調用的時候要改爲 Ming.g。因爲 g 方法和 c 方法都在 Ming 中,也就是說這 2 個方法都是單例對象 Ming 的。而對象中的 this 指向當前對象。因此咱們也能夠像上面代碼那樣直接使用 this.g 。


建立代碼庫

單例模式 除了定義命名空間的做用以外還有一個做用就是經過單例模式來管理代碼庫中的各個模塊。

好比咱們之後在寫本身的小型方法庫時能夠用單例模式來規範本身代碼庫的各個模塊。

var A={
	Util:{
		Util_1:function(){},
		Util_2:function(){}
	},
	Tool:{
		Tool_1:function(){},
		Tool_2:function(){}
	},
	Ajax:{
		getName:function(){},
		postName:function(){}
	}
	// ...
}
複製代碼

使用模塊方法時

A.Util.Util_1();
A.Ajax.getName();
// ....
複製代碼

惰性單例
惰性單例指在須要的時候纔會建立,也稱爲延遲建立。 惰性單例模式,用到時才建立,再次使用是不須要在建立的。

var Lazy = (function(){
	var instance = null;
	// 單例
	function Sligle(){
		/*這裏能夠定義私有屬性和方法*/
		return {
			p:function(){},
			pv:"1.0"
		}
	}
	// 獲取單例對象接口
	return (function(){
		// 若是尚未建立單例纔開始建立
		if(!instance ){
			instance = Sligle();
		}
		// 返回單例
		return instance;
	})
})()
Lazy().p // 經過 Lazy對象 獲取內部建立的單例模式對象
複製代碼

參考:JavaScript 設計模式

相關文章
相關標籤/搜索