GO语言设计模式-工厂模式
2022-05-26 01:44:26    17    0    0
weibo-007

GO语言设计模式-工厂模式

工厂模式背景

假设一个场景,在一个基本的MVC框架中,视图层承担着渲染数据的职责,假如要实现一个框架的视图层,必须满足

  1. 支持html输出
  2. 支持json格式输出
  3. 支持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")
	}
}

这样做代码比较简单,但是,这样的实现存在很多问题。

  1. 无法灵活地扩展维护,如果再添加一个excel类型的输出,switch分支将变得老长老长
  2. 修改任何一个功能,都需要改动主体代码块
    实际上,这是一种典型的面向过程的编码方式,这种编码方式,不易分工,职责不明确,如果采用面向对象的方式来救场,针对接口编程而不是针对实现编程。因为这样的视图层在我们的前辈也一样会碰到,只不过他们把这种问题解决掉了,留给了我们后人一些很经典的解决问题的设计模式。接下面,使用设计模式中的工厂模式解决这个问题

工厂模式分类

工厂模式主要是为创建对象提供了接口。工厂模式按照《Java与模式》中的提法分为三类:

  1. 简单工厂模式(Simple Factory)
  2. 工厂方法模式(Factory Method)
  3. 抽象工厂模式(Abstract Factory)
    这三种模式从上到下逐步抽象,并且更具一般性。还有一种分类法,就是将简单工厂模式看为工厂方法模式的一种特例,两个归为一类。下面是使用工厂模式的两种情况:
    1.在编码时不能预见需要创建哪种类的实例。
    2.系统不应依赖于产品类实例如何被创建、组合和表达的细节

简单工厂模式

其实严格意义上来说,这并不算一种设计模式,或者可以说这是一种很好的封装方式。比如,可以解决我们上面的问题,我们可以做出以下优化

  1. 为每个View的实现约定一个接口,这个接口定义每个子类必须实现特定的方法
  2. 变化的动画抽到一个地方,编写一个专门管理视图类的地方

简单工厂模式可以把类的创建归属到一个专门的类中管理,我们称这个类为工厂

它由三种角色组成:

  1. 工厂类角色:这是本模式的核心,含有一定的商业逻辑和判断逻辑
  2. 抽象产品角色:它一般是具体产品继承的父类或者实现的接口
  3. 具体产品角色:工厂类所创建的对象就是此角色的实例
    使用简单工厂类我们可以优化上面的View实现,UML类图:

image

接下来,按照简单工厂的方式从新编写我们的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格式的输出,看一下我们需要做什么

  1. 继承ViewInterface编写JsonpView的具体实现
  2. ViewFactory工厂方法中添加case分支,是工厂方法支持jsonp格式的输出

问题就出在第2步,不符合我们面向对象编程中的对修改闭合,对扩展开放。现在我们使用工厂模式再来改造这个程序,工厂模式的定义:

工厂方法模式定义了了一个创建对象的接口,但由子类决定要实例化的类是哪一个。工厂方法让类把实例化推迟到子类。

工厂模式的角色分为工厂接口,具体工厂类,产品接口,具体产品类

  1. 工厂接口,抽象工厂创建者类
  2. 具体工厂类,能够生产产品的具体创建者类
  3. 产品接口,它一般是具体产品继承的父类或者实现的接口
  4. 具体产品角色:工厂类所创建的对象就是此角色的实例

可以画出UML类图
image

我们按照工厂模式再次改写我们的代码,产品接口和具体产品角色和上面一样,不变。我们实现一个工厂相关的类。

  • 工厂接口
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

 

 

Pre: GO语言设计模式 - 构建者模式

Next: GO语言设计模式-单例模式

17
Sign in to leave a comment.
No Leanote account? Sign up now.
0 comments
Table of content