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

2022-01-22 11:21:05 浏览数 (2)

一、工厂方法模式

概念

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

实例

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

接口

代码语言: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 %vn", pancakeVendor.SellPancake())

	pancakeVendor = NewPancakeVendor(NewMilletPancakeCook())
	fmt.Printf("Millet pancake value is %vn", 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: %vn", HaveMeal(NewBreakfastCook()))
	fmt.Printf("lunch: %vn", HaveMeal(NewLunchCook()))
	fmt.Printf("dinner: %vn", 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("摊一个普通煎饼 %#vn", pancakeCook.MakePancake())

	pancakeCook.SetPancakeBuilder(NewHealthyPancakeBuilder())
	fmt.Printf("摊一个健康的加量煎饼 %#vn", 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

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

持续更新......

0 人点赞