一、工厂方法模式
概念
定义一个用于创建对象的接口,让子类决定实例化哪一个类。
实例
摊煎饼的小贩需要先摊个煎饼,再卖出去,摊煎饼就可以类比为一个工厂方法,根据顾客的喜好摊出不同口味的煎饼。
接口
代码语言: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
写到最后,其实设计模式每一种基本实现都会根据使用场景的不同有很多变体,不同的设计模式在不同的场景下又回产生组合,所以使用设计模式一定不要教条,根据不同场景灵活变通,这是最难的,后面也会持续更新一些通用场景下的设计模式在重构过程中的应用,最终让代码变得易维护,降低开发维护成本才是目的。
持续更新......