前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >GO语言实现设计模式之【创建型模式】

GO语言实现设计模式之【创建型模式】

原创
作者头像
陈皮
发布2022-01-22 11:21:05
3110
发布2022-01-22 11:21:05
举报

一、工厂方法模式

概念

定义一个用于创建对象的接口,让子类决定实例化哪一个类。

实例

摊煎饼的小贩需要先摊个煎饼,再卖出去,摊煎饼就可以类比为一个工厂方法,根据顾客的喜好摊出不同口味的煎饼。

接口

代码语言:javascript
复制
package factory_method

// Pancake 煎饼
type Pancake interface {
	// ShowFlour 煎饼使用的面粉
	ShowFlour() string
	// Value 煎饼价格
	Value() float32
}

// PancakeCook 煎饼厨师
type PancakeCook interface {
	// MakePancake 摊煎饼
	MakePancake() Pancake
}

// PancakeVendor 煎饼小贩
type PancakeVendor struct {
	PancakeCook
}

// NewPancakeVendor ...
func NewPancakeVendor(cook PancakeCook) *PancakeVendor {
	return &PancakeVendor{
		PancakeCook: cook,
	}
}

// SellPancake 卖煎饼,先摊煎饼,再卖
func (vendor *PancakeVendor) SellPancake() (money float32) {
	return vendor.MakePancake().Value()
}

实现

各种面的煎饼实现

代码语言:javascript
复制
package factory_method

// cornPancake 玉米面煎饼
type cornPancake struct{}

// NewCornPancake ...
func NewCornPancake() *cornPancake {
	return &cornPancake{}
}

func (cake *cornPancake) ShowFlour() string {
	return "玉米面"
}

func (cake *cornPancake) Value() float32 {
	return 5.0
}

// milletPancake 小米面煎饼
type milletPancake struct{}

func NewMilletPancake() *milletPancake {
	return &milletPancake{}
}

func (cake *milletPancake) ShowFlour() string {
	return "小米面"
}

func (cake *milletPancake) Value() float32 {
	return 8.0
}

 制作各种口味煎饼的工厂方法实现

代码语言:javascript
复制
package factory_method

// cornPancakeCook 制作玉米面煎饼厨师
type cornPancakeCook struct{}

func NewCornPancakeCook() *cornPancakeCook {
	return &cornPancakeCook{}
}

func (cook *cornPancakeCook) MakePancake() Pancake {
	return NewCornPancake()
}

// milletPancakeCook 制作小米面煎饼厨师
type milletPancakeCook struct{}

func NewMilletPancakeCook() *milletPancakeCook {
	return &milletPancakeCook{}
}

func (cook *milletPancakeCook) MakePancake() Pancake {
	return NewMilletPancake()
}

运用

代码语言:javascript
复制
package factory_method

import (
	"fmt"
	"testing"
)

func TestFactoryMethod(t *testing.T) {
	pancakeVendor := NewPancakeVendor(NewCornPancakeCook())
	fmt.Printf("Corn pancake value is %v\n", pancakeVendor.SellPancake())

	pancakeVendor = NewPancakeVendor(NewMilletPancakeCook())
	fmt.Printf("Millet pancake value is %v\n", pancakeVendor.SellPancake())
}

 输出:

代码语言:javascript
复制
=== RUN   TestFactoryMethod
Corn pancake value is 5
Millet pancake value is 8
--- PASS: TestFactoryMethod (0.00s)
PASS

二、抽象工厂模式

概念

提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。

实例

厨师准备一餐时,会分别做吃的和喝的,根据早、中、晚三餐饮食习惯,会分别制作不同的饮食,厨师就相当于抽象工厂,制作三餐的不同烹饪方式就好比不同抽象工厂的实现。

接口

代码语言:javascript
复制
package abstract_factory
// Cook 厨师接口,抽象工厂
type Cook interface {
	// MakeFood 制作主食
	MakeFood() Food
	// MakeDrink 制作饮品
	MakeDrink() Drink
}

// Food 主食接口
type Food interface {
	// Eaten 被吃
	Eaten() string
}

// Drink 饮品接口
type Drink interface {
	// Drunk 被喝
	Drunk() string
}

实现

三餐不同厨师接口的实现

代码语言:javascript
复制
package abstract_factory

// breakfastCook 早餐厨师
type breakfastCook struct{}

func NewBreakfastCook() *breakfastCook {
	return &breakfastCook{}
}

func (b *breakfastCook) MakeFood() Food {
	return &cakeFood{"切片面包"}
}

func (b *breakfastCook) MakeDrink() Drink {
	return &gruelDrink{"小米粥"}
}

// lunchCook 午餐厨师
type lunchCook struct{}

func NewLunchCook() *lunchCook {
	return &lunchCook{}
}

func (l *lunchCook) MakeFood() Food {
	return &dishFood{"烤全羊"}
}

func (l *lunchCook) MakeDrink() Drink {
	return &sodaDrink{"冰镇可口可乐"}
}

// dinnerCook 晚餐厨师
type dinnerCook struct{}

func NewDinnerCook() *dinnerCook {
	return &dinnerCook{}
}

func (d *dinnerCook) MakeFood() Food {
	return &noodleFood{"大盘鸡拌面"}
}

func (d *dinnerCook) MakeDrink() Drink {
	return &soupDrink{"西红柿鸡蛋汤"}
}

 不同吃的

代码语言:javascript
复制
package abstract_factory

import "fmt"

// cakeFood 蛋糕
type cakeFood struct {
	cakeName string
}

func (c *cakeFood) Eaten() string {
	return fmt.Sprintf("%v被吃", c.cakeName)
}

// dishFood 菜肴
type dishFood struct {
	dishName string
}

func (d *dishFood) Eaten() string {
	return fmt.Sprintf("%v被吃", d.dishName)
}

// noodleFood 面条
type noodleFood struct {
	noodleName string
}

func (n *noodleFood) Eaten() string {
	return fmt.Sprintf("%v被吃", n.noodleName)
}

 不同喝的

代码语言:javascript
复制
package abstract_factory

import "fmt"

// gruelDrink 粥
type gruelDrink struct {
	gruelName string
}

func (g *gruelDrink) Drunk() string {
	return fmt.Sprintf("%v被喝", g.gruelName)
}

// sodaDrink 汽水
type sodaDrink struct {
	sodaName string
}

func (s *sodaDrink) Drunk() string {
	return fmt.Sprintf("%v被喝", s.sodaName)
}

// soupDrink 汤
type soupDrink struct {
	soupName string
}

func (s *soupDrink) Drunk() string {
	return fmt.Sprintf("%v被喝", s.soupName)
}

运用

代码语言:javascript
复制
package abstract_factory

import (
	"fmt"
	"testing"
)

func TestAbstractFactory(t *testing.T) {
	fmt.Printf("breakfast: %v\n", HaveMeal(NewBreakfastCook()))
	fmt.Printf("lunch: %v\n", HaveMeal(NewLunchCook()))
	fmt.Printf("dinner: %v\n", HaveMeal(NewDinnerCook()))
}

// HaveMeal 吃饭
func HaveMeal(cook Cook) string {
	return fmt.Sprintf("%s %s", cook.MakeFood().Eaten(), cook.MakeDrink().Drunk())
}

 输出

代码语言:javascript
复制
=== RUN   TestAbstractFactory
breakfast: 切片面包被吃 小米粥被喝
lunch: 烤全羊被吃 冰镇可口可乐被喝
dinner: 大盘鸡拌面被吃 西红柿鸡蛋汤被喝
--- PASS: TestAbstractFactory (0.00s)
PASS

三、生成器模式

概念

将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。

实例

还是摊煎饼的例子,摊煎饼分为四个步骤,1放面糊、2放鸡蛋、3放调料、4放薄脆,通过四个创建过程,制作好一个煎饼,这个摊煎饼的过程就好比煎饼生成器接口,不同生成器的实现就相当于摊不同品类的煎饼,比如正常的煎饼,健康的煎饼(可能用的是粗粮面、柴鸡蛋、非油炸薄脆、不放酱等),生成器接口方法也可以通过参数控制煎饼的大小,比如放两勺面糊,放2个鸡蛋等。

生成器的使用者为了避免每次都调用相同的构建步骤,也可以通过包装类固定几种构建过程,生成几类常用的产品,就好像摊煎饼有几类常卖固定成品,比如普通的,加两个鸡蛋的,不要香菜的等等,这几类固定构建过程提前定制好,直接通过简单工厂方法就直接创建,如果用户再需要细粒度的定制构建,再通过生成器创建。

接口

代码语言:javascript
复制
package builder

// Quantity 分量
type Quantity int

const (
	Small  Quantity = 1
	Middle Quantity = 5
	Large  Quantity = 10
)

type PancakeBuilder interface {
	// PutPaste 放面糊
	PutPaste(quantity Quantity)
	// PutEgg 放鸡蛋
	PutEgg(num int)
	// PutWafer 放薄脆
	PutWafer()
	// PutFlavour 放调料 Coriander香菜,Shallot葱 Sauce酱
	PutFlavour(hasCoriander, hasShallot, hasSauce bool)
	// Build 摊煎饼
	Build() *Pancake
}

// Pancake  煎饼
type Pancake struct {
	pasteQuantity Quantity // 面糊分量
	eggNum        int      // 鸡蛋数量
	wafer         string   // 薄脆
	hasCoriander  bool     // 是否放香菜
	hasShallot    bool     // 是否放葱
	hasSauce      bool     // 是否放酱
}

实现

正常煎饼创建器

代码语言:javascript
复制
package builder

type normalPancakeBuilder struct {
	pasteQuantity Quantity // 面糊量
	eggNum        int      // 鸡蛋数量
	friedWafer    string   // 油炸薄脆
	hasCoriander  bool     // 是否放香菜
	hasShallot    bool     // 是否放葱
	hasHotSauce   bool     // 是否放辣味酱
}

func NewNormalPancakeBuilder() *normalPancakeBuilder {
	return &normalPancakeBuilder{}
}

func (n *normalPancakeBuilder) PutPaste(quantity Quantity) {
	n.pasteQuantity = quantity
}

func (n *normalPancakeBuilder) PutEgg(num int) {
	n.eggNum = num
}

func (n *normalPancakeBuilder) PutWafer() {
	n.friedWafer = "油炸的薄脆"
}

func (n *normalPancakeBuilder) PutFlavour(hasCoriander, hasShallot, hasSauce bool) {
	n.hasCoriander = hasCoriander
	n.hasShallot = hasShallot
	n.hasHotSauce = hasSauce
}

func (n *normalPancakeBuilder) Build() *Pancake {
	return &Pancake{
		pasteQuantity: n.pasteQuantity,
		eggNum:        n.eggNum,
		wafer:         n.friedWafer,
		hasCoriander:  n.hasCoriander,
		hasShallot:    n.hasShallot,
		hasSauce:      n.hasHotSauce,
	}
}

 健康煎饼创建器

代码语言:javascript
复制
package builder

type healthyPancakeBuilder struct {
	milletPasteQuantity Quantity // 小米面糊量
	chaiEggNum          int      // 柴鸡蛋数量
	nonFriedWafer       string   // 非油炸薄脆
	hasCoriander        bool     // 是否放香菜
	hasShallot          bool     // 是否放葱
}

func NewHealthyPancakeBuilder() *healthyPancakeBuilder {
	return &healthyPancakeBuilder{}
}

func (n *healthyPancakeBuilder) PutPaste(quantity Quantity) {
	n.milletPasteQuantity = quantity
}

func (n *healthyPancakeBuilder) PutEgg(num int) {
	n.chaiEggNum = num
}

func (n *healthyPancakeBuilder) PutWafer() {
	n.nonFriedWafer = "非油炸的薄脆"
}

func (n *healthyPancakeBuilder) PutFlavour(hasCoriander, hasShallot, _ bool) {
	n.hasCoriander = hasCoriander
	n.hasShallot = hasShallot
}

func (n *healthyPancakeBuilder) Build() *Pancake {
	return &Pancake{
		pasteQuantity: n.milletPasteQuantity,
		eggNum:        n.chaiEggNum,
		wafer:         n.nonFriedWafer,
		hasCoriander:  n.hasCoriander,
		hasShallot:    n.hasShallot,
		hasSauce:      false,
	}
}

 煎饼生成器的封装类-厨师

代码语言:javascript
复制
package builder

// PancakeCook 摊煎饼师傅
type PancakeCook struct {
	builder PancakeBuilder
}

func NewPancakeCook(builder PancakeBuilder) *PancakeCook {
	return &PancakeCook{
		builder: builder,
	}
}

// SetPancakeBuilder 重新设置煎饼构造器
func (p *PancakeCook) SetPancakeBuilder(builder PancakeBuilder) {
	p.builder = builder
}

// MakePancake 摊一个一般煎饼
func (p *PancakeCook) MakePancake() *Pancake {
	p.builder.PutPaste(Middle)
	p.builder.PutEgg(1)
	p.builder.PutWafer()
	p.builder.PutFlavour(true, true, true)
	return p.builder.Build()
}

// MakeBigPancake 摊一个巨无霸煎饼
func (p *PancakeCook) MakeBigPancake() *Pancake {
	p.builder.PutPaste(Large)
	p.builder.PutEgg(3)
	p.builder.PutWafer()
	p.builder.PutFlavour(true, true, true)
	return p.builder.Build()
}

// MakePancakeForFlavour 摊一个自选调料霸煎饼
func (p *PancakeCook) MakePancakeForFlavour(hasCoriander, hasShallot, hasSauce bool) *Pancake {
	p.builder.PutPaste(Large)
	p.builder.PutEgg(3)
	p.builder.PutWafer()
	p.builder.PutFlavour(hasCoriander, hasShallot, hasSauce)
	return p.builder.Build()
}

运用

代码语言:javascript
复制
package builder

import (
	"fmt"
	"testing"
)

func TestBuilder(t *testing.T) {
	pancakeCook := NewPancakeCook(NewNormalPancakeBuilder())
	fmt.Printf("摊一个普通煎饼 %#v\n", pancakeCook.MakePancake())

	pancakeCook.SetPancakeBuilder(NewHealthyPancakeBuilder())
	fmt.Printf("摊一个健康的加量煎饼 %#v\n", pancakeCook.MakeBigPancake())
}

 输出

代码语言:javascript
复制
=== RUN   TestBuilder
摊一个普通煎饼 &builder.Pancake{pasteQuantity:5, eggNum:1, wafer:"油炸的薄脆", hasCoriander:true, hasShallot:true, hasSauce:true}
摊一个健康的加量煎饼 &builder.Pancake{pasteQuantity:10, eggNum:3, wafer:"非油炸的薄脆", hasCoriander:true, hasShallot:true, hasSauce:false}
--- PASS: TestBuilder (0.00s)
PASS

四、原型模式

概念

用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。用于解决对象私有域无法显示拷贝的问题。

实例

纸质文件可以通过复印机轻松拷贝出多份,设置Paper接口,包含读取文件内容和克隆文件两个方法。同时声明两个类报纸(Newspaper)和简历(Resume)实现了Paper接口,通过复印机(Copier)复印出两类文件的副本,并读取文件副本内容。

接口实现

代码语言:javascript
复制
package prototype

import (
	"bytes"
	"fmt"
	"io"
)

// Paper 纸张,包含读取内容的方法,拷贝纸张的方法,作为原型模式接口
type Paper interface {
	io.Reader
	Clone() Paper
}

// Newspaper 报纸 实现原型接口
type Newspaper struct {
	headline string
	content  string
}

func NewNewspaper(headline string, content string) *Newspaper {
	return &Newspaper{
		headline: headline,
		content:  content,
	}
}

func (np *Newspaper) Read(p []byte) (n int, err error) {
	buf := bytes.NewBufferString(fmt.Sprintf("headline:%s,content:%s", np.headline, np.content))
	return buf.Read(p)
}

func (np *Newspaper) Clone() Paper {
	return &Newspaper{
		headline: np.headline + "_copied",
		content:  np.content,
	}
}

// Resume 简历 实现原型接口
type Resume struct {
	name       string
	age        int
	experience string
}

func NewResume(name string, age int, experience string) *Resume {
	return &Resume{
		name:       name,
		age:        age,
		experience: experience,
	}
}

func (r *Resume) Read(p []byte) (n int, err error) {
	buf := bytes.NewBufferString(fmt.Sprintf("name:%s,age:%d,experience:%s", r.name, r.age, r.experience))
	return buf.Read(p)
}

func (r *Resume) Clone() Paper {
	return &Resume{
		name:       r.name + "_copied",
		age:        r.age,
		experience: r.experience,
	}
}

运用

代码语言:javascript
复制
package prototype

import (
	"fmt"
	"reflect"
	"testing"
)

func TestPrototype(t *testing.T) {
	copier := NewCopier("云打印机")
	oneNewspaper := NewNewspaper("Go是最好的编程语言", "Go语言十大优势")
	oneResume := NewResume("小明", 29, "5年码农")

	otherNewspaper := copier.copy(oneNewspaper)
	copyNewspaperMsg := make([]byte, 100)
	byteSize, _ := otherNewspaper.Read(copyNewspaperMsg)
	fmt.Println("copyNewspaperMsg:" + string(copyNewspaperMsg[:byteSize]))

	otherResume := copier.copy(oneResume)
	copyResumeMsg := make([]byte, 100)
	byteSize, _ = otherResume.Read(copyResumeMsg)
	fmt.Println("copyResumeMsg:" + string(copyResumeMsg[:byteSize]))
}

// Copier 复印机
type Copier struct {
	name string
}

func NewCopier(n string) *Copier {
	return &Copier{name: n}
}

func (c *Copier) copy(paper Paper) Paper {
	fmt.Printf("copier name:%v is copying:%v ", c.name, reflect.TypeOf(paper).String())
	return paper.Clone()
}

 输出

代码语言:javascript
复制
=== RUN   TestPrototype
copier name:云打印机 is copying:*prototype.Newspaper copyNewspaperMsg:headline:Go是最好的编程语言_copied,content:Go语言十大优势
copier name:云打印机 is copying:*prototype.Resume copyResumeMsg:name:小明_copied,age:29,experience:5年码农
--- PASS: TestPrototype (0.00s)
PASS

五、单例模式

概念

保证一个类仅有一个实例,并提供一个访问它的全局访问点

实例

通过地球对象实现单例,earth不能导出,通过TheEarth方法访问全局唯一实例,并通过sync.Once实现多协程下一次加载。

接口实现

代码语言:javascript
复制
package singleton

import "sync"

var once sync.Once

// 不可导出对象
type earth struct {
	desc string
}

func (e *earth) String() string {
	return e.desc
}

// theEarth 地球单实例
var theEarth *earth

// TheEarth 获取地球单实例
func TheEarth() *earth {
	if theEarth == nil {
		once.Do(func() {
			theEarth = &earth{
				desc: "美丽的地球,孕育了生命。",
			}
		})
	}
	return theEarth
}

运用

代码语言:javascript
复制
package singleton

import (
	"fmt"
	"testing"
)

func TestSingleton(t *testing.T) {
	fmt.Println(TheEarth().String())
}

 输出

代码语言:javascript
复制
=== RUN   TestSingleton
美丽的地球,孕育了生命。
--- PASS: TestSingleton (0.00s)
PASS

写到最后,其实设计模式每一种基本实现都会根据使用场景的不同有很多变体,不同的设计模式在不同的场景下又回产生组合,所以使用设计模式一定不要教条,根据不同场景灵活变通,这是最难的,后面也会持续更新一些通用场景下的设计模式在重构过程中的应用,最终让代码变得易维护,降低开发维护成本才是目的。

持续更新......

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、工厂方法模式
    • 概念
      • 实例
        • 接口
        • 实现
        • 运用
    • 二、抽象工厂模式
      • 概念
        • 实例
          • 接口
          • 实现
          • 运用
      • 三、生成器模式
        • 概念
          • 实例
            • 接口
            • 实现
            • 运用
        • 四、原型模式
          • 概念
            • 实例
              • 接口实现
              • 运用
          • 五、单例模式
            • 概念
              • 实例
                • 接口实现
                • 运用
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档