设计模式-装饰器/Decorator模式


1. 从一个需求说起

01-basic
左边是一个输入框,右边是输出结果(只读)。现在要求点击读取按钮把左边文本框的内容读取到右边结果栏。
同时在读取的过程中,可以做一些操作,例如:

  1. 设置字体颜色
  2. 添加背景和边框
  3. 添加序号

基础功能代码(只把左边的值读取到右边)如下:

// 省略了css和head代码
<body>
<div class="box">
<div class="left">
<textarea rows="15"></textarea>
</div>
<div class="middle">
<button>读取</button>
</div>
<div class="right">
<div class="result">
</div>
</div>
</div>

<script>
class NormalReader {
constructor (txt) {
this.txt = txt;
this.buffer = txt.split(/[\n\r]/g);
this.index = 0;
}
readLine() {
if (this.index > this.buffer.length - 1) {
return;
}
return this.buffer[this.index++];
}
}
let btn = document.querySelector('button');
let result = document.querySelector('.result');

btn.addEventListener('click', e => {
const txt = document.querySelector('textarea').value;
const reader = new NormalReader(txt);
result.innerHTML = "";

let line;
while (line = reader.readLine()) {
const div = document.createElement('div');
div.innerHTML = line;
result.append(div);
}
});
</script>
</body>

2. 用继承的方式实现以上功能

  1. 设置字体颜色
class ColorReader extends NormalReader {
readLine() {
let line = super.readLine();
if (!line) return;
const span = document.createElement('span');
span.style.color = 'red';
span.innerHTML = line;
return span.outerHTML;
}
}

let btn = document.querySelector('button');
let result = document.querySelector('.result');

btn.addEventListener('click', e => {
const txt = document.querySelector('textarea').value;
const reader = new ColorReader(txt);
result.innerHTML = "";

let line;
while (line = reader.readLine()) {
const div = document.createElement('div');
div.innerHTML = line;
result.append(div);
}
});

  1. 添加背景和边框
class BoxReader extends ColorReader {
readLine() {
let line = super.readLine();
if (!line)
return;
const div = document.createElement('div');
div.style.border = '1px solid #999';
div.style.background = 'green';
div.style.height = '25px';
div.innerHTML = line;
return div.outerHTML;
}
}
// ...
btn.addEventListener('click', e => {
const txt = document.querySelector('textarea').value;
const reader = new BoxReader(txt);
// ...
});
  1. 添加序号
class OrderedReader extends BoxReader {
constructor(txt) {
super(txt);
this.num = 1;
}
readLine() {
let line = super.readLine();
if (!line)
return;

return `${this.num++} ${line}`;
}
}
// ...
btn.addEventListener('click', e => {
const txt = document.querySelector('textarea').value;
const reader = new OrderedReader(txt);
// ...
});

缺点:

用继承的方式,能实现这些功能,有个缺点,就是不方便组合这些功能,比如只想要添加序号 和 改变字体颜色这两个功能,那么需要修改OrderedReader的父类。

3. 装饰器模式

  1. 首先添加一个Decorator类

构造它是可以接受一个reader类,然后它提供readLine方法,它会调用reader的readLine方法。
其他实现具体功能的reader可以继承这个类,从而可以调用其他reader的方法。

class Decorator {
constructor(reader) {
this.reader = reader;
}
readLine() {
return this.reader.readLine();
}
}
  1. 改造其他reader类 (很小的改动,只需要修改继承的类名即可)
// 设置元素为红色
class ColorReader extends Decorator {
readLine() {
let line = super.readLine();
if (!line) return;
const span = document.createElement('span');
span.style.color = 'red';
span.innerHTML = line;
return span.outerHTML;
}
}
// 给元素添加绿色背景和边框
class BoxReader extends Decorator {
readLine() {
let line = super.readLine();
if (!line) return;
const div = document.createElement('div');
div.style.border = '1px solid #999';
div.style.background = 'green';
div.style.height = '25px';
div.innerHTML = line;
return div.outerHTML;
}
}
// 给元素添加序号
class OrderedReader extends Decorator {
constructor(txt) {
super(txt);
this.num = 1;
}
readLine() {
let line = super.readLine();
if (!line) return;

return `${this.num++} - ${line}`;
}
}

  1. 修改构造reader的方法,可以方便的组合各种功能
btn.addEventListener('click', e => {
const txt = document.querySelector('textarea').value;
// const reader = new BoxReader(new OrderedReader(new NormalReader(txt)));
// const reader = new ColorReader(new BoxReader(new OrderedReader(new NormalReader(txt))));
const reader = new ColorReader(new OrderedReader(new NormalReader(txt)));
// ...
});

结果:
02-result

4. 完整代码

<html>
<head>
<title>Test decorator design pattern</title>
<style>
.box {
width: 500px;
display: flex;
justify-content: space-between;
align-items: center;
}

.result {
border: 1px solid;
width: 200px;
height: 200px;
}
</style>
</head>
<body>
<h2>学习装饰器模式</h2>
<div class="box">
<div class="left">
<textarea rows="15"></textarea>
</div>
<div class="middle">
<button>读取</button>
</div>
<div class="right">
<div class="result">
</div>
</div>
</div>

<br>
<div>
总结:
<ul>
<li>继承:以继承的方式来实现,添加序号,设置字体颜色,添加边框,继承得到的功能都是静态的,无法动态组合各个功能点</li>
<li>装饰器模式:可以很好的组合各个功能,比如设置字体颜色和添加边框</li>
</ul>
</div>

<script>
class NormalReader {
constructor (txt) {
this.txt = txt;
this.buffer = txt.split(/[\n\r]/g);
this.index = 0;
}
readLine() {
console.log(this.buffer);
if (this.index > this.buffer.length - 1) {
return;
}
return this.buffer[this.index++];
}
}

class Decorator {
constructor(reader) {
this.reader = reader;
}
readLine() {
return this.reader.readLine();
}
}

// 设置元素为红色
class ColorReader extends Decorator {
readLine() {
let line = super.readLine();
if (!line)
return;
const span = document.createElement('span');
span.style.color = 'red';
span.innerHTML = line;
return span.outerHTML;
}
}
// 给元素添加绿色背景和边框
class BoxReader extends Decorator {
readLine() {
let line = super.readLine();
if (!line)
return;
const div = document.createElement('div');
div.style.border = '1px solid #999';
div.style.background = 'green';
div.style.height = '25px';
div.innerHTML = line;
return div.outerHTML;
}
}
// 给元素添加序号
class OrderedReader extends Decorator {
constructor(txt) {
super(txt);
this.num = 1;
}
readLine() {
let line = super.readLine();
if (!line)
return;

return `${this.num++} - ${line}`;
}
}

let btn = document.querySelector('button');
let result = document.querySelector('.result');

btn.addEventListener('click', e => {
const txt = document.querySelector('textarea').value;
// const reader = new BoxReader(new OrderedReader(new NormalReader(txt)));
// const reader = new ColorReader(new BoxReader(new OrderedReader(new NormalReader(txt))));
const reader = new ColorReader(new OrderedReader(new NormalReader(txt)));

result.innerHTML = "";

let line;
while (line = reader.readLine()) {
const div = document.createElement('div');
div.innerHTML = line;
result.append(div);
}
});
</script>
</body>
</html>

(说明:本文是学习了抖音大神的讲解视频,动手写并总结整理而成。少数代码细节可能有细微差别。通过实际案例,学习设计模式,特别清楚。分享出来,希望更多人能学习和掌握设计模式。)


文章作者: IT神助攻
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 IT神助攻 !
  目录