设计模式
发表于:2024-08-16 | 分类: 学习

单体模式

单体模式的思想在于保证一个特定的类仅有一个实例。这意味着第二次使用同一个类创建新对象的时候,应该得到与第一次所创建对象完全相同

js 中没有类,只有对象,创建新对象就已经是单体了

1
var obj = {}

js 中对象之间永远不会相等,除非是同一对像,所以每次使用对象字面量创建对象就是创建一个单体

使用 new 操作符

1
2
3
var a = new Foo()
var b = new Foo()
a === b //true

上面代码中,a 对象仅在第一次调用构造函数时创建。第二次创建时将返回同一个 a 对象,使得 a === b,本质上是指相同一个对象的引用

js 实现单体需要 Foo 构造函数缓存该对象实例 this,以便第二次调用该构造函数时能够创建并返回同一个对象。实现这一目的有以下几种方法:

  • 使用全局变量存储该实例。不过全局变量是有缺点的,不符合一般性原则,任何人都能覆盖全局变量
  • 在构造函数的静态属性中缓存该实例。可以使用类似Foo.instance的属性将实例缓存在该属性中,这种方案的缺点是 instance 属性是公开可访问的属性,代码外部中可能会修改该属性
  • 将该实例包装在闭包中。这样可以保证实例的私有性,代价是带来了额外的闭包开销
静态属性实例

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function Foo() {
if (typeof Foo.instance === 'object') {
return Foo.instance
}
//加入属性
this.s_time = 0
this.bang = 'big'
// 缓存
Foo.instance = this
// 隐式返回
// return this
}

var a = new Foo()
var b = new Foo()
a === b// true
闭包中的实例

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function Foo() {
// 缓存实例
var instance
// 重写构造函
Foo = function Foo() {
return instance
}
// 保留原型属性
Foo.prototype = this
// 实例
instance = new Foo()
// 重置构造函数指针
instance.constructor = Foo
// 添加属性
this.s_time = 0
this.bang = "big"
return instance
}
var a = new Foo()
var b = new Foo()
a === b// true

工厂模式

工厂模式的目的是为了创建对象。通常以类或类静态方法实现,有以下设计目标:

  • 创建相似对象时执行重复操作。
  • 编译不知道具体类型(类)的情况下,为工厂客户提供一种创建对象的接口。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 父构造函数
function Maker() { }
Maker.prototype.foo = function () {
return "foo" + this.name
}
//工厂方法
Maker.factor = function (type) {
var constr = type, str;

if (typeof Maker[constr] !== "function") {
throw {
name: "Error",
message: constr + "doesn't exist"
}
}
if (typeof Maker[constr].prototype.foo !== "function") {
Maker[constr].prototype = new Maker()
}
str = new Maker[constr]()
return str
}
Maker.bar = function () {
this.name = "bar"
}
var corolla = Maker.factor('bar')
console.log(corolla.foo()) //foobar

迭代器模式

迭代器通常是一个包含某种数据集合的对象。该数据可能存储在一个复杂数据结构内部,需要提供一个简单的方法访问数据结构中的每个元素。消费者只需要取出单个数据进行工作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
var gen = (function () {
var index = 0, data = [1, 2, 3, 4, 5], length = data.length;
return {
next: function () {
var ele;
if (!this.hasNext()) {
return null
}
ele = data[index]
index = index + 2
return ele
},
hasNext: function () {
return index < length
},
rewind: function () {
index = 0
},
current: function () {
return data[index]
}
}
}())

while (gen.hasNext()) {
console.log(gen.next())
}

装饰者模式

装饰者模式可以在运行时动态添加附加功能到对象中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// 添加一个原型方法和构造函数
function trimmer(options) {
this.options = options || {}
}

trimmer.prototype.getTrimmer = function () {
return this.options
}
// 实现装饰者对象
trimmer.decorators = {}

// 添加装饰者
trimmer.decorators.foo = {
getTrimmer: function () {
var options = this.uber.getTrimmer()
options += options * 5 / 100
return options
}
}

trimmer.prototype.decorate = function (decorate) {
var F = function () { },
overrides = this.constructor.decorators[decorate],
i, newObj
F.prototype = this;
newObj = new F()
newObj.uber = F.prototype;
for (i in overrides) {
if (overrides.hasOwnProperty(i)) {
newObj[i] = overrides[i]
}
}
return newObj;
}
let tri = new trimmer("bar") // options: 'bar'
tri.decorate("foo")

使用列表实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function trimmer(options) {
this.options = options || {}
this.decorators_list = []
}

trimmer.prototype.decorate = function (decorate) {
this.decorators_list.push(decorate)
}
trimmer.prototype.getOptions = function () {
var options = this.options, i, max = this.decorators_list.length, name
for (i = 0; i < max; i++) {
name = this.decorators_list[i]
options = trimmer.decorators[name].getTrimmer(options)
}
return options
}

策略模式

此模式支持在运行时选择算法。

实现一个表单验证器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
var validator = {
// 可用检查
types: {},
// 错误消息
message: [],
//验证类型
config: {},
// 接口方法
validate: function (data) {
var i, msg, type, check, result_ok;
this.message = []
for (i in data) {
if (data.hasOwnProperty(i)) {
type = this.config[i]
check = this.types[type]
if (!type) {
continue;
}
if (!check) {
throw {
name: "ValidationError",
message: '验证空类型' + type
}
}

result_ok = check.validate(data[i])
if (!result_ok) {
msg = "校验值" + i + "," + check.instructions;
this.message.push(msg)
}
}
}
return this.hasErrors()
},
hasErrors: function () {
return this.message.length !== 0
}
}

validator.types.isNonEmpty = {
validate: function (value) {
return value !== "";
},
instructions: "不能为空"
}
validator.types.isNumber = {
validate: function (value) {
return !isNaN(value)
},
instructions: "只能为数字形式"
}
validator.types.isAlphaNum = {
validate: function (value) {
return !/[^a-z0-9]/i.test(value)
},
instructions: "由数字和字母组成"
}

var data = {
userName: "😄️",
firstName: "😸️",
age: "unknown"
}
validator.config = {
userName: "isAlphaNum",
firstName: "isNonEmpty",
age: "isNumber"
}

validator.validate(data)

外观模式

此模式为对象提供一个可供选择的接口。外观模式对重构和重新设计代码也很有帮助。通过外观模式可以优先考虑新对象的 API,在原有对象前面创建一个外观。这样仅须修改少量代码就可以取代原有对象。

当处理浏览器事件时有两个方法供调用:

1
2
3
stopPropagation()

preventDefault()

当需要同时调用时可以创建一个外观方法同时调用这两个方法

1
2
3
4
5
6
var event = {
stop: function(e) {
e.stopPropagation();
e.preventDefault()
}
}

代理模式

代理模式中,一个对象充当另一个对象的接口。代理充当了某个对象的守护对象,并试图使本体对象做尽可能少的工作。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<p><span id="toggle-all">toggle checked</span></p>
<ol id="vids">
<li>
<input type="checkbox" checked />
<a href="https://www.bilibili.com/video/BV1Zs411A717?p=1">paly1</a>
</li>
<li>
<input type="checkbox" checked />
<a href="https://www.bilibili.com/video/BV1Zs411A717?p=2">paly2</a>
</li>
<li>
<input type="checkbox" checked />
<a href="https://www.bilibili.com/video/BV1Zs411A717?p=3">paly3</a>
</li>
<li>
<input type="checkbox" checked />
<a href="https://www.bilibili.com/video/BV1Zs411A717?p=4">paly4</a>
</li>
<li>
<input type="checkbox" checked />
<a href="https://www.bilibili.com/video/BV1Zs411A717?p=5">paly5</a>
</li>
</ol>
</body>
<script>
var $ = function (id) {
return document.getElementById(id);
};
var videos = {
getPlayer: function (id) {},
updateList: function (data) {},
getInfo: function (id) {
var info = $("info" + id);
if (!info) {
http.makeRequest([id], "?p=", "https://www.bilibili.com/");
return;
}
if (info.style.display == "none") {
info.style.display = "";
} else {
info.style.display = "none";
}
},
};
var http = {
makeRequest: function (ids, callback, url) {
var url = url,
sql = "video/BV1Zs411A717",
handler = callback,
script = document.createElement("script");

url += sql + handler + ids;
script.src = url;
console.log(script);
document.body.appendChild(script);
},
};
// 代理代码
var proxy = {
ids: [],
delay: 50,
timeout: null,
callback: null,
context: null,
makeRequest: function (id, callback, context) {
this.ids.push(id);
this.callback = callback;
this.context = context;
if (!this.timeout) {
this.timeout = setTimeout(function () {
proxy.flush();
}, this.delay);
}
},
flush: function () {
http.makeRequest(this.ids, "proxy.handler");
this.timeout = null;
this.ids = [];
},
handler: function (data) {
var i, max;
if (parseInt(data.query.count, 10) === 1) {
proxy.callback.call(proxy.context, data.query.results.Video);
return;
}
for (i = 0, max = data.query.results.Video.length; i < max; i++) {
proxy.callback.call(proxy.context, data.query.results.Video[i]);
}
},
};
$("vids").onclick = function (e) {
var src, id;
e = e || window.event;
src = e.target || e.srcElement;
if (src.nodeName !== "A") {
return;
}
if (typeof e.preventDefault === "function") {
e.preventDefault();
}
e.returnValue = false;
id = src.href.split("?p=")[1];
if (src.className === "play") {
src.parentNode.innerHTML = videos.getPlayer(id);
return;
}
src.parentNode.id = "v" + id;
videos.getInfo(id);
};
$("toggle-all").onclick = function (e) {
var link, i, max, id;
link = $("vids").getElementsByTagName("a");
for (i = 0, max = link.length; i < max; i++) {
if (link[i].className === "play") {
continue;
}
if (!link[i].parentNode.firstChild.checked) {
continue;
}
id = link[i].href.split("?p=")[1];
link[i].parentNode.id = "v" + id;
videos.getInfo(id);
}
};
</script>
</html>

中介者模式

应用程序无论大小,都是由一些单个的对象组成。当这些对象需要一种方式来实现相互通信,这种通信一定程度上并不降低对代码的可维护性,也不会破坏程序的完整性。但是随着应用程序的增长,将添加越来越多的对象。这期间代码经历多次增删改。当对象膨胀的越来越大,不可避免地对象相互通信越来越多,这将会导致不良的耦合问题。这样导致后期最简单的修改将也变得不容易,几乎无法估计修改花费的时间。中介者模式缓解了这个问题,促进形成松耦合并且有助于提高可维护性。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<p>玩家1敲击键盘上的1,玩家2敲击键盘上的0(只有一分钟的时间!)</p>
<div id="results"></div>
</body>
<script>
function Player(name) {
this.points = 0;
this.name = name;
}

Player.prototype.play = function () {
this.points += 1;
mediator.played();
};

var scoreboard = {
element: document.getElementById("results"),
update: function (score) {
var i,
msg = "";
for (i in score) {
if (score.hasOwnProperty(i)) {
msg += `<p><strong>${i}</strong>:${score[i]}</p>`;
}
}
this.element.innerHTML = msg;
},
};

var mediator = {
players: {},
setup: function () {
var players = this.players;
players.home = new Player("Hone");
players.guest = new Player("Guest");
},
played: function () {
var players = this.players,
score = {
Home: players.home.points,
Guest: players.guest.points,
};
scoreboard.update(score);
},
keypress: function (e) {
e = e || window.event;
if (e.which === 49) {
mediator.players.home.play();
return;
}
if (e.which === 48) {
mediator.players.guest.play();
return;
}
},
};
mediator.setup();
window.onkeypress = mediator.keypress;

setTimeout(function () {
window.onkeypress = null;
alert("游戏结束 !");
}, 30000);
</script>
</html>

观察者模式

此模式是一个对象订阅另外一个对象的特定活动并在状态改变后获得通知,订阅者也被称之为观察者,被观察的对象称为发布者或主题

示例 1:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
var publisher = {
subscribers: {
any: []
},
subscribe: function (fn, type) {
type = type || 'any'
if (typeof this.subscribers[type] === "undefined") {
this.subscribers[type] = []
}
this.subscribers[type].push(fn)
},
unsubscribe: function (fn, type) {
this.visitSubscribers('unsubscribe', fn, type)
},
publish: function (publication, type) {
this.visitSubscribers('publish', publication, type)
},
visitSubscribers: function (action, arg, type) {
var pubType = type || 'any',
subscribers = this.subscribers[pubType],
i,
max = subscribers.length;
for (i = 0; i < max; i++) {
if (action === 'publish') {
subscribers[i](arg)
} else {
if (subscribers[i] === arg) {
subscribers.splice(i, 1)
}
}
}
}
}
function makePublisher(o) {
var i
for (i in publisher) {
if (publisher.hasOwnProperty(i) && typeof publisher[i] === "function") {
o[i] = publisher[i]
}
}
o.subscribers = { any: [] }
}

var paper = {
daily: function () {
this.publish('今天有大新闻!!!')
},
monthly: function () {
this.publish("分析", "monthly")
}
}

makePublisher(paper)

var joe = {
drinkCoffee: function (paper) {
console.log("阅读" + paper)
},
sundayPreNap: function (monthly) {
console.log('催眠的文章', monthly)
}
}
paper.subscribe(joe.drinkCoffee)
paper.subscribe(joe.sundayPreNap, 'monthly')

paper.daily()
paper.daily()
paper.daily()
paper.monthly()

示例 2:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<p>玩家1敲击键盘上的1,玩家2敲击键盘上的0(只有一分钟的时间!)</p>
<div id="results"></div>
</body>
<script>
var publisher = {
subscribers: {
any: [],
},
on: function (type, fn, context) {
type = type || "any";
fn = typeof fn === "function" ? fn : context[fn];
if (typeof this.subscribers[type] === "undefined") {
this.subscribers[type] = [];
}
this.subscribers[type].push({ fn: fn, context: context || this });
},
remove: function (type, fn, context) {
this.visitSubscribers("unsubscribe", type, fn, context);
},
fire: function (type, publication) {
this.visitSubscribers("publish", type, publication);
},
visitSubscribers: function (action, type, arg, context) {
let n = 0;
console.log(n);
var pubType = type || "any",
subscribers = this.subscribers[pubType],
i,
max = subscribers ? subscribers.length : 0;
for (i = 0; i < max; i++) {
if (action === "publish") {
subscribers[i].fn.call(subscribers[i].context, arg);
} else {
if (
subscribers[i].fn === arg &&
subscribers[i].context === context
) {
subscribers.splice(i, 1);
}
}
}
},
};
function makePublisher(o) {
var i;
for (i in publisher) {
if (publisher.hasOwnProperty(i) && typeof publisher[i] === "function") {
o[i] = publisher[i];
}
}
o.subscribers = { any: [] };
}
function Player(name, key) {
this.point = 0;
this.name = name;
this.key = key;
this.fire("newPlayer", this);
}
Player.prototype.play = function () {
this.point += 1;
this.fire("play", this);
};
var game = {
keys: {},
addPlayer: function (player) {
var key = player.key.toString().charCodeAt(0);
this.keys[key] = player;
},
handleKeypress: function (e) {
e = e || window.event;
if (game.keys[e.which]) {
game.keys[e.which].play();
}
},
handlePlay: function (player) {
var i,
players = this.keys,
score = {};
for (i in players) {
if (players.hasOwnProperty(i)) {
score[players[i].name] = players[i].points;
}
}
this.fire("scoreChange", score);
},
};
var scoreboard = {
element: document.getElementById("results"),
update: function (score) {
var i,
msg = "";
for (i in score) {
if (score.hasOwnProperty(i)) {
msg += `<p><strong>${i}</strong>:${score[i]}</p>`;
}
}
this.element.innerHTML = msg;
},
};
makePublisher(Player.prototype);
makePublisher(game);

Player.prototype.on("newPlayer", "addPlayer", game);
Player.prototype.on("play", "handlePlay", game);
game.on("scoreChange", scoreboard.update, scoreboard);
window.onkeypress = game.handleKeypress;

var playerName,
key,
num = 0;
while (num < 1) {
playerName = prompt("添加玩家");
if (!playerName) {
break;
}
while (num < 1) {
key = prompt(`玩家${playerName}的键位`);
num += 1;
if (key) {
break;
}
}
new Player(playerName, key);
}
</script>
</html>

上一篇:
CSS揭秘
下一篇:
代码复用模式