这篇文章遵循GoF书中的脉络,本篇是这个系列的第4篇:Abstract Factory,因为下周不能按时更新,所以在周末赶写了这个post。欢迎大家访问[我的博客](http://zuozuohao.github.io/),代码可以在[@Zuozuohao](https://github.com/Zuozuohao/GolangGOFPatterns)下载。
GoF在第二章通过设计一个Lexi的文档编辑器来介绍设计模式的使用,在上一篇文章[每周一个GoLang设计模式之Decorator](http://zuozuohao.github.io/2016/07/06/Golang-Design-Patterns-Decorator/)中我们为Lexi的文本编辑器增加了Border和Scroller修饰图元。这一周我们将继续沿着GoF的脚步实现下一个设计模式:Abstract Factory模式。
**问题提出**
在搭建好Lexi文本编辑器的基本结构之后,面临着一个新的问题,就是如何解决软件的跨平台移植问题,主要有以下几个难点:
1. 不同操作系统视感标准之间的差异
2. 如何最大限度的对用户隐藏这些差异
Lexi用户界面看到的和操作的是一个图元,这些图元由可见图元(文字、图形等)和不可见图元(行、列等),操作系统负责绘制并且正确的展现他们。界面风格关于所谓的“窗口组件”(widgets)关于用户界面上作为控制元素的按钮、滚动条和菜单等可视图元的另一个术语。
GoF采用两个窗口组件来实现多个视感标准:
1) 第一个集合是由抽象[Glyph](http://zuozuohao.github.io/2016/06/25/Golang-Design-Patterns-Composite/))子类构成的。
2) 另一个集合是与抽象子类对应的实现不同视感标准的具体的子类的集合。
针对以上两点一滚动条Scroller为例进行说明,Scroller是Glyph的抽象子类,他扩展了Glyph的功能,增加了滚动条的职责。与此同时,由于不同操作系统视感标准之间的差异,我们必须设计针对不同操作系统的具体子类,例如具体子类MotifScroller和PMScroller分别针对Motif和PM风格的滚动条。
在完成基本策略之后,我们需要一个具体的方法来确定是创建Motif风格的MotifScroller,还是PM风格的PMScroller。好吧,GoF采用了抽象对象创建的过程来实现这个方案。
不同于直接创建MotifScroller,GoF增加了一个创建窗口组件的抽象工厂类GUIFactory负责组件的创建的具体子类的选择。而不同视图标准的工厂MotifFactory和PMFactory类负责创建各自风格的窗口组件,GUIFactory及其子类的层次结构如下图所示。
![](http://77fkk5.com1.z0.glb.clouddn.com/upload/image/551a1d85465111e697e4525400020562.png)
进而Glyph的子类图元,例如Scroller、Button等都可以根据视图风格进行动态切换,这些窗口组件之间的关系如下图所示:
![](http://77fkk5.com1.z0.glb.clouddn.com/upload/image/777688bc465111e697e4525400020562.png)
****Golang数据结构设计**
GoF设计的MotifFactory和PMFactory相当于两个可以创建视窗组件的具体类型,我们这里为了简便将其替换成MotifScroller和PMScroller两个结构体类型,具体的代码如下所示:
```
type MotifScroller struct{}
type PMScroller struct{}
```
**Golang接口实现**
至于提供接口契约的GUIFactory类型,我们这里用接口类型ScrollerFactoryer来代替,代码如下:
```
type ScrollerFactoryer interface {
ScrollerTo(position int)
}
func (b *MotifScroller) ScrollerTo(position int) {
fmt.Println("Create MotifScrollerBar")
fmt.Println("MotifScrollerBar move to position ", position)
}
func (b *PMScroller) ScrollerTo(position int) {
fmt.Println("Create PMScrollerBar")
fmt.Println("PMScrollerBar move to position ", position)
}
```
基本的接口设计完毕后,我们需要有一个根据具体的视窗标准进行动态选择创建具体视窗组件的接口,这里通过一个变量DisplayStandard和方法NewScrollerFactory()来实现,具体代码如下:
```
var DisplayStandard string = "Motif"
func NewScrollerFactory() (s ScrollerFactoryer) {
switch DisplayStandard {
case "Motif":
s = &MotifScroller{}
case "PM":
s = &PMScroller{}
}
return s
}
```
****Golang完整代码**
完成了整体的设计工作之后,我们就可以模拟不同视图标准情景下的窗口组件的动态创建过程,代码如下:
```
package main
import (
"fmt"
_ "reflect"
)
var DisplayStandard string = "Motif"
type ScrollerFactoryer interface {
ScrollerTo(position int)
}
type MotifScroller struct{}
type PMScroller struct{}
func NewScrollerFactory() (s ScrollerFactoryer) {
switch DisplayStandard {
case "Motif":
s = &MotifScroller{}
case "PM":
s = &PMScroller{}
}
return s
}
func (b *MotifScroller) ScrollerTo(position int) {
fmt.Println("Create MotifScrollerBar")
fmt.Println("MotifScrollerBar move to position ", position)
}
func (b *PMScroller) ScrollerTo(position int) {
fmt.Println("Create PMScrollerBar")
fmt.Println("PMScrollerBar move to position ", position)
}
func main() {
DisplayStandard = "PM"
scroFactory := NewScrollerFactory()
scroFactory.ScrollerTo(12)
}
```
输出:
```
Create PMScrollerBar
PMScrollerBar move to position 12
```
[点击这里可以试一下](https://play.golang.org/p/QUtToTWFZq)。
上面的例子中,你可以进行动态的扩展,比如在MotifScroller和PMScroller中添加button等组件,但是本质是一样的。
**Abstract Factory**
1. AbstractFactory描述了怎样在不直接实例化类的情况下创建一系列相关的产品对象。
2. 它最适用于产品对象的数目和种类不变,而具体产品系列之间存在不同的情况。
3. 通过实例化一个特定的具体工厂对象来选择产品系列,并且以后一直使用该工厂生产产品对象。
非常感谢您读完这篇冗长的文章,如有错误之处请指出,我会尽快修改,谢谢!
**其他链接**
1. [每周一个Golang设计模式之组合模式](http://zuozuohao.github.io/2016/06/25/Golang-Design-Patterns-Composite/)
2. [每周一个Golang设计模式之策略模式](http://zuozuohao.github.io/2016/06/30/Golang-Design-Patterns-Startergy/)
3. [每周一个Golang设计模式之Decorator](http://zuozuohao.github.io/2016/07/06/Golang-Design-Patterns-Decorator/)
4. [每周一个Golang设计模式之Abstract Factory](http://zuozuohao.github.io/2016/07/10/Golang-Design-Patterns-Abstract-Factory/)