设计模式 - 笔记 - 结构型设计模式 (JavaScript实现)

创造型设计模式

关注于代码结构的优化,以降低系统耦合

外观模式

为一组复杂的子系统接口提供一个更高级的统一接口,通过这个接口访问子接口,简化使用,通过对接口方法的封装,提供给上层代码使用

  • 优点
    • 降低了子系统与客户端之间的耦合度,使得子系统的变化不会影响调用它的客户类
    • 对客户屏蔽了子系统组件,减少了客户处理的对象数目,并使得子系统使用起来更加容易
    • 降低了大型软件系统中的编译依赖性,简化了系统在不同平台之间的移植过程,编译一个子系统不会影响其他的子系统,也不会影响外观对象
  • 缺点
    • 不能很好地限制客户使用子系统类,很容易带来未知风险
    • 增加新的子系统可能需要修改外观类或客户端的源代码,违背了“开闭原则”
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const Facade = function () {
// 模拟一个存在差异性的对象
const obj = {
value: Math.random() * 2 > 1 ? 0 : 1,
otherValue: Math.random() * 2 > 1 ? 0 : 1
}
return {
getValue: function(){
return obj.value || obj.otherValue
},
getObj: function(){
return obj
}
}
}

适配器模式

将一个类的接口转换成另一个接口,以满足用户需求,使类中接口不兼容问题得到解决

  • 优点
    • 复用了现存的类,程序员不需要修改原有代码而重用现有的适配者类
    • 将目标类和适配者类解耦,解决了目标类和适配者类接口不一致的问题
  • 缺点
    • 适配器编写过程需要结合业务场景全面考虑,可能会增加系统的复杂性
    • 增加代码阅读难度,降低代码可读性,过多使用适配器会使系统代码变得凌乱
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const extend = function(_default, options) {
for (const key in _default) {
if (_default.hasOwnProperty(key)) {
const element = _default[key];
options[key] = options[key] || element
}
}
return options
}

// 数据适配器
const adapter = function(arr=[]) {
return {
name: arr[0],
age: arr[1],
}
}

代理模式

通过一个中介代理两个对象的传递

  • 优点
    • 客户端与目标间起到一个中介作用和保护目标对象的作用
    • 代理对象可以扩展目标对象的功能
    • 将客户端与目标对象分离,降低了系统的耦合度,增加可扩展性
  • 缺点
    • 会造成系统设计中类的数量增加
    • 增加一个代理对象,会造成请求处理速度变慢
    • 增加了系统的复杂度
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const Girl = function(name){
this.name = name
}

const Boy = function(name){
this.name = name
this.sendGift = function(gift, girl){
console.log(`${this.name}${gift}${girl.name}`)
}
}

const ProxySend = function(boy, girl) {
this.sendGift = function(gift){
boy.sendGift(gift, girl)
}
}

装饰者模式

在不改变对象的基础上,对其进行包装扩展,对原有对象进行一个扩展,是一种良性的扩展

  • 优点
    • 装饰器是继承的有力补充,比继承灵活,在不改变原有对象的情况下,动态的给一个对象扩展功能,即插即用
    • 通过使用不用装饰类及这些装饰类的排列组合,可以实现不同效果
    • 装饰器模式完全遵守开闭原则
  • 缺点
    • 饰器模式会增加许多子类,过度使用会增加程序得复杂性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const Person = function() {}
Person.prototype.getName = function(a, b, c) {
console.log('Person-getName',a, b, c)
}
const log = function(a, b, c) {
console.log('Decorator-',a, b, c)
}
const decorator = function(A, name, fn) {
var f = A.prototype[name]
var bind = function(){
fn.apply(this, arguments)
return f.apply(this, arguments)
}
A.prototype[name] = bind
}
decorator(Person, 'getName', log)

桥接模式

在系统沿着多个维度变化的同时,不增加其复杂度并达到解耦

  • 优点
    • 抽象与实现分离,扩展能力强
    • 符合开闭原则
    • 符合合成复用原则
    • 实现细节对客户透明
  • 缺点
    • 聚合关系建立在抽象层,要求开发者针对抽象化进行设计与编程,能正确地识别出系统中两个独立变化的维度,增加了系统的理解与设计难度
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
const Speed = function(x, y){
this.x = x
this.y = y
}
Speed.prototype.run = function(x, y){
console.log(`(${this.x}, ${this.y}) ==> (${x}, ${y})`)
this.x = x
this.y = y
}
// 颜色单元
const Color = function(color){
this.color = color
}
Color.prototype.draw = function(){
console.log(`绘制${this.color}`)
}
// 变形单元
const Shape = function(shape){
this.shape = shape
}
Shape.prototype.change = function(){
console.log(`变形 ${this.shape}`)
}
// 说话单元
const Speak = function(){

}
Speak.prototype.say = function(word){
console.log(`${word}`)
}
// 球类
const Ball = function(c, x=0, y=0){
this.speed = new Speed(x, y)
this.color = new Color(c)
}
// 精灵类
const Sprite = function(c, x, y, s){
this.shape = new Shape(s)
this.speed = new Speed(x, y)
this.color = new Color(c)
}
// 人类
const Person = function(x, y){
this.speed = new Speed(x, y)
this.speak = new Speak()
}

组合模式

部分-整体模式,将对象组合成树形结构以表示部分整体的层次结构

  • 优点
    • 组合模式使得客户端代码可以一致地处理单个对象和组合对象,无须关心自己处理的是单个对象,还是组合对象,这简化了客户端代码
    • 更容易在组合体内加入新的对象,客户端不会因为加入了新的对象而更改源代码,满足“开闭原则”
  • 缺点
    • 设计复杂,需要花更多时间理清类之间的层次关系
    • 不容易限制容器中的构件
    • 不容易用继承的方法来增加构件的新功能
      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

      const El = function(type, content='', children = []) {
      this.type = type
      this.content = content
      this.children = children
      }
      El.prototype.render = function(){
      return `<${this.type}>
      ${this.content}
      ${this.children.reduce((p, i) => p + i.render(), '')}
      </${this.type}>`
      }
      const News = function(){
      this.children = []
      this.element = null
      }
      News.prototype.init = function(){
      throw new Error('抽象方法')
      }
      News.prototype.add = function(){
      throw new Error('抽象方法')
      }
      News.prototype.getElement = function(){
      throw new Error('抽象方法')
      }

      const inherit = function(child, parent) {
      function F() {}
      F.prototype = new parent()
      child.constructor = child
      child.prototype = new F()
      }

      const Container = function(id, parent){
      News.call(this)
      this.id = id
      this.parent = parent
      this.init()
      }
      inherit(Container, News)
      Container.prototype.init = function(){
      this.element = new El('ul')
      this.element.id = this.id
      return this
      }
      Container.prototype.add = function(child){
      this.element.children.push(child)
      return this
      }
      Container.prototype.getElement = function(){
      return this.element.render()
      }

      const Item = function(id, parent){
      News.call(this)
      this.id = id
      this.parent = parent
      this.init()
      }
      inherit(Item, News)
      Item.prototype.init = function(){
      this.element = new El('li')
      this.element.id = this.id
      return this
      }
      Item.prototype.add = function(child){
      this.element.children.push(child)
      return this
      }
      Item.prototype.getElement = function(){
      return this.element.render()
      }

      const Group = function(id, parent){
      News.call(this)
      this.id = id
      this.parent = parent
      this.init()
      }
      inherit(Group, News)
      Group.prototype.init = function(){
      this.element = new El('div')
      this.element.id = this.id
      return this
      }
      Group.prototype.add = function(child){
      this.element.children.push(child)
      return this
      }
      Group.prototype.getElement = function(){
      return this.element.render()
      }

      const ImageNews = function(url, href){
      News.call(this)
      this.url = url
      this.href = href
      this.init()
      }
      inherit(ImageNews, News)
      ImageNews.prototype.init = function(){
      this.element = new El('a', this.href)
      this.element.children.push(new El('img', this.url))
      return this
      }
      ImageNews.prototype.add = function(child){
      this.element.children.push(child)
      return this
      }
      ImageNews.prototype.getElement = function(){
      return this.element.render()
      }

      const TextNews = function(content, href){
      News.call(this)
      this.content = content
      this.href = href
      this.init()
      }
      inherit(TextNews, News)
      TextNews.prototype.init = function(){
      this.element = new El('a', this.href)
      this.element.children.push(new El('span', this.content))
      return this
      }
      TextNews.prototype.add = function(child){
      this.element.children.push(child)
      return this
      }
      TextNews.prototype.getElement = function(){
      return this.element.render()
      }

      享元模式

运用共享技术有效支持大量的细粒度的对象,避免对象间拥有相同内容造成多余开销

  • 优点
    • 相同对象只要保存一份,这降低了系统中对象的数量,从而降低了系统中细粒度对象给内存带来的压力
  • 缺点
    • 为了使对象可以共享,需要将一些不能共享的状态外部化,这将增加程序的复杂性
    • 读取享元模式的外部状态会使得运行时间稍微变长
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

// 运动单元
const speed = {
run: function(x, y){
console.log(`(${this.x}, ${this.y}) ==> (${x}, ${y})`)
this.x = x
this.y = y
}
}
// 颜色单元
const color = {
draw: function(){
console.log(`绘制${this.color}`)
}
}
// 变形单元
const shape = {
change: function(){
console.log(`变形 ${this.shape}`)
}
}
// 说话单元
const speak = {
say: function(word){
console.log(`${word}`)
}
}
// 球类
const Ball = function(c, x=0, y=0){
this.x = x
this.y = y
this.color = c
}
Ball.prototype = {
...color,
...speed,
}

// 精灵类
const Sprite = function(c, x, y, s){
this.x = x
this.y = y
this.color = c
this.shape = s
}
Sprite.prototype = {
...color,
...speed,
...shape
}

// 人类
const Person = function(x, y){
this.x = x
this.y = y
}
Person.prototype = {
...speak,
...speed,
}

源码地址

结构型设计模式.png