當前位置:
首頁 >
前端技术
> javascript
>内容正文
javascript
javascript设计模式-模板方法模式(Template)
生活随笔
收集整理的這篇文章主要介紹了
javascript设计模式-模板方法模式(Template)
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
1 <!DOCTYPE HTML>
2 <html lang="en-US">
3 <head>
4 <meta charset="utf-8">
5 <title></title>
6 </head>
7 <body>
8
9 <script>
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 (function () {
48 // 示例代碼
49
50 // 定義模板方法,原語操作等的抽象類
51 function AbstractClass() {
52 }
53
54 AbstractClass.prototype = {
55 // 原語操作1,所謂的原語操作就是抽象的操作,必須要由子類提供實現
56 doPrimitiveOperation1: function () {
57 },
58 // 原語操作2
59 doPrimitiveOperation2: function () {
60 },
61 // 模板方法,定義算法骨架
62 templateMethod: function () {
63 this.doPrimitiveOperation1();
64 this.doPrimitiveOperation2();
65 }
66 };
67
68 function ConcreteClass() {
69 }
70
71 ConcreteClass.prototype = {
72 __proto__: AbstractClass.prototype,
73
74 doPrimitiveOperation1: function () {
75 // 具體的實現
76 },
77 doPrimitiveOperation2: function () {
78 // 具體的實現
79 }
80 };
81 }());
82
83 (function(){
84 // 驗證人員登錄的例子
85
86 // 封裝進行登錄控制所需要的數據
87 function LoginModel(){
88 // 登錄人員編號
89 this.loginId;
90 // 登錄密碼
91 this.pwd;
92 }
93
94 // 登錄控制的模板
95 function LoginTemplate(){}
96 LoginTemplate.prototype = {
97 // 判斷登錄數據是否正確,也就是是否能登錄成功
98 login: function(loginModel){
99 var dbLm = this.findLoginUser(loginModel.loginId);
100
101 if(dbLm) {
102 // 對密碼進行加密
103 var encryptPwd = this.encryptPwd(loginModel.pwd);
104 // 把加密后的密碼設置回到登錄數據模型中
105 loginModel.pwd = encryptPwd;
106 // 判斷是否匹配
107 return this.match(loginModel, dbLm);
108 }
109
110 return false;
111 },
112 // 根據登錄編號來查找和獲取存儲中相應的數據
113 findLoginUser: function(loginId){},
114 // 對密碼數據進行加密
115 encryptPwd: function(pwd){
116 return pwd;
117 },
118 // 判斷用戶填寫的登錄數據和存儲中對應的數據是否匹配得上
119 match: function(lm, dbLm){
120 return lm.loginId === dbLm.loginId
121 && lm.pwd === dbLm.pwd;
122 }
123 };
124
125 // 普通用戶登錄控制的邏輯處理
126 function NormalLogin(){}
127 NormalLogin.prototype = {
128 __proto__: LoginTemplate.prototype,
129
130 findLoginUser: function(loginId){
131 var lm = new LoginModel();
132 lm.loginId = loginId;
133 lm.pwd = 'testpwd';
134 return lm;
135 }
136 };
137
138 // 工作人員登錄控制的邏輯處理
139 function WorkerLogin(){}
140 WorkerLogin.prototype = {
141 __proto__: LoginTemplate.prototype,
142
143 findLoginUser: function(loginId){
144 var lm = new LoginModel();
145 lm.loginId = loginId;
146 lm.pwd = 'workerpwd';
147 return lm;
148 },
149 encryptPwd: function(pwd){
150 console.log('使用MD5進行密碼加密');
151 return pwd;
152 }
153 };
154
155 var lm = new LoginModel();
156 lm.loginId = 'admin';
157 lm.pwd = 'workerpwd';
158
159 var lt = new WorkerLogin();
160 var lt2 = new NormalLogin();
161
162 var flag = lt.login(lm);
163 console.log('可以登錄工作平臺=' + flag);
164
165 var flag2 = lt2.login(lm);
166 console.log('可以進行普通人員登錄=' + flag2);
167
168
169 // another style
170
171 function test(){
172 var crypto = require('crypto');
173 function createHmac(){
174 return crypto.createHmac('sha1', 'password');
175 }
176
177 // 封裝進行登錄控制所需要的數據
178 function LoginModel(){
179 // 登錄人員編號
180 this.loginId;
181 // 登錄密碼
182 this.pwd;
183 }
184
185 // 登錄控制的模板
186 function LoginTemplate(){}
187 LoginTemplate.prototype = {
188 // 判斷登錄數據是否正確,也就是是否能登錄成功
189 login: function(loginModel){
190 var dbLm = this.findLoginUser(loginModel.loginId);
191
192 if(dbLm) {
193 // 對密碼進行加密
194 var encryptPwd = this.encryptPwd(loginModel.pwd);
195 // 把加密后的密碼設置回到登錄數據模型中
196 loginModel.pwd = encryptPwd;
197 // 判斷是否匹配
198 return this.match(loginModel, dbLm);
199 }
200
201 return false;
202 },
203 // 根據登錄編號來查找和獲取存儲中相應的數據
204 findLoginUser: function(loginId){},
205 // 對密碼數據進行加密
206 encryptPwd: function(pwd){
207 return pwd;
208 },
209 // 判斷用戶填寫的登錄數據和存儲中對應的數據是否匹配得上
210 match: function(lm, dbLm){
211 return lm.loginId === dbLm.loginId
212 && lm.pwd === dbLm.pwd;
213 }
214 };
215
216 function createLoginClass(prop){
217 Template.prototype = LoginTemplate.prototype;
218
219 return Template;
220
221 function Template(){
222 for(var i in prop) {
223 if(!prop.hasOwnProperty(i)) continue;
224
225 this[i] = prop[i];
226 }
227 }
228 }
229
230 var NormalLogin = createLoginClass({
231 findLoginUser: function(loginId){
232 var lm = new LoginModel();
233 lm.loginId = loginId;
234 lm.pwd = 'testpwd';
235 return lm;
236 }
237 });
238
239 var WorkerLogin = createLoginClass({
240 findLoginUser: function(loginId){
241 var lm = new LoginModel();
242 lm.loginId = loginId;
243 lm.pwd = createHmac().update('workerpwd').digest("hex");
244 return lm;
245 },
246 encryptPwd: function(pwd){
247 console.log('使用MD5進行密碼加密');
248 return createHmac().update(pwd).digest('hex');
249 }
250 });
251
252 var lm = new LoginModel();
253 lm.loginId = 'admin';
254 lm.pwd = 'workerpwd';
255
256 var lt = new WorkerLogin();
257 var lt2 = new NormalLogin();
258
259 var flag = lt.login(lm);
260 console.log('可以登錄工作平臺=' + flag);
261
262 var flag2 = lt2.login(lm);
263 console.log('可以進行普通人員登錄=' + flag2);
264
265
266
267 // 擴展登錄控制
268
269 function NormalLoginModel(){
270 LoginModel.call(this);
271
272 // 密碼驗證問題
273 this.question;
274 // 密碼驗證答案
275 this.answer;
276 }
277
278 function NormalLogin2(){}
279 NormalLogin2.prototype = {
280 __proto__: LoginTemplate,
281
282 findLoginUser: function(loginId){
283 var nlm = new NormalLoginModel();
284 nlm.loginId = loginId;
285 nlm.pwd = 'testpwd';
286 nlm.question = 'testQuestion';
287 nlm.answer = 'testAnswer';
288
289 return nlm;
290 },
291 match: function(lm, dblm){
292 var f1 = LoginTemplate.prototype.match.apply(this,arguments);
293
294 if(f1) {
295 return dblm.question === lm.question
296 && dblm.answer === lm.answer;
297 }
298
299 return false;
300 }
301 };
302
303 var nlm = new NormalLoginModel();
304 nlm.loginId = 'testUser';
305 nlm.pwd = 'testpwd';
306 nlm.question = 'testQuestion';
307 nlm.answer = 'testAnswer';
308 var lt3 = new NormalLogin2();
309 var flag3 = lt3.login(nlm);
310 console.log('可以進行普通人員加強版登錄=' + flag3);
311
312 }
313
314 }());
315
316
317 (function () {
318 // 咖啡因飲料是一個抽象類
319 var CaffeineBeverage = function () {
320 };
321 CaffeineBeverage.prototype = {
322 /*---模板方法 ----*/
323 /**
324 * 它的用作一個算法的模板,在這個例子中,算法是用來制作咖啡因飲料的,
325 * 在這個模板中,算法內的每一個步驟都被一個方法代表了
326 */
327 prepareRecipe: function () {
328 this.boilWater();
329 this.brew();
330 this.pourInCup();
331 this.addConditions();
332 },
333 /*----------------*/
334 /* 因為咖啡和茶處理這些方法的做法不同,所以這兩個方法必須被聲明為抽象 */
335 brew: function () {
336 throw new Error('abstract brew method should be written.');
337 },
338 addConditions: function () {
339 throw new Error('abstract addConditions method should be written.');
340 },
341 /* ------------------------------- */
342 boilWater: function () {
343 console.log('boiling water');
344 },
345 pourInCup: function () {
346 console.log('pouring into cup');
347 }
348 };
349
350 var Tea = function () {
351 };
352 Tea.prototype = {
353 __proto__: CaffeineBeverage.prototype,
354
355 brew: function () {
356 console.log('steeping the tea.');
357 },
358 addConditions: function () {
359 console.log('adding lemon');
360 }
361 };
362
363 var Coffee = function () {
364 };
365 Coffee.prototype = {
366 __proto__: CaffeineBeverage.prototype,
367
368 brew: function () {
369 console.log('Dripping Coffee through filter');
370 },
371 addConditions: function () {
372 console.log('adding Sugar and Milk');
373 }
374 };
375
376 var myTea = new Tea();
377 myTea.prepareRecipe();
378 }());
379
380 /*
381 由CaffeineBeverage類主導一切,它擁有算法,而且保護這個算法。對子類來說,CaffeineBeverage類deep存在,可以將代碼的復用最大化。算法只存在于一個地方,所以容易修改。這個模板方法提供了一個框架,可以讓其他的咖啡因飲料插進去,新的咖啡因飲料只需要實現自己的方法就可以了。CaffeeineBeverage類專注在算法本身,而由子類提供完整的實現。
382 */
383
384 (function(){
385 /*
386 對模板方法進行掛鉤
387
388 鉤子是一種被聲明在抽象類中的方法,但只有空的或者默認的實現。鉤子的存在,可以讓子類有能力對算法的不同點進行掛鉤。要不要掛鉤,由子類自行決定。
389 */
390
391 // 高層組件,只有在需要子類實現某個方法時,方調用子類。
392 var CaffeineBeverageWithHook = function () {
393 };
394 CaffeineBeverageWithHook.prototype = {
395 prepareRecipe: function () {
396 this.boilWater();
397 this.brew();
398 this.pourInCup();
399 /*---------- 鉤子 ----------*/
400 if (this.customerWantsCondiments()) {
401 this.addCondiments();
402 }
403 /*---------------------------*/
404 },
405 brew: function () {
406 throw new Error('brew method should be rewritten.');
407 },
408 addCondiments: function () {
409 throw new Error('addCondiments method should be written.');
410 },
411 boilWater: function () {
412 console.log('Boiling water');
413 },
414 pourInCup: function () {
415 console.log('pourng into cup');
416 },
417 /*------- 鉤子方法 ------*/
418 customerWantsCondiments: function () {
419 return true;
420 }
421 /*----------------------*/
422 };
423
424 var CoffeeWithHook = function () {
425 };
426 CoffeeWithHook.prototype = {
427 __proto__: CaffeineBeverageWithHook.prototype,
428
429 brew: function () {
430 console.log('Dripping coffee through filter');
431 },
432 customerWantsCondiments: function () {
433 var answer = this.getUSerInput();
434
435 return answer === true;
436 },
437 getUSerInput: function () {
438 return confirm('Would you like milk and sugar with your coffee (y/n)?');
439 },
440 addCondiments: function () {
441 console.log('adding sugar and milk');
442 }
443 };
444
445 var coffeeHook = new CoffeeWithHook();
446 coffeeHook.prepareRecipe();
447 }());
448
449 /*
450 好萊塢原則
451
452 別調用我們,我們會調用你。
453
454 好萊塢原則可以給我們一種防止“依賴腐敗”的方法。當高層組件依賴低層組件,而低層組件又依賴高層組件,而高層組件又依賴邊側組件,而邊側組件又依賴低層組件時,依賴腐敗就發生了。在這種情況下,沒有人可以輕易地搞懂系統是如何設計的。
455 在好萊塢原則之下,我們允許低層組件將自己掛鉤到系統上,但是高層組件會決定什么時候和怎樣使用這些低層組件。換句話說,高層組建對待低層組件的方式是“別調用我們,我們會調用你”。
456 */
457
458 (function () {
459 /*
460 抽象類不一定包含抽象方法;有抽象方法的類一定是抽象類。
461 “既要約束子類的行為,又要為子類提供公共功能”的時候使用抽象類。
462 */
463
464 var Duck = function (name, weight) {
465 this.name = name;
466 this.weight = weight;
467 };
468 Duck.prototype = {
469 toString: function () {
470 return name + ' weighs ' + this.weight;
471 }
472 };
473
474 var ducks = [
475 new Duck('A', 8),
476 new Duck('B', 2),
477 new Duck('C', 7),
478 new Duck('D', 2),
479 new Duck('E', 10),
480 new Duck('E', 2)
481 ];
482 console.log('before');
483 display(ducks);
484
485 /*---------- 內置對象的模板方法 --------*/
486 ducks.sort(function (obj1, obj2) {
487 return obj1.weight - obj2.weight;
488 });
489 /*-------------------------------------*/
490
491 console.log('after');
492 display(ducks);
493
494 function display(arr) {
495 for (var i = 0, len = arr.length; i < len; i++) {
496 console.log(arr[i] + '');
497 }
498 }
499
500 /*
501 排序的算法步驟是固定的,也就是算法骨架是固定的了,只是其中具體比較數據大小的步驟,需要由外部來提供。
502 排序的實現,實際上組合使用了模板方法模式和策略模式,從整體來看是模板方法模式,但到了局部,比如排序比較算法的實現上,就是用的是策略模式了。
503 */
504 }());
505
506 /**
507 * 模板方法里面包含的操作類型:
508 * 1.模板方法: 就是定義算法骨架的方法。
509 * 2.具體的操作: 在模板中直接實現某些步驟的方法。通常這些步驟的實現算法是固定的,而且是不怎么變化的,因此可以將其當作公共功能實現在模板中。如果不需為子類提供訪問這些方法的話,還可以是私有的。這樣子類的視線就相對簡單些。
510 * 3.具體的AbstractClass操作: 在模板中實現某些公共的功能,可以提供給子類使用,一般不是具體的算法步驟實現,而是一些輔助的公共功能。
511 * 4.原語操作: 就是在模板中定義的抽象操作,通常是模板方法需要調用的操作,時必須的操作,而且在父類中還沒有辦法確定下來如何實現,需要子類來真正實現的方法。
512 * 5.鉤子操作: 在模板中定義,并提供默認實現的操作。這些方法通常被視為可擴展的點,但不是必需的,子類可以有選擇地覆蓋這些方法,已提供新的實現來擴展功能。
513 * 6.Factory Method:在模板方法中,如果需要得到某些對象實例的話,可以考慮通過工廠方法模式來獲取,把具體的構建對象的實現延遲到子類中去。
514 */
515
516 (function(){
517 // 一個較為完整的模板定義示例
518
519 function AbstractTemplate(){
520 // 模板方法,定義算法骨架
521 this.templateMethod = function(){
522 operation1();
523 this.operation2();
524 this.doPrimitiveOperation1();
525 this.dePrimitiveOperation2();
526 this.hookOperation();
527 }
528 // 具體操作2,算法中的步驟,固定實現,子類可能需要訪問
529 this.operation2 = function(){};
530 // 具體的AbstractClass操作,子類的公共方法,但通常不是具體的算法
531 this.commondOperationi = function(){};
532 // 原語操作1,算法中的步驟,父類無法確定如何真正實現,需要子類來實現
533 this.doPrimitiveOperation1 = function(){};
534 this.doPrimitiveOperation2 = function(){};
535 // 鉤子操作,算法中的步驟,可選,提供默認實現
536 // 由子類選擇并具體實現
537 this.hookOperationi = function(){};
538
539 // 具體操作1,算法中的步驟,固定實現,而且子類不需要訪問
540 function operation1(){}
541 // 工廠方法,創建某個對象,在算法實現中可能需要
542 this.createOneObject = function(){};
543 }
544 }());
545
546 /*
547 優點
548 實現代碼復用。
549 模板方法模式是一種實現代碼復用的很好的手段。通過把子類的公共功能提煉和抽取,把公共部分放到模板中去實現。
550
551
552 缺點
553 算法骨架不容易升級
554 模板方法模式最基本的功能就是通過模板的制定,把算法骨架完全固定下來。事實上模板和子類是非常耦合的,如果要對模板中的算法骨架進行變更,可能就會要求所有相關的子類進行相應的變化。所以抽取算法骨架的時候要特別小心,盡量確保不會變化的部分才放到模板中。
555 */
556
557 /*
558 何時使用
559
560 1.需要固定定義算法骨架,實現了一個算法的不變的部分,并把可變的行為留給子類來實現的情況。
561 2.各個子類中具有公共行為,應該抽取出來,集中在一個公共類去實現,從而避免代碼重復。
562 3.需要控制子類擴展的情況。模板方法模式會在特定的點來調用子類的方法,這樣只允許在這些點進行擴展。
563 */
564
565
566 // http://blog.csdn.net/dead_of_winter/article/details/2159420
567
568 function parent(prototype) {
569 return function () {
570 for (var p in o) this[p] = prototype[p];
571 // 模板方法
572 this.show = function () {
573 alert("show");
574 }
575 };
576 }
577
578 // 廣度優先搜索的例子
579
580 function BreadthFirstSearch(extend, beam, finish) {
581 return function () {
582 this.finish = finish;
583 this.extend = extend;
584 this.beam = beam;
585 this.search = function () {
586
587 var queue = [this];
588 while (queue.length) {
589 var current = queue.shift();
590 if (!current.beam()) {
591 var extended = current.extend();
592 for (var i = 0; i < extended.length; i++) {
593 if (extended[i].finish())return extended[i];
594 queue.push(extended[i]);
595 }
596 }
597 }
598 return null;
599 }
600 }
601 }
602
603
604 (function () {
605 // 解決八皇后問題的例子的例子
606
607 function Queen(n) {
608 var ret = new Array();
609 ret.size = n; //皇后問題的規模
610 ret.depth = 0; //搜索的深度
611 ret.pos = 0; //新皇后的水平位置
612 for (var y = 0; y < n; y++) {
613 ret.push([]);
614 for (var x = 0; x < n; x++)
615 ret[ret.length - 1].push(0);
616 }
617 function objectPrototypeClone() {
618 var tmp = function () {
619 };
620 tmp.prototype = this;
621 return new tmp;
622 }
623
624 ret.clone = function () {
625 var r = objectPrototypeClone.call(this);
626 for (var i = 0; i < n; i++) {
627 r[i] = objectPrototypeClone.call(this[i])
628 }
629 return r;
630 }
631 ret.toString = function () {
632 var str = "";
633 for (var y = 0; y < n; y++) {
634 for (var x = 0; x < n; x++)
635 str += this[y][x] == 0 ? "○" : "★";
636 str += " ";
637 }
638 return str;
639 }
640 return ret;
641 }
642
643 function extendQueen() {
644 var ret = new Array();
645 if (this.depth == this.size)return ret;
646 for (var i = 0; i < this.size; i++) {
647 var current = this.clone();
648 //alert(current.depth);
649 current[current.depth][i] = 1;
650 current.pos = i;
651 current.depth++;
652 ret.push(current);
653 }
654 return ret;
655 }
656
657 function beamQueen() {
658 var x, y;
659 if (this.depth == 0)return false;
660 if (this.depth == this.size)return true;
661 x = this.pos;
662 y = this.depth - 1;
663 while (--x >= 0 && --y >= 0)
664 if (this[y][x] != 0)return true;
665
666 x = this.pos;
667 y = this.depth - 1;
668 while (--y >= 0)
669 if (this[y][x] != 0)return true;
670
671 x = this.pos;
672 y = this.depth - 1;
673 while (--y >= 0 && ++x < this.size) {
674 if (this[y][x] != 0)return true;
675 }
676 return false;
677 }
678
679 function finishQueen() {
680
681 if (this.depth < this.size)return false;
682 x = this.pos;
683 y = this.depth - 1;
684 while (--x >= 0 && --y >= 0)
685 if (this[y][x] != 0)return false;
686
687 x = this.pos;
688 y = this.depth - 1;
689 while (--y >= 0)
690 if (this[y][x] != 0)return false;
691
692 x = this.pos;
693 y = this.depth - 1;
694 while (--y >= 0 && ++x < this.size) {
695 if (this[y][x] != 0)return false;
696 }
697
698 console.log(++count + ". " + this);
699 return false;
700 }
701
702 function BreadthFirstSearch(extend, beam, finish) {
703 return function () {
704 this.finish = finish;
705 this.extend = extend;
706 this.beam = beam;
707 this.search = function () {
708
709 var queue = [this];
710 while (queue.length) {
711 var current = queue.shift();
712 if (!current.beam()) {
713 var extended = current.extend();
714 for (var i = 0; i < extended.length; i++) {
715 if (extended[i].finish())return extended[i];
716 queue.push(extended[i]);
717 }
718 }
719 }
720 return null;
721 }
722 }
723 }
724
725 function BFSQueen(n) {
726 var ret = new Queen(n);
727 var BFS = new BreadthFirstSearch(extendQueen, beamQueen, finishQueen);
728 BFS.apply(ret);
729 return ret;
730 }
731
732 var queen = new BFSQueen(8);
733 var count = 0;
734 queen.search();
735 }());
736 </script>
737 </body>
738 </html>
?
轉載于:https://www.cnblogs.com/webFrontDev/archive/2013/06/02/3114045.html
總結
以上是生活随笔為你收集整理的javascript设计模式-模板方法模式(Template)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 黑马程序员-Java基础-正则表达式
- 下一篇: ReflectionLabel(倒影控件