不知道看几遍写几遍了,常温常新,时时刻刻提醒自己在写出屎山代码之前,某些子模块能不能复用某个设计模式。
以下代码demo都是自己随性写的,欢迎大家吐槽。
1. 创建型
1.1 工厂方法
我们熟知的工厂模式是那种根据标签的名字进行一堆if-else,帮你new出对应的产品,比如说一个水果厂,我要吃橘子他帮我new出一个橘子,吃苹果就new出一个苹果,但这有一个很明确的弊端,比如说我要吃草莓,那么就需要修改水果厂这个类,这不符合开闭原则,即对修改关闭,对扩展开发,所以工厂方法应运而生。
工厂方法放弃了一个工厂的大包大揽,而是基于工厂角色和产品角色进行设计,比如说橘子厂只生产橘子,如果引入反射机制,系统的扩展性就非常好,无需改动客户端代码和之前的服务端代码,并且没有了一大堆丑陋的if-else了。
package main
import "log"
type RiceCookerFactory interface {
createRiceCooker() RiceCooker
}
type XiaoMiRiceCookerFactory struct {
}
func (p *XiaoMiRiceCookerFactory) createRiceCooker() RiceCooker {
return &XiaoMiRiceCooker{}
}
type MediRiceCookerFactory struct {
}
func (p *MediRiceCookerFactory) createRiceCooker() RiceCooker {
return &MidiRiceCooker{}
}
type RiceCooker interface {
display()
}
type XiaoMiRiceCooker struct {
}
func (p *XiaoMiRiceCooker) display() {
log.Printf("米家电饭煲,生产完成")
}
type MidiRiceCooker struct {
}
func (p *MidiRiceCooker) display() {
log.Printf("美地电饭煲,生产完成")
}
func main() {
xiaomi := XiaoMiRiceCookerFactory{}
coocker := xiaomi.createRiceCooker()
coocker.display()
medi := MediRiceCookerFactory{}
coocker = medi.createRiceCooker()
coocker.display()
/*
output:
024/02/06 13:45:05 米家电饭煲,生产完成
2024/02/06 13:45:05 美地电饭煲,生产完成
*/
}
1.2 抽象工厂
适用于产品族的创建,也就说小米智能家居工厂可以生产说小米电饭锅,小米智能台灯等等,他并不会生产说美的的电饭煲,即抽象工厂强制生产出同风格的对象。
package main
import (
"log"
)
type SmartHouseFactory interface {
createRiceCooker() RiceCooker
createLamp() Lamp
}
type XiaoMiSmartHouseFactory struct {
}
func (p *XiaoMiSmartHouseFactory) createRiceCooker() RiceCooker {
return &XiaoMiRiceCooker{}
}
func (p *XiaoMiSmartHouseFactory) createLamp() Lamp {
return &XiaoMiLamp{}
}
type MediSmartHouseFactory struct {
}
func (p *MediSmartHouseFactory) createRiceCooker() RiceCooker {
return &MediRiceCooker{}
}
func (p *MediSmartHouseFactory) createLamp() Lamp {
return &MediLamp{}
}
type RiceCooker interface {
display()
}
type Lamp interface {
display()
}
type XiaoMiRiceCooker struct {
}
func (p *XiaoMiRiceCooker) display() {
log.Printf("新鲜的米家电饭煲生产完成!")
}
type XiaoMiLamp struct {
}
func (p *XiaoMiLamp) display() {
log.Printf("新鲜的米家台灯生产完成!")
}
type MediRiceCooker struct {
}
func (p *MediRiceCooker) display() {
log.Printf("新鲜的美地电饭煲生产完成!")
}
type MediLamp struct {
}
func (p *MediLamp) display() {
log.Printf("新鲜的美地台灯生产完成!")
}
func main() {
xiaomi := XiaoMiSmartHouseFactory{}
lamp := xiaomi.createLamp()
coocker := xiaomi.createRiceCooker()
lamp.display()
coocker.display()
medi := MediSmartHouseFactory{}
lamp = medi.createLamp()
coocker = medi.createRiceCooker()
lamp.display()
coocker.display()
/*
output:
2024/02/06 11:02:14 新鲜的米家台灯生产完成!
2024/02/06 11:02:14 新鲜的米家电饭煲生产完成!
2024/02/06 11:02:14 新鲜的美地台灯生产完成!
2024/02/06 11:02:14 新鲜的美地电饭煲生产完成!
*/
}
1.3 克隆
克隆模式其实就是深拷贝,写过c语言经常能遇到过char str[],这种字符串赋值的情况,如果直接用等于号其实是浅拷贝,他是指向同一个地址,而深拷贝是自己malloc一块内存后strcpy过去,这样修改源字符串不会影响新字符串。
package main
import "log"
type StringPrototype interface {
getStr() string
setStr(str string)
clone() StringPrototype
}
type MyString struct {
str string
}
func (p *MyString) getStr() string {
return p.str
}
func (p *MyString) setStr(str string) {
p.str = str
}
func (p *MyString) clone() StringPrototype {
return &MyString{str: p.str}
}
func main() {
myString := MyString{"你好"}
myString1 := myString.clone()
log.Printf("%p-%p", &myString, myString1)
log.Printf("%v-%v", myString, myString1)
/*
output:
024/02/06 10:20:31 0xc000028080-0xc000028090
2024/02/06 10:20:31 {你好}-&{你好}
*/
}
1.4 建造者模式
建筑者模式关注如何一步步创建一个复杂对象,不同的具体建造者定义了不同的创建过程,且具体建造者相互独立。
比如最近刚做好全屋定制,那么一个柜子的组装就是建造者模式,他要有柜门,要有背板,要有封边,要有五金和抽屉,有的厂商柜门是实木的,有的是颗粒板等等。
package main
import "log"
type Cupboard struct {
door string
body string
hardware string
drawer string
}
func (p *Cupboard) display() {
log.Printf("柜子已经组装完成,配置如下:\n柜门:%s\n柜体:%s\n五金:%s\n抽屉:%s\n",
p.door, p.body, p.hardware, p.drawer)
}
type CupboardBuilder interface {
BuildCupboardDoor()
BuildCupboardBody()
BuildCupboardHardware()
BuildCupboardDrawer()
CreateCupboard() Cupboard
}
type MouPaiCupboardBuilder struct {
cupboard Cupboard
}
func (p *MouPaiCupboardBuilder) BuildCupboardDoor() {
p.cupboard.door = "禾香板"
}
func (p *MouPaiCupboardBuilder) BuildCupboardBody() {
p.cupboard.body = "颗粒板"
}
func (p *MouPaiCupboardBuilder) BuildCupboardHardware() {
p.cupboard.hardware = "进口诺米"
}
func (p *MouPaiCupboardBuilder) BuildCupboardDrawer() {
p.cupboard.drawer = "托底抽"
}
func (p *MouPaiCupboardBuilder) CreateCupboard() Cupboard {
return p.cupboard
}
type MouFeiYaCupboardBuilder struct {
cupboard Cupboard
}
func (p *MouFeiYaCupboardBuilder) BuildCupboardDoor() {
p.cupboard.door = "多层实木"
}
func (p *MouFeiYaCupboardBuilder) BuildCupboardBody() {
p.cupboard.body = "橡胶木"
}
func (p *MouFeiYaCupboardBuilder) BuildCupboardHardware() {
p.cupboard.hardware = "国产五金"
}
func (p *MouFeiYaCupboardBuilder) BuildCupboardDrawer() {
p.cupboard.drawer = "三节轨"
}
func (p *MouFeiYaCupboardBuilder) CreateCupboard() Cupboard {
return p.cupboard
}
type CupboardContrller struct {
}
func (p *CupboardContrller) NewCupboard(cupboardBuilder CupboardBuilder) Cupboard {
cupboardBuilder.BuildCupboardBody()
cupboardBuilder.BuildCupboardDoor()
cupboardBuilder.BuildCupboardDrawer()
cupboardBuilder.BuildCupboardHardware()
return cupboardBuilder.CreateCupboard()
}
func main() {
furnitureStore := &CupboardContrller{}
cupboard1 := furnitureStore.NewCupboard(&MouFeiYaCupboardBuilder{})
cupboard2 := furnitureStore.NewCupboard(&MouPaiCupboardBuilder{})
cupboard1.display()
cupboard2.display()
/*
output:
2024/02/06 09:51:44 柜子已经组装完成,配置如下:
柜门:多层实木
柜体:橡胶木
五金:国产五金
抽屉:三节轨
2024/02/06 09:51:44 柜子已经组装完成,配置如下:
柜门:禾香板
柜体:颗粒板
五金:进口诺米
抽屉:托底抽
*/
}
2.结构型
2.1 适配器模式
将那些由于接口不兼容而不能交互的类可以一起工作。
举个非常常见的例子,我老婆经常出国际差,有些国家的插座的孔间距或者孔的个数,和我们国家的不一致,这就导致我们电脑充电线的插头插不进去,这时我们要买很多的转接头,而这个转接头其实就是适配器,在不改变我们笔记本的输入插头的前提下,在其他国家能正常使用。
package main
import "log"
type ChinaPlugin struct {
}
func (p *ChinaPlugin) plugIn() {
log.Println("笔记本点亮")
}
type Adapter interface {
plugIn()
}
type FranceAdapter struct {
chinaPlugin ChinaPlugin
}
func (p *FranceAdapter) plugIn() {
log.Println("这是一个法国转接头,转换成功")
p.chinaPlugin.plugIn()
}
func main() {
franceAdapter := FranceAdapter{ChinaPlugin{}}
franceAdapter.plugIn()
/*
output:
2024/02/05 17:19:45 这是一个法国转接头,转换成功
2024/02/05 17:19:45 笔记本点亮
*/
}
2.2 桥接模式
系统具备多个变化维度时,需要进行拆分进行独立变化,不能耦合在一起。
类似于用不同粗细的画笔,可以蘸不同颜色的涂料,画出不同大小的图像,这里面颜色和线条粗细就是两个变化的维度,后面新增颜色时不能影响之前的毛笔类。
package main
import "log"
type Color interface {
display() string
}
type Green struct {
}
func (p *Green) display() string {
return "绿色"
}
type Yellow struct {
}
func (p *Yellow) display() string {
return "黄色"
}
type Piant interface {
setColor(color Color)
doPaint()
}
type SmallPiant struct {
color Color
}
func (p *SmallPiant) setColor(color Color) {
p.color = color
return
}
func (p *SmallPiant) doPaint() {
log.Printf("小毛笔画出一只[%s]猪猪侠", p.color.display())
return
}
type BigPiant struct {
color Color
}
func (p *BigPiant) setColor(color Color) {
p.color = color
return
}
func (p *BigPiant) doPaint() {
log.Printf("大毛笔画出一只[%s]猪猪侠", p.color.display())
return
}
func main() {
firstPaint := BigPiant{}
firstPaint.setColor(&Green{})
firstPaint.doPaint()
secondPaint := SmallPiant{}
secondPaint.setColor(&Yellow{})
secondPaint.doPaint()
/*
output:
2024/02/05 17:06:02 大毛笔画出一只[绿色]猪猪侠
2024/02/05 17:06:02 小毛笔画出一只[黄色]猪猪侠
*/
}
2.3 组合模式
统一处理单个对象和整体的架构,类似于linux中一切即文件的思想,把网卡,cpu,文本,文件夹都看成文件进行处理,那么删除文件夹,相当于递归的把里面的文件都删除了。
package main
import "log"
type FileAbstract interface {
add(file FileAbstract)
remove()
}
type PdfFile struct {
name string
}
// 叶子节点不需要实现
func (p *PdfFile) add(file FileAbstract) {
return
}
func (p *PdfFile) remove() {
log.Printf("当前[%s.pdf]已被删除", p.name)
}
type TxtFile struct {
name string
}
// 叶子节点不需要实现
func (p *TxtFile) add(file FileAbstract) {
return
}
func (p *TxtFile) remove() {
log.Printf("当前[%s.txt]已被删除", p.name)
}
type Folder struct {
name string
fileList []FileAbstract
}
// 叶子节点不需要实现
func (p *Folder) add(file FileAbstract) {
p.fileList = append(p.fileList, file)
return
}
func (p *Folder) remove() {
log.Printf("当前文件夹[%s]已被删除", p.name)
for _, file := range p.fileList {
file.remove()
}
}
func main() {
pdf1 := PdfFile{"设计模式"}
txtFile1 := TxtFile{"好好学习"}
txtFile1.remove()
folder1 := Folder{"Working", nil}
folder1.add(&pdf1)
folder1.add(&txtFile1)
folder1.remove()
/*
output:
2024/02/05 16:40:57 当前[好好学习.txt]已被删除
2024/02/05 16:40:57 当前文件夹[Working]已被删除
2024/02/05 16:40:57 当前[设计模式.pdf]已被删除
2024/02/05 16:40:57 当前[好好学习.txt]已被删除
*/
}
2.4 装饰模式
对已有功能进行扩展,比如要在原有基础上新增功能A,这叫一次装饰,新增功能A的基础上还能继续新增功能B进行二次装饰。
现实中我们常见到这种模式,比如说点了一杯奶茶,然后你可再次基础上加一些珍珠,或者是茅子,或者是手提单换成镀金的,这都可以,相当于这杯奶茶这个主体不再变动,在此基础上可以进行二创,另外二创的东西还可以进行三创,比如说我这杯是加了茅子的奶茶,然后我可以继续加一些珍珠啥的。
package main
import (
"fmt"
"log"
)
type MilkTeaItem interface {
display() string
}
type MilkTea struct {
}
func (p *MilkTea) display() string {
return "500ml传说级别的龙井奶茶"
}
type BlackPearlDecorate struct {
milkTeaItem MilkTeaItem
}
func (p *BlackPearlDecorate) display() string {
return fmt.Sprintf("加了[黑色的珍珠]->[%s]", p.milkTeaItem.display())
}
type MaoTaiDecorate struct {
milkTeaItem MilkTeaItem
}
func (p *MaoTaiDecorate) display() string {
return fmt.Sprintf("加了[3ml尊贵的茅台]->[%s]", p.milkTeaItem.display())
}
func main() {
meOrder := &MilkTea{}
wifeOrder := &BlackPearlDecorate{meOrder}
papaOrder := MaoTaiDecorate{wifeOrder}
log.Println(meOrder.display())
log.Println(wifeOrder.display())
log.Println(papaOrder.display())
/*
output:
2024/02/05 16:19:08 500ml传说级别的龙井奶茶
2024/02/05 16:19:08 加了[黑色的珍珠]->[500ml传说级别的龙井奶茶]
2024/02/05 16:19:08 加了[3ml尊贵的茅台]->[加了[黑色的珍珠]->[500ml传说级别的龙井奶茶]]
*/
}
2.5 外观模式
当一个类需要和很多其他类打交道的时候,而这些类往往作为一个整体出现,类似于我去办理车牌照,我要先去拍照,还要去选号,还要检车试试刹车行不,这几个交互下来我感觉好累,所以我想能不能将照片和汽车给工作人员帮,让他帮我搞定直接把车牌照和行驶证给我,这就是外观模式,意在屏蔽与子系统复杂的交互。
package main
import (
"log"
)
type PhotoManger struct {
}
func (p *PhotoManger) receive(photo string) {
log.Printf("收到了[%s]二寸照片\n", photo)
}
type NumManger struct {
}
func (p *NumManger) pick() {
log.Printf("您选的号码为888888\n")
}
type CarManger struct {
}
func (p *CarManger) check(car string) {
log.Printf("您的爱车[%s]刹车正常\n", car)
}
type VehicleOfficeFacade struct {
photoManger PhotoManger
numManger NumManger
carManger CarManger
}
func (p *VehicleOfficeFacade) getDriverLicense(photo, car string) {
p.photoManger.receive(photo)
p.numManger.pick()
p.carManger.check(car)
log.Printf("您久等了,这是您的行驶证和车牌\n")
}
func main() {
carSaler := VehicleOfficeFacade{}
carSaler.getDriverLicense("靓仔", "Red Mi Car")
/*
output:
2024/02/05 15:39:53 收到了[靓仔]二寸照片
2024/02/05 15:39:53 您选的号码为888888
2024/02/05 15:39:53 您的爱车[Red Mi Car]刹车正常
2024/02/05 15:39:53 您久等了,这是您的行驶证和车牌
*/
}
2.6 享元模式
通过共享技术将相同或相似对象的重用,类似于资源池
游戏常用的一种设计模式,比如说陈友谅和朱元璋百万军队鄱阳湖决战,那我们不能每个士兵都new一遍,这些士兵身高体重,肌肤纹理完全可以复用,我们只需要根据冲锋的位置和士兵的级别这些独有特征进行渲染出画面即可。
package main
import (
"log"
"sync"
)
type State struct {
rank string
coordinatX string
coordinatY string
}
func (s *State) setRank(rank string) {
s.rank = rank
}
func (s *State) getRank() string {
return s.rank
}
func (s *State) setCoordinatX(coordinatX string) {
s.coordinatX = coordinatX
}
func (s *State) getCoordinatX() string {
return s.coordinatX
}
func (s *State) setCoordinatY(coordinatY string) {
s.coordinatY = coordinatY
}
func (s *State) getCoordinatY() string {
return s.coordinatY
}
type Soldier interface {
render(s State) error
}
type ChenYouLiangSoldier struct {
}
func (u *ChenYouLiangSoldier) render(s State) error {
log.Printf("[陈友谅部队%s] -> (%s,%s) 发起冲锋", s.getRank(), s.getCoordinatX(), s.getCoordinatY())
return nil
}
type ZhuYuanZhangSoldier struct {
}
func (u *ZhuYuanZhangSoldier) render(s State) error {
log.Printf("[朱元璋部队%s] -> (%s,%s) 发起冲锋", s.getRank(), s.getCoordinatX(), s.getCoordinatY())
return nil
}
var (
soldierFactory *SoldierFactory
once = &sync.Once{}
)
type SoldierFactory struct {
soldierMap map[string]Soldier
}
func (u *SoldierFactory) GetSoldier(troopName string) Soldier {
return u.soldierMap[troopName]
}
func GetSoldierFactoryInstance() *SoldierFactory {
if soldierFactory != nil {
return soldierFactory
}
once.Do(func() {
soldierFactory = &SoldierFactory{}
soldierFactory.soldierMap = make(map[string]Soldier, 0)
soldierFactory.soldierMap["ZhuYuanZhang"] = &ZhuYuanZhangSoldier{}
soldierFactory.soldierMap["ChenYouLiang"] = &ChenYouLiangSoldier{}
})
return soldierFactory
}
func main() {
mingSoldierFactory := GetSoldierFactoryInstance()
XuDa := mingSoldierFactory.GetSoldier("ZhuYuanZhang")
ChangYuChun := mingSoldierFactory.GetSoldier("ZhuYuanZhang")
ZhangDingBian := mingSoldierFactory.GetSoldier("ChenYouLiang")
XuDa.render(State{
rank: "猛将",
coordinatX: "平江",
coordinatY: "虎丘",
})
ChangYuChun.render(State{
rank: "谋士",
coordinatX: "平江",
coordinatY: "北门",
})
ZhangDingBian.render(State{
rank: "第一猛士",
coordinatX: "鄱阳湖",
coordinatY: "朱元璋所在战舰",
})
log.Printf("%p - %p", XuDa, ChangYuChun)
/*
output:
2024/02/05 15:03:46 [朱元璋部队猛将] -> (平江,虎丘) 发起冲锋
2024/02/05 15:03:46 [朱元璋部队谋士] -> (平江,北门) 发起冲锋
2024/02/05 15:03:46 [陈友谅部队第一猛士] -> (鄱阳湖,朱元璋所在战舰) 发起冲锋
2024/02/05 15:03:46 0x10d8500 - 0x10d8500
*/
}
2.7 代理模式
和外观模式有些类似,都是一个代理的过程。但外观模式是为了隐藏与子系统交互的复杂性,而代理模式是控制对子系统的访问。
非常经典的就是ngnix,他可以帮你的请求转发到对于的web服务上,除了这个最基本的功能,还会提供一些其他功能,比如说:负载均衡,黑名单,访问统计等等
package main
import (
"fmt"
"log"
)
type WebServer interface {
doSearch(username, password, url string) error
}
type JdWeb struct {
}
func (u *JdWeb) doSearch(username, password, url string) error {
fmt.Println("欢迎访问东东首页!")
return nil
}
type PddWeb struct {
}
func (u *PddWeb) doSearch(username, password, url string) error {
fmt.Println("欢迎访问多多首页!")
return nil
}
type MyNgnix struct {
realServerMap map[string]WebServer
}
func (u *MyNgnix) init() error {
u.realServerMap = make(map[string]WebServer, 0)
return nil
}
func (u *MyNgnix) register(name string, realServer WebServer) error {
u.realServerMap[name] = realServer
return nil
}
func (u *MyNgnix) doSearch(username, password, url string) error {
err := u.valid(username, password)
if err != nil {
return err
}
_, ok := u.realServerMap[url]
if !ok {
return fmt.Errorf("404 Not Found")
}
u.log(username, password, url)
return nil
}
func (u *MyNgnix) log(username, password, url string) error {
log.Printf("[%s] -> [%s]", username, url)
return nil
}
func (u *MyNgnix) valid(username, password string) error {
if password != "123456" {
return fmt.Errorf("密码错误")
}
return nil
}
func main() {
jdWeb := JdWeb{}
pddWeb := PddWeb{}
myNgnix := &MyNgnix{}
myNgnix.init()
myNgnix.register("http://jjd.com", &jdWeb)
myNgnix.register("http://pdddd.com", &pddWeb)
err := myNgnix.doSearch("hala", ":12", "http://jjd.com")
if err != nil {
log.Println(err.Error())
}
err = myNgnix.doSearch("hala", "123456", "http://jjd.com")
if err != nil {
log.Println(err.Error())
}
err = myNgnix.doSearch("hala", "123456", "http://pdddd.com")
if err != nil {
log.Println(err.Error())
}
/*
output:
2024/02/05 10:59:03 密码错误
2024/02/05 10:59:03 [hala] -> [http://jjd.com]
2024/02/05 10:59:03 [hala] -> [http://pdddd.com]
*/
}
3.行为型
3.1 职责链
用于处理请求链式传递的模式,比如说很常见的审批流程,流程A需要人员甲乙丙去审批,流程B需要丙甲这个顺序去审批。
本质上将多个审批逻辑的if else的处理,转成了链式的next处理,就是当前处理者只关心当前请求自己能否处理或做一部分处理,如果不能,就会传递给下一个处理者。
package main
import "fmt"
type Approver interface {
SetApproverName(name string)
GetApproverName() string
SetNextApprover(approver Approver)
ProcessRequest(item string)
}
type Pressident struct {
name string
nextApprover Approver
}
func (p *Pressident) SetApproverName(name string) {
p.name = name
}
func (p *Pressident) GetApproverName() string {
return p.name
}
func (p *Pressident) SetNextApprover(approver Approver) {
p.nextApprover = approver
}
func (p *Pressident) ProcessRequest(item string) {
if item == "采购炉石传说" {
fmt.Printf("%s:【%s】审批通过\n", p.GetApproverName(), item)
} else {
p.nextApprover.ProcessRequest(item)
}
}
type Monitor struct {
name string
nextApprover Approver
}
func (m *Monitor) SetApproverName(name string) {
m.name = name
}
func (m *Monitor) GetApproverName() string {
return m.name
}
func (m *Monitor) SetNextApprover(approver Approver) {
m.nextApprover = approver
}
func (m *Monitor) ProcessRequest(item string) {
if item == "采购PS5" {
fmt.Printf("%s:【%s】审批通过\n", m.GetApproverName(), item)
} else {
fmt.Printf("%s:【%s】审批不通过\n", m.GetApproverName(), item)
}
}
func main() {
pressident := &Pressident{}
pressident.SetApproverName("whisperGu")
monitor := &Monitor{}
monitor.SetApproverName("liyaYuan")
pressident.SetNextApprover(monitor)
/* 模拟一种采购流程 */
pressident.ProcessRequest("采购PS5")
pressident.ProcessRequest("采购炉石传说")
pressident.ProcessRequest("采购破音747")
/*
output:
liyaYuan:【采购PS5】审批通过
whisperGu:【采购炉石传说】审批通过
liyaYuan:【采购破音747】审批不通过
*/
}
4. Reference
https://book.douban.com/subject/35163478/
https://www.cnblogs.com/Renyi-Fan/p/11031682.html#
===========================分割线==============================
持续更新ing~~