GO语言设计模式-工厂模式
工厂模式背景
假设一个场景,在一个基本的MVC框架中,视图层承担着渲染数据的职责,假如要实现一个框架的视图层,必须满足
- 支持html输出
- 支持json格式输出
- 支持xml格式输出
于是,我们可以这样写出一个视图调用的展示程序View
:
func render(typ string, data interface{}) { switch typ { case "json": bytedata, _ := json.Marshal(data) fmt.Println(string(bytedata)) case "xml": bytedata, _ := xml.Marshal(data) fmt.Println(string(bytedata)) //...继续延续其他类型 default: panic("type err") } }
这样做代码比较简单,但是,这样的实现存在很多问题。
- 无法灵活地
扩展
和维护
,如果再添加一个excel类型的输出,switch分支将变得老长老长 - 修改任何一个功能,都需要改动主体代码块
实际上,这是一种典型的面向过程的编码方式,这种编码方式,不易分工,职责不明确,如果采用面向对象的方式来救场,针对接口编程
而不是针对实现编程。因为这样的视图层在我们的前辈也一样会碰到,只不过他们把这种问题解决掉了,留给了我们后人一些很经典的解决问题的设计模式
。接下面,使用设计模式中的工厂模式
解决这个问题
工厂模式分类
工厂模式主要是为创建对象提供了接口。工厂模式按照《Java与模式》中的提法分为三类:
- 简单工厂模式(Simple Factory)
- 工厂方法模式(Factory Method)
- 抽象工厂模式(Abstract Factory)
这三种模式从上到下逐步抽象,并且更具一般性。还有一种分类法,就是将简单工厂模式看为工厂方法模式的一种特例,两个归为一类。下面是使用工厂模式的两种情况:
1.在编码时不能预见需要创建哪种类的实例。
2.系统不应依赖于产品类实例如何被创建、组合和表达的细节
简单工厂模式
其实严格意义上来说,这并不算一种设计模式,或者可以说这是一种很好的封装方式。比如,可以解决我们上面的问题,我们可以做出以下优化
- 为每个View的实现约定一个接口,这个接口定义每个子类必须实现特定的方法
- 把
变化
的动画抽到一个地方,编写一个专门管理视图类的地方
简单工厂模式可以把类的创建归属到一个专门的类中管理,我们称这个类为工厂
。
它由三种角色组成:
- 工厂类角色:这是本模式的核心,含有一定的商业逻辑和判断逻辑
- 抽象产品角色:它一般是具体产品继承的父类或者实现的接口
- 具体产品角色:工厂类所创建的对象就是此角色的实例
使用简单工厂类我们可以优化上面的View实现,UML类图:
接下来,按照简单工厂的方式从新编写我们的View层
首先我们定义一个接口,这个接口规定每种视图必须要实现的方法,这个接口就担任了抽象产品角色
//抽象产品 type View interface { Render(data interface{}) []byte }
因为我们要实现json,xml,html几种数据格式的渲染,所以这些具体的实现称之为具体产品角色
Json和xml的的具体实现
//json具体产品实现 type JsonView struct { } func (jv *JsonView) Render(data interface{}) []byte { bytedata, err := json.Marshal(data) if err != nil { fmt.Printf("error: %v\n", err) } return bytedata } //xml具体产品实现 type XmlView struct { } func (xv *XmlView) Render(data interface{}) []byte { bytedata, err := xml.Marshal(data) if err != nil { fmt.Printf("error: %v\n", err) } return bytedata }
功能我们都写完了,应用方要找到这些功能,就剩下一个中介了,我们需要一个工厂类角色
来更具不同的type来实例化相应的类
//view工厂 func ViewFactory(typ string) View { switch typ { case "json": return &JsonView{} case "xml": return &XmlView{} } return nil }
客户端使用
package main import ( "fmt" "learn/design" ) type Data struct { Name string } func main() { var data = Data{ Name: "test", } viewFactory := design.ViewFactory("json") viewFata := viewFactory.Render(data) fmt.Println(viewFata) }
工厂方法模式
前面介绍简单工厂模式的并不算是工厂模式,现在要介绍的才是真正的工厂模式。试想一下,假如我们上面涉及的View
实现需要再加一种jsonp
格式的输出,看一下我们需要做什么
- 继承
ViewInterface
编写JsonpView
的具体实现 - 在
ViewFactory
工厂方法中添加case分支,是工厂方法支持jsonp格式的输出
问题就出在第2步,不符合我们面向对象编程中的对修改闭合,对扩展开放
。现在我们使用工厂模式再来改造这个程序,工厂模式的定义:
工厂方法模式定义了了一个创建对象的接口,但由子类决定要实例化的类是哪一个。工厂方法让类把实例化推迟到子类。
工厂模式的角色分为工厂接口,具体工厂类,产品接口,具体产品类
- 工厂接口,抽象工厂创建者类
- 具体工厂类,能够生产产品的具体创建者类
- 产品接口,它一般是具体产品继承的父类或者实现的接口
- 具体产品角色:工厂类所创建的对象就是此角色的实例
可以画出UML类图
我们按照工厂模式再次改写我们的代码,产品接口和具体产品角色和上面一样,不变。我们实现一个工厂相关的类。
- 工厂接口
type ViewFactory interface { CreateView() View }
- 产品接口
type View interface { Render(data interface{}) []byte }
- json工厂
type JsonViewFactory struct { } func (jvf *JsonViewFactory) CreateView() View { return &JsonView{} }
- json产品
type JsonView struct { } func (jv *JsonView) Render(data interface{}) []byte { bytedata, err := json.Marshal(data) if err != nil { fmt.Printf("error: %v\n", err) } return bytedata }
客户端使用:
package main import ( "fmt" "learn/design" ) type Data struct { Name string } func main() { data := Data{ Name: "test", } jsonFactory := (&design.JsonViewFactory{}).CreateView() viewData := jsonFactory.Render(data) fmt.Println(viewData) }
工厂方法的优缺点:
- 优点
1)复合开闭原则,增加一种产品时,只需要增加一个工厂类和产品类
2)符合单一指责原则,不像简单工厂中的各种switch(注意:得客户端知道使用哪个工厂)
3)客户端只需要知道相应的工厂,不关心实现
- 缺点
1)每新增一个产品时,都需要新增相应的工厂类
2)一个具体工厂只能生产一种具体产品
抽象工厂模式
抽象工厂模式,在工厂方法模式中再创建一层,工厂的工厂
参考这位作者的见解,确实强:https://blog.csdn.net/qq_33732195/article/details/110101808
No Leanote account? Sign up now.