1. 創建型模式
這些設計模式提供了一種在創建對象的同時隱藏創建邏輯的方式,而不是使用new運算符直接實例化這些對象
這使得程序在判斷針對某個給定實例需要創建哪些對象時更加靈活
1.1 工廠模式
在工廠模式中,我們在創建對象時不會對客戶端暴露創建邏輯,而是通過使用一個共同的接口來指向新創建的對象
代碼實現:
type API interface {
Say(name string) string
}
// 核心邏輯
func NewAPI(t int) API {
if t == 1 {
return &hiAPI{}
} else if t == 2 {
return &helloAPI{}
}
return nil
}
type hiAPI struct{}
func (h *hiAPI) Say(name string) string {
return fmt.Sprintf("hi %s", name)
}
type helloAPI struct{}
func (h *helloAPI) Say(name string) string {
return fmt.Sprintf("hello %s", name)
}
我們通過一個通用的接口來頂一個兩個對象的創建,通過傳入參數的方式指定我們創建的是哪個對象,總結工廠模式的優缺點
優點:
- 一個調用者想創建一個對象,只需要知道其名稱就可以
- 擴展性高,想要增加一個產品,只需要為
NewAPI()
增加一條邏輯即可 - 屏蔽產品的具體實現,調用者只關心產品的接口
缺點:
- 每次增加一個產品,都需要修改
NewAPI()
的邏輯
1.2 抽象工廠模式
抽象工廠模式是圍繞一個超級工廠創建其他工廠,超級工廠可以理解為其他工廠的工廠
在抽象工廠模式中,借口負責創建一個相關對象的工廠,不需要顯示的指定他們的類,每個生成的工廠都可以提供對象
代碼實現:
// OrderMainDAO 訂單主記錄
type OrderMainDAO interface {
SaveOrderMain()
}
// OrderDetailDAO 訂單詳情記錄
type OrderDetailDAO interface {
SaveOrderDetail()
}
// DAOFactory 抽象工廠接口
type DAOFactory interface {
CreateOrderMainDAO() OrderMainDAO
CreateOrderDetailDAO() OrderDetailDAO
}
// RDBMainDAO 為關系型數據庫的OrderMainDAO實現
type RDBMainDAO struct{}
// SaveOrderMain ...
func (r *RDBMainDAO) SaveOrderMain() {
fmt.Println("rdb main save")
}
// RDBDetailDAO 為關系型數據庫的OrderDetailDAO實現
type RDBDetailDAO struct{}
// SaveOrderDetail ...
func (r *RDBDetailDAO) SaveOrderDetail() {
fmt.Println("rdb detail save")
}
// RDBDAOFactory 是RDB 抽象工廠實現
type RDBDAOFactory struct{}
func (r *RDBDAOFactory) CreateOrderMainDAO() OrderMainDAO {
return &RDBMainDAO{}
}
func (r *RDBDAOFactory) CreateOrderDetailDAO() OrderDetailDAO {
return &RDBDetailDAO{}
}
// XMLMainDAO XML存儲
type XMLMainDAO struct{}
// SaveOrderMain ...
func (*XMLMainDAO) SaveOrderMain() {
fmt.Println("xml main save")
}
// XMLDetailDAO XML存儲
type XMLDetailDAO struct{}
// SaveOrderDetail ...
func (*XMLDetailDAO) SaveOrderDetail() {
fmt.Println("xml detail save")
}
// XMLDAOFactory 是RDB抽象工廠實現
type XMLDAOFactory struct{}
func (*XMLDAOFactory) CreateOrderMainDAO() OrderMainDAO {
return &XMLMainDAO{}
}
func (*XMLDAOFactory) CreateOrderDetailDAO() OrderDetailDAO {
return &XMLDetailDAO{}
}
在代碼中使用RDB和XML存儲訂單信息,抽象工廠分別就能生成相關的主訂單信息和訂單詳情信息
如果業務邏輯中需要替換使用的時候只需要修改工廠函數相關的類就可以替換使用不同的存儲方式了
下面簡單看一下使用:
func getMainAndDetail(factoy DAOFactory) {
factoy.CreateOrderMainDAO().SaveOrderMain()
factoy.CreateOrderDetailDAO().SaveOrderDetail()
}
func ExampleRDBFactory() {
var factory DAOFactory
factory = &RDBDAOFactory{}
getMainAndDetail(factory)
}
func ExampleXMLFactory() {
var factory DAOFactory
factory = &XMLDAOFactory{}
getMainAndDetail(factory)
}
因為RDBDAOFactory
和XMLDAOFactory
均實現了抽象工廠接口DAOFactory
,所以我們可以在工廠創建階段傳入任意一個想實現的存儲方法
它們會對應不同的自己實現的Save方法
優點:
- 當一個產品家族中的多個對象被設計成一起工作時,它能保證客戶端始終只使用同一個產品族中的對象
缺點:
- 產品族擴展非常困難,要增加一個系列的某一產品,既要在抽象工廠里面加代碼,又要在具體實現里面加代碼
1.3 單例模式
單例模式設計一個單一的類,該類負責創建自己的對象,同時確保只有一個對象被創建
這個類提供了一種訪問其唯一對象的方式,可以直接訪問,不需要實例化該類的對象
單例模式的線程安全懶漢模式實現:
// Singleton 單例接口、可導出
// 通過該接口可以避免 GetInstance 返回一個包私有類型的指針
type Singleton interface {
foo()
}
// singleton 私有單例類
type singleton struct {}
func (s singleton) foo(){}
var (
instance *singleton
once sync.Once
)
// GetInstance 獲取單例對象
func GetInstance() Singleton{
once.Do(func() {
instance=&singleton{}
})
return instance
}
優點:
- 進程中只存在一個實例,內存消耗小,避免頻繁銷毀和創建
- 避免對資源的多重占用,比如文件
缺點:
- 沒有接口,不能繼承,與單一職責原則沖突
1.4 建造者模式
存在一個類Builder會一步一步構造最終的對象,該Builder類是獨立於其他對象的
首先我們需要定義一個生成器接口,這里面是產品的共同方法
然后需要一個向外部暴露的Director結構體,在結構體的Construct()
方法中去調用所有的建造方法
我們可以選擇將具體的建造方法隱藏,而只對外暴露一個Construct()
方法提供整個建造流程的調用
// Builder 生成器接口
type Builder interface {
part1()
part2()
part3()
}
type Director struct {
builder Builder
}
func (d *Director) Construct() {
d.builder.part1()
d.builder.part2()
d.builder.part3()
}
然后我們對外提供一個構建Director
對象的方法
func NewDirector(builder Builder) *Director {
return &Director{
builder: builder,
}
}
最后就是產品的具體實現,我們在此實現兩個產品,這兩個產品都需要實現Builder
接口的三個方法
// 建造者一
type Builder1 struct {
result string
}
func (b *Builder1) part1() {
b.result += "1"
}
func (b *Builder1) part2() {
b.result += "2"
}
func (b *Builder1) part3() {
b.result += "3"
}
func (b *Builder1) GetResult() string {
return b.result
}
// 建造者二
type Builder2 struct {
result int
}
func (b *Builder2) part1() {
b.result += 1
}
func (b *Builder2) part2() {
b.result += 2
}
func (b *Builder2) part3() {
b.result += 3
}
func (b *Builder2) GetResult() int {
return b.result
}
在使用中,我們首先需要構建一個具體的建造者,比我我們構建一個建造者一對象
然后使用NewDirector()
方法生成一個抽象的Director
對象,然后調用Construct()
方法來建造,最后調用建造者二本身的方法獲取結果即可
func TestBuilder1(t *testing.T) {
builder := &Builder1{}
director := NewDirector(builder)
director.Construct()
res := builder.GetResult()
if res != "123" {
t.Fatalf("Builder1 fail expect 123 acture %s", res)
}
}
優點:
- 建造者獨立、易擴展
- 便於控制細節風險
缺點:
- 產品必須有共同點、范圍有限制
- 內部變化復雜的話會有很多的建造類
1.5 原型模式
原型模式用於創建重復的對象,同時又能保證性能
這種模式實現一個原型接口,該接口用於創建當前對象的克隆,當直接創建對象的代價比較大時,則采用這種模式
例如,一個對象需要在一個高代價的數據庫操作之后被創建,我們可以緩存該對象,在下一個請求時返回它的克隆,在需要時更新數據庫,以此來減少數據庫調用
原型模式配合原型管理器使用,使得客戶端在不知道具體類的情況下,通過接口管理器得到新的實例,並且包含部分預設配置
原型管理器的實現我們使用一個map,如果想要線程安全考慮使用sync.map
原型管理器提供了兩個主要方法,分別是取得原型對象和存入/修改原型對象,值得注意的是原型對象必須自己實現Cloneable
接口
// Cloneable 原型對象需要實現的接口,具體是一個Clone()方法,返回自身
type Cloneable interface {
Clone() Cloneable
}
// PrototypeManager 存儲原型對象
type PrototypeManager struct {
prototypes map[string]Cloneable
}
func NewPrototypeManager() *PrototypeManager {
return &PrototypeManager{
prototypes: make(map[string]Cloneable),
}
}
func (p *PrototypeManager) Get(name string) Cloneable {
return p.prototypes[name].Clone()
}
func (p *PrototypeManager) Set(name string, prototype Cloneable) {
p.prototypes[name] = prototype
}
我們看一下簡單使用,我們先寫一個原型類,他要實現Cloneable
接口
type Type1 struct {
name string
}
func (t *Type1) Clone() Cloneable {
tc := *t
return &tc
}
然后使用原型管理器管理這個對象
func main() {
protoMgr := NewPrototypeManager()
type1 := &Type1{name: "lucy"}
protoMgr.Set("type1", type1)
type2 := protoMgr.Get("type1")
fmt.Println(type2.(*Type1).name)
}
優點:
- 性能提高
- 逃避構造函數的約束
缺點:
- 配備克隆方法需要對類的功能進行通盤考慮,特別是當一個類引用不支持串行化的間接對象,或者引用含有循環結構的時候
- 必須實現Cloneable接口
2. 結構性模式
這些設計模式關注類和對象的組合
繼承的概念被用來組合接口和定義組合對象獲得新功能的方式
2.1 適配器模式
適配器模式是作為兩個不兼容的接口之間的橋梁
這種模式涉及到一個單一的類,該類負責加入獨立的或者不兼容的接口功能,比如讀卡器是作為內存卡和電腦之間的適配器
首先我們存在一個被適配的類adaptee
,要使用一個適配器將其適配為目標類:
// Adaptee 被適配的目標接口
type Adaptee interface {
SpecificRequest() string
}
// adapteeImpl 被適配的目標類
type adapteeImpl struct{}
// SpecificRequest 被適配的目標類方法
func (a *adapteeImpl) SpecificRequest() string {
return "adaptee method"
}
// NewAdaptee 構建被適配目標類
func NewAdaptee() Adaptee {
return &adapteeImpl{}
}
然后對於適配目標類target
,我們定義一個適配器來實現適配
// Target 適配的目標接口
type Target interface {
Request() string
}
// adapter 將adaptee -> target的適配器
type adapter struct {
Adaptee
}
func (a *adapter) Request() string {
return a.SpecificRequest()
}
func NewAdapter(adaptee Adaptee) Target {
return &adapter{adaptee}
}
要記住我們的目標是什么?目標是在目標類中使用適配器來調用被適配類的方法,所以我們在使用時首先要實例化被適配類和適配類
adaptee := NewAdaptee()
target := NewAdapter(adaptee)
然后調用目標類的方法,就可以通過適配器來調用被適配類的方法了
res := target.Request() // adaptee method
if res != expect {
t.Fatalf("expect: %s, actual: %s", expect, res)
}
優點:
- 可以讓任何兩個沒有關聯的類一起運行
- 提高了類的復用
- 增加了類的透明度
- 靈活性好
缺點:
- 過多的使用適配器會讓整個系統很亂
- 不要過多適配,不要過多繼承
2.2 橋接模式
橋接模式適用於把抽象化與實現化解耦,使得二者可以獨立變化
這種模式涉及到一個作為橋接的接口,使得實體類的功能獨立於接口實現類,這兩種類型的類可以被結構化改變而不相互影響
我們以發送驗證碼為例,需求是存在兩種發送驗證碼的方式:短信和郵件,同時需要發送兩類驗證碼:普通驗證碼和緊急驗證碼
在這種情況下,需要一個發送驗證碼的抽象接口和實現接口,實現接口負責實現兩種發送方式,抽象接口負責實現兩類驗證碼
首先來看實現接口:
// MessageImplementer 發送驗證碼的實現接口
type MessageImplementer interface {
Send(text, to string)
}
// MessageSMS 發送手機驗證碼的實現類
type MessageSMS struct{}
func (m *MessageSMS) Send(text, to string) {
fmt.Printf("send %s to %s via SMS\n", text, to)
}
func ViaSMS() MessageImplementer {
return &MessageSMS{}
}
// MessageEmail 發送電子郵件驗證碼的實現類
type MessageEmail struct{}
func (m *MessageEmail) Send(text, to string) {
fmt.Printf("send %s to %s via Email\n", text, to)
}
func ViaEmail() MessageImplementer {
return &MessageEmail{}
}
然后是抽象接口
// AbstractMessage 發送驗證碼的抽象接口
type AbstractMessage interface {
SendMessage(text, to string)
}
// CommonMessage 發送普通驗證碼的實現類,實現了抽象接口AbstractMessage
type CommonMessage struct {
method MessageImplementer
}
func (m *CommonMessage) SendMessage(text, to string) {
m.method.Send(text, to)
}
func NewCommonMessage(method MessageImplementer) *CommonMessage {
return &CommonMessage{
method: method,
}
}
// UrgencyMessage 發送緊急驗證碼的實現類,實現了抽象接口AbstractMessage
type UrgencyMessage struct {
method MessageImplementer
}
func (u *UrgencyMessage) SendMessage(text, to string) {
u.method.Send(fmt.Sprintf("[Urgency] %s", text), to)
}
func NewUrgencyMessage(method MessageImplementer) *UrgencyMessage {
return &UrgencyMessage{
method: method,
}
}
橋接模式的結構可以簡單的看為實現接口和抽象接口分離,如果我們需要擴展發送驗證碼的方式比如APP內推送,可以只在實現接口那一部分增加一個實現類就可以
下面來看一下如何使用橋接模式
func ExampleCommonSMS() {
m := NewCommonMessage(ViaSMS())
m.SendMessage("have a drink?", "bob")
// Output:
// send have a drink? to bob via SMS
}
func ExampleCommonEmail() {
m := NewCommonMessage(ViaEmail())
m.SendMessage("have a drink?", "bob")
// Output:
// send have a drink? to bob via Email
}
優點:
- 抽象和實現的分離
- 優秀的擴展能力
- 實現細節對客戶透明
缺點:
- 增加系統理解和設計的難度,要求對抽象層和實現層分別編程
2.3 組合模式
組合模式又叫整體部分模式,用於把一組相似的對象當做一個單一的對象,組合模式根據樹形結構來組合對象,用來表示部分以及整體層次
這種模式創建了一個包含對象組的類,該類提供了修改相同對象組的方式
首先定義一個接口,包含了我們需要的所有方法
const (
LEAF_NODE = iota
COMPOSITE_NODE
)
type Component interface {
Parent() Component
SetParent(component Component)
Name() string
SetName(s string)
AddChild(component Component)
Print(s string)
}
繼而用一個實現類來實現這個接口
type component struct {
parent Component
name string
}
func (c *component) Parent() Component {
return c.parent
}
func (c *component) Name() string {
return c.name
}
func (c *component) SetParent(component Component) {
c.parent = component
}
func (c *component) SetName(name string) {
c.name = name
}
func (c *component) AddChild(component Component) {}
func (c *component) Print(s string) {}
現在讓我們來組合對象
type Leaf struct {
component
}
func (l *Leaf) Print(s string) {
fmt.Printf("%s-%s\n", s, l.Name())
}
func NewLeaf() *Leaf {
return &Leaf{}
}
type Composite struct {
component
childs []Component
}
func (c *Composite) AddChild(component Component) {
component.SetParent(c)
c.childs = append(c.childs, component)
}
func (c *Composite) Print(s string) {
fmt.Printf("%s+%s\n", s, c.Name())
s += " "
for _, comp := range c.childs {
comp.Print(s)
}
}
func NewComposite() *Composite {
return &Composite{
childs: make([]Component, 0),
}
}
定義一個統一的實例化方法
func NewComponent(kind int, name string) Component {
var c Component
switch kind {
case LEAF_NODE:
c = NewLeaf()
case COMPOSITE_NODE:
c = NewComposite()
}
c.SetName(name)
return c
}
在使用時,需要將這些方法組成一個樹形結構:
func ExampleComposite() {
root := NewComponent(COMPOSITE_NODE, "root")
c1 := NewComponent(COMPOSITE_NODE, "c1")
c2 := NewComponent(COMPOSITE_NODE, "c2")
c3 := NewComponent(COMPOSITE_NODE, "c3")
l1 := NewComponent(LEAF_NODE, "l1")
l2 := NewComponent(LEAF_NODE, "l2")
l3 := NewComponent(LEAF_NODE, "l3")
root.AddChild(c1)
root.AddChild(c2)
c1.AddChild(c3)
c1.AddChild(l1)
c2.AddChild(l2)
c2.AddChild(l3)
root.Print("")
// Output:
// +root
// +c1
// +c3
// -l1
// +c2
// -l2
// -l3
}
優點:
- 高層模塊調用簡單
- 節點自由增加
缺點:
- 在使用組合模式時,其葉子和樹枝的聲明都是實現類,而不是接口,違反了依賴倒置原則
2.4 代理模式
在代理模式中,一個類代表另一個類的功能,我們創建現有對象的對象,以便向外界提供功能接口
代理模式用於延遲處理操作或者在進行實際操作前后進行其它處理
我們首先定義一組對象接口及其實現類
// Subject 對象接口
type Subject interface {
Do() string
}
// RealSubject 接口的實現類
type RealSubject struct{}
func (r RealSubject) Do() string {
return "real"
}
然后定義一個代理類,在代理類中對對象接口的方法在調用前和調用后進行一系列處理
// Proxy 代理類
type Proxy struct {
real RealSubject
}
func (p Proxy) Do() string {
var res string
// 在調用真是對象之前的工作,檢查緩存,判斷權限,實例化真實對象等
res += "pre:"
// 調用真實對象
res += p.real.Do()
// 調用之后的操作,如緩存結果,對結果進行進一步處理等
res += ":after"
return res
}
如何使用代理模式?在外界看來,Proxy
類完全代理了RealSubject
類,而二者都實現了Subject
接口,所以我們只需要實例化一個代理類Proxy
在調用代理類實現的方法時,就會調用對象類所實現的方法,並且在代理類中做了進一步的封裝
var sub Subject
sub = &Proxy{}
res := sub.Do()
if res != "pre:real:after" {
fmt.Println("error")
}
優點:
- 職責清晰
- 高擴展性
- 智能化
缺點:
- 增加了客戶端和服務端之間的中間層,處理請求可能會變慢
- 實現代理模式需要額外的工作,有些代理模式的實現非常負責
2.5 裝飾模式
裝飾器模式允許向一個現有的對象添加新的功能,同時又不改變其結構
這種模式提供了一個裝飾類,用來包裝原有的類,並在保持類方法簽名完整的前提下,提供了額外的功能
Go語言借助於匿名組合和非入侵式接口可以很方便的實現裝飾模式,使用匿名組合時,在裝飾器中不必顯示定義轉調原對象方法
在裝飾模式中,首先定義被裝飾的接口及其實現類:
// Component 目標接口
type Component interface {
Calc() int
}
// ConcreteComponent 被裝飾的類
type ConcreteComponent struct{}
func (c *ConcreteComponent) Calc() int {
return 0
}
然后我們定義一個乘法裝飾器,其匿名組合了目標接口Component
,並且自身又實現了這個接口
// MulDecorator 乘法裝飾類
type MulDecorator struct {
Component
num int
}
func (m *MulDecorator) Calc() int {
return m.Component.Calc() * m.num
}
func WarpMulDecorator(c Component, num int) Component {
return &MulDecorator{
Component: c,
num: num,
}
}
同樣地,我們也可以實現一個加法裝飾類
type AddDecorator struct {
Component
num int
}
func (a *AddDecorator) Calc() int {
return a.Component.Calc() + a.num
}
func WrapAddDecorator(c Component, num int) Component {
return &AddDecorator{
Component: c,
num: num,
}
}
關於裝飾器的使用,首先實例化一個目標類,然后調用裝飾器即可
var c Component = &ConcreteComponent{}
c = WarpAddDecorator(c, 10)
c = WarpMulDecorator(c, 8)
res := c.Calc()
fmt.Printf("res %d\n", res)
// Output:
// res 80
優點:
- 裝飾類和被裝飾類可以獨立發展,完全解藕
- 裝飾模式是繼承的一個替代模式,裝飾模式可以動態擴展一個實現類的功能
缺點:
- 多層裝飾會使得代碼結構變的復雜
2.6 享元模式
享元模式主要用於減少創建對象的數量,以減少內存占用和提升性能
享元模式嘗試重用現有的同類對象,如果未找到匹配的對象,則重新創建對象
首先我們創建一個享元模式的工廠和單例對象,並且向外界暴露兩個接口:獲取單例對象和獲取map中的對象
type ImageFlyweightFactory struct {
maps map[string]*ImageFlyweight
}
var imageFactory *ImageFlyweightFactory
func GetImageFlyweightFactory() *ImageFlyweightFactory {
if imageFactory == nil {
imageFactory = &ImageFlyweightFactory{
maps: make(map[string]*ImageFlyweight),
}
}
return imageFactory
}
func (i *ImageFlyweightFactory) Get(filename string) *ImageFlyweight {
image := i.maps[filename]
if image == nil {
image := NewImageFlyweight(filename)
i.maps[filename] = image
}
return image
}
對於map中的對象類,我們可以簡單模擬一個讀寫文件的操作
type ImageFlyweight struct {
data string
}
func (i *ImageFlyweight) Data() string {
return i.data
}
func NewImageFlyweight(filename string) *ImageFlyweight {
data := fmt.Sprintf("image data %s", filename)
return &ImageFlyweight{
data: data,
}
}
這樣我們就不必頻繁去讀取某個文件的數據,而是使用一個map將這些與文件數據關聯的對象保存起來,如果調用一個已經存在的對象則直接從map中獲得即可
優點:
- 大大減少對象的創建,降低內存分配,提升效率
缺點:
- 提升了系統的復雜度,需要分離出外部狀態和內部狀態,而且外部狀態具有固有化的性質,不應該隨着內部狀態的變化而變化,否則會造成系統的混亂
2.7 外觀模式
外觀模式隱藏系統的復雜性,並向客戶端提供了一個可以訪問系統的接口
這種模式涉及到一個單一的類,該類提供了客戶端請求的簡化方法和對現有系統類方法的委托調用
facade模塊同時暴露了a和b兩個Module的NewXXX和interface,其它代碼如果需要使用細節功能時可以直接調用
現在我們有兩個常規的接口及其實現類AModuleAPI
和BModuleAPI
//AModuleAPI ...
type AModuleAPI interface {
TestA() string
}
//NewAModuleAPI return new AModuleAPI
func NewAModuleAPI() AModuleAPI {
return &aModuleImpl{}
}
type aModuleImpl struct{}
func (*aModuleImpl) TestA() string {
return "A module running"
}
//BModuleAPI ...
type BModuleAPI interface {
TestB() string
}
//NewBModuleAPI return new BModuleAPI
func NewBModuleAPI() BModuleAPI {
return &bModuleImpl{}
}
type bModuleImpl struct{}
func (*bModuleImpl) TestB() string {
return "B module running"
}
對於外觀模式而言,我們需要提供一個統一接口來訪問這兩個常規接口
值得注意的是,雖然我們在其他package中實例化api對象時可以只調用NewAPI()
方法,但是我們仍然將NewAModuleAPI()
和NewBModuleAPI()
接口暴露出來,目的是用戶可以做一些內部實現的查看
type API interface {
Test() string
}
type apiImpl struct {
a AModuleAPI
b BModuleAPI
}
func (a *apiImpl) Test() string {
aRet := a.a.TestA()
bRet := a.b.TestB()
return fmt.Sprintf("%s\n%s", aRet, bRet)
}
func NewAPI() API {
return &apiImpl{
a: NewAModuleAPI(),
b: NewBModuleAPI(),
}
}
我們來看一下如何使用外觀模式,因為API
接口已經提供給我們了調用兩個對象類的統一接口,我么只需要實例化API就可以
api := NewAPI()
ret := api.Test()
優點:
- 減少系統的相互依賴
- 提高靈活性
- 提升安全性
缺點:
- 不符合開閉原則,如果發生改動會很麻煩,繼承重寫都不合適
3. 行為型模式
行為型模式特別關注對象之間的通信
3.1 中介者模式
中介者模式用來降低多個對象之間的通信復雜性
這種模式提供了一個中介類,該類通常處理不同類之間的通信,並支持松耦合,使得代碼易於維護
現在我們模擬一個CPU和移動硬盤或者CD通信的場景,首先定義一個驅動類,用於讀取CD的數據
// CDDriver CD驅動類,讀取CD數據
type CDDriver struct {
Data string
}
func (c *CDDriver) ReadData() {
c.Data = "music,image"
fmt.Printf("CDDriver: reading data %s\n", c.Data)
GetMediatorInstance().changed(c)
}
對於讀取到的數據,CPU做處理,將數據分為音頻數據和視頻數據,同時定義聲卡類和顯卡類用於音頻和視頻的顯示
// CPU 處理讀入的數據
type CPU struct {
Video string
Sound string
}
func (c *CPU) Process(data string) {
sp := strings.Split(data, ",")
c.Sound = sp[0]
c.Video = sp[1]
fmt.Printf("CPU: split data with Sound %s, Video %s\n", c.Sound, c.Video)
GetMediatorInstance().changed(c)
}
// VideoCard 顯卡類,用於播放視頻數據
type VideoCard struct {
Data string
}
func (v *VideoCard) Display(data string) {
v.Data = data
fmt.Printf("VideoCard: display %s\n", v.Data)
GetMediatorInstance().changed(v)
}
// SoundCard 聲卡類,播放音頻數據
type SoundCard struct {
Data string
}
func (s *SoundCard) Play(data string) {
s.Data = data
fmt.Printf("SoundCard: play %s\n", s.Data)
GetMediatorInstance().changed(s)
}
根據目前已經寫了的代碼來看,主要有兩大類:外部設備驅動和CPU處理
所以中介模式的作用就是將這兩個大類連起來,那么怎么做中介模式呢?我們只需要定義一個中介類包含所有已有的對象,同時根據傳入對象的類型來判斷此時執行外部驅動還是CPU邏輯就可以
我們使用單例模式生成中介者,同時使用switch語句完成類型判斷,主要就是這兩種類型
// Mediator 中介類
type Mediator struct {
CD *CDDriver
Cpu *CPU
Video *VideoCard
Sound *SoundCard
}
func (m *Mediator) changed(i interface{}) {
switch inst := i.(type) {
case *CDDriver:
m.Cpu.Process(inst.Data)
case *CPU:
m.Sound.Play(inst.Sound)
m.Video.Display(inst.Video)
}
}
var mediator *Mediator
// GetMediatorInstance 獲取單例對象
func GetMediatorInstance() *Mediator {
if mediator == nil {
mediator = &Mediator{}
}
return mediator
}
在使用中介模式時,我們需要挨個實例化中介類中所有的對象,個人感覺這種寫法並不是很好
mediator := GetMediatorInstance()
mediator.CD = &CDDriver{}
mediator.CPU = &CPU{}
mediator.Video = &VideoCard{}
mediator.Sound = &SoundCard{}
//Tiggle
mediator.CD.ReadData()
優點:
- 降低了類的復雜性,將一對多轉化成了一對一
- 各個類之間的解耦
- 符合迪米特原則
缺點:
- 中介者會龐大,變得復雜難以維護
3.2 觀察者模式
當對象存在一對多關系時,則使用觀察者模式
比如,當一個對象唄修改時,則會自動通知依賴它的對象
我們首先定義一個最簡單的被觀察的對象,其需要維護一個與它關聯的觀察者列表,同時實現通知、綁定觀察者等一系列方法
// Subject 目標類,被觀察對象
type Subject struct {
observers []Observer // 觀察者列表
context string // 上下文信息
}
// Attach 綁定某個觀察者
func (s *Subject) Attach(o Observer) {
s.observers = append(s.observers, o)
}
// notify 通知觀察者變更信息
func (s *Subject) notify() {
for _, o := range s.observers {
o.Update(s)
}
}
// UpdateContext 變更自己的上下文信息,並通知觀察者變更
func (s *Subject) UpdateContext(ctx string) {
s.context = ctx
s.notify()
}
func NewSubject() *Subject {
return &Subject{
observers: make([]Observer, 0),
}
}
然后我們定義一個觀察者接口和其實現類
// Observer 觀察者接口
type Observer interface {
Update(s *Subject)
}
// 其中一個觀察者的實現類
type Reader struct {
name string
}
func (r *Reader) Update(s *Subject) {
fmt.Printf("%s receive %s\n", r.name, s.context)
}
func NewReader(name string) Observer {
return &Reader{name: name}
}
代碼可以很清晰的看出來,當我們變更被觀察者的上下文信息時,其將會將變更信息推送至所有的觀察者
我們可以綁定多個Reader
觀察者,來看一下被觀察者上下文變更通知的實現
func ExampleObserver() {
subject := NewSubject()
reader1 := NewReader("reader1")
reader2 := NewReader("reader2")
reader3 := NewReader("reader3")
subject.Attach(reader1)
subject.Attach(reader2)
subject.Attach(reader3)
subject.UpdateContext("observer mode")
// Output:
// reader1 receive observer mode
// reader2 receive observer mode
// reader3 receive observer mode
}
優點:
- 觀察者和被觀察者是抽象耦合的
- 建立一套觸發機制
缺點:
- 如果一個被觀察者對象有很多直接和間接的觀察者的話,將所有的觀察者都通知到需要一定的時間成本
- 如果觀察者和觀察目標之間有循環依賴的話,觀察目標會觸發他們之間的循環調用,可能使系統崩潰
- 觀察者模式沒有相應的機制讓觀察者知道所觀察的目標對象是怎么發生變化的,而僅僅知道觀察目標發生了變化
3.3 命令模式
命令模式是一種數據驅動的設計模式
請求以命令的形式包裹在對象中,並上傳給調用對象。調用對象尋找可以處理該命令的合適的對象,並把命令傳給相應的對象,該對象執行命令
命令模式的本質就是把某個對象的方法調用封裝到對象中,方便傳遞、存儲、調用
讓我們考慮這樣一個場景:一台電腦的啟動和重啟,需要主板類、按鍵類、命令類的參與
首先定義一個抽象的命令接口,其中有兩個具體實現類
// Command 命令抽象接口
type Command interface {
Execute()
}
// StartCommand 開機命令類
type StartCommand struct {
mb *MotherBoard
}
func (s *StartCommand) Execute() {
s.mb.Start()
}
func NewStartCommand(mb *MotherBoard) Command {
return &StartCommand{
mb: mb,
}
}
// RebootCommand 重啟命令類
type RebootCommand struct {
mb *MotherBoard
}
func (r *RebootCommand) Execute() {
r.mb.Reboot()
}
func NewRebootCommand(mb *MotherBoard) Command {
return &RebootCommand{
mb: mb,
}
}
然后設計主板類,主板需要有兩個方法:開機和重啟
// MotherBoard 主板類,命令的具體執行類
type MotherBoard struct{}
func (m *MotherBoard) Start() {
fmt.Println("system starting")
}
func (m *MotherBoard) Reboot() {
fmt.Println("system rebooting")
}
最后實現按鈕類,按鈕類只是定義了兩個按鈕所執行的對象的方法,而具體執行哪個對象的方法需要我們自己指定
// Box 按鈕類
type Box struct {
button1 Command
button2 Command
}
func (b *Box) PressButton1() {
b.button1.Execute()
}
func (b *Box) PressButton2() {
b.button2.Execute()
}
func NewBox(button1, button2 Command) *Box {
return &Box{
button1: button1,
button2: button2,
}
}
在使用中,我們讓第一個機箱box1
的按鈕1是開機,按鈕2是重啟;box2
的按鈕1是重啟,按鈕2是開機
func ExampleCommand() {
mb := &MotherBoard{}
startCommand := NewStartCommand(mb)
rebootCommand := NewRebootCommand(mb)
box1 := NewBox(startCommand, rebootCommand)
box1.PressButton1()
box1.PressButton2()
box2 := NewBox(rebootCommand, startCommand)
box2.PressButton1()
box2.PressButton2()
// Output:
// system starting
// system rebooting
// system rebooting
// system starting
}
優點:
- 降低了系統耦合度
- 新的命令可以很容易添加到系統中去
缺點:
- 使用命令模式可能會導致某些系統有過多的具體命令類
3.4 迭代器模式
迭代器模式用於順序訪問集合對象的元素,不需要知道集合對象的底層表示
在迭代器模式中,我們需要定義一個聚合對象接口和迭代器接口,之后所有加入的聚合對象都需要實現迭代器方法
// Aggregate 聚合對象抽象接口,聚合對象需要實現迭代器
type Aggregate interface {
Iterator() Iterator
}
// Iterator 迭代器抽象接口,至少有以下三個方法
type Iterator interface {
First() // 第一個元素
IsDone() bool // 是否結束
Next() interface{} // 下一個元素
}
現在我們實現一個聚合對象的類,這個類必須實現迭代器方法
// Numbers 一個聚合對象
type Numbers struct {
start, end int
}
func (n *Numbers) Iterator() Iterator {
return &NumberIterator{
numbers: n,
next: n.start,
}
}
然后來實現它的迭代器類,迭代器類至少需要實現迭代器抽象接口定義的所有方法
// NumberIterator Number聚合對象的迭代器類
type NumberIterator struct {
numbers *Numbers
next int
}
func (n *NumberIterator) First() {
n.next = n.numbers.start
}
func (n *NumberIterator) IsDone() bool {
return n.next > n.numbers.end
}
func (n *NumberIterator) Next() interface{} {
if !n.IsDone() {
next := n.next
n.next++
return next
}
return nil
}
關於迭代器的使用在C++和java中非常多,C++的STL容器就是通過迭代器來訪問的
我們只需要聲明一個聚合對象,然后使用迭代器來遍歷它就可以了
在此我們在定義一個使用迭代器遍歷打印的函數
func IteratorPrint(i Iterator) {
for i.First(); !i.IsDone(); {
c := i.Next()
fmt.Printf("%#v\n", c)
}
}
func ExampleIterator() {
var aggregate Aggregate
aggregate = NewNumbers(1, 10)
IteratorPrint(aggregate.Iterator())
// Output:
// 1
// 2
// 3
// 4
// 5
// 6
// 7
// 8
// 9
// 10
}
優點:
- 支持以不同的方式遍歷一個聚合對象
- 迭代器簡化了聚合類
- 在同一個聚合上可以有很多遍歷
- 在迭代器模式中,新增加的聚合類和迭代器類都很方便,無需修改原有代碼
缺點:
- 由於迭代器模式將存儲數據和遍歷數據的指責分離,新增加的聚合類需要對應增加新的迭代器類,類的個數成對增加,這在一定程度上增加了系統的復雜性
3.5 模版方法模式
在模版模式中,一個抽象類公開定義了執行它的方法/模版,它的子類可以按需重寫方法實現,但調用將以抽象類中定義的方式進行
// Downloader 下載器抽象接口
type Downloader interface {
Download(uri string)
}
// implement 實現接口
type implement interface {
download()
save()
}
// template 模板類
type template struct {
implement
uri string
}
func (t *template) Download(uri string) {
t.uri = uri
fmt.Println("prepare downloading")
t.implement.download()
t.implement.save()
fmt.Println("finish downloading")
}
func (t *template) save() {
fmt.Println("default save")
}
// newTemplate 實例化一個模板需要implement接口的實現
func newTemplate(impl implement) *template {
return &template{
implement: impl,
}
}
// HTTPDownloader HTTP下載器類
type HTTPDownloader struct {
*template
}
func (d *HTTPDownloader) download() {
fmt.Printf("download %s via http\n", d.uri)
}
func (*HTTPDownloader) save() {
fmt.Printf("http save\n")
}
func NewHTTPDownloader() Downloader {
downloader := &HTTPDownloader{}
temp := newTemplate(downloader)
downloader.template = temp
return downloader
}
// FTPDownloader FTP下載器類
type FTPDownloader struct {
*template
}
func (d *FTPDownloader) download() {
fmt.Printf("download %s via ftp\n", d.uri)
}
func NewFTPDownloader() Downloader {
downloader := &FTPDownloader{}
template := newTemplate(downloader)
downloader.template = template
return downloader
}
var downloader Downloader = NewHTTPDownloader()
downloader.Download("http://example.com/abc.zip")
// Output:
// prepare downloading
// download http://example.com/abc.zip via http
// http save
// finish downloading
優點:
- 封裝不變部分,擴展可變部分
- 提取公公帶嗎,便於維護
- 行為由父類控制,子類實現
缺點:
- 每一個不同的實現都需要一個子類,導致子類數量增加
3.6 策略模式
在策略模式中,一個類的行為或其算法可以在運行時更改
在策略模式中,我們創建表示各種策略的對象和一個行為隨着策略對象改變而改變的context對象,策略對象改變context對象的執行算法
我們模擬一個支付場景,可選擇的支付方式為現金和銀行轉賬
我們首先需要定義一個總體的支付類,其中包含了可能的支付方式和賬戶上下文信息
// Payment 支付類
type Payment struct {
context *PaymentContext // 上下文信息(金額和卡號等)
strategy PaymentStrategy // 支付方式
}
func (p *Payment) Pay() {
p.strategy.Pay(p.context)
}
func NewPayment(name, cardid string, money int, strategy PaymentStrategy) *Payment {
return &Payment{
context: &PaymentContext{
Name: name,
CardID: cardid,
Money: money,
},
strategy: strategy,
}
}
// PaymentContext 支付上下文信息
type PaymentContext struct {
Name, CardID string
Money int
}
然后我們定義支付方式的抽象接口,之后天機任意的支付方式只需要實現這個接口即可
// PaymentStrategy 支付方式抽象接口
type PaymentStrategy interface {
Pay(*PaymentContext)
}
進而添加現金支付和銀行轉賬的方式
// Cash 現金類,需要實現 PaymentStrategy 接口
type Cash struct{}
func (*Cash) Pay(ctx *PaymentContext) {
fmt.Printf("Pay $%d to %s by cash", ctx.Money, ctx.Name)
}
// Bank 銀行類,需要實現 PaymentStrategy 接口
type Bank struct{}
func (*Bank) Pay(ctx *PaymentContext) {
fmt.Printf("Pay $%d to %s by bank account %s", ctx.Money, ctx.Name, ctx.CardID)
}
在使用策略模式時,我們只需要通過對外暴露的NewPayment()
函數使用不同的支付方式的實現進行初始化,就可以調用相應的方法了
func ExamplePayByCash() {
payment := NewPayment("Ada", "", 123, &Cash{})
payment.Pay()
// Output:
// Pay $123 to Ada by cash
}
優點:
- 算法可以自由切換
- 避免使用多重條件判斷
- 擴展性良好
缺點:
- 策略類會增多
- 所有策略類都需要對外暴露
3.7 狀態模式
在狀態模式中,類的行為是基於它的狀態而改變的
我們創建表示各種狀態的對象和一個行為隨着狀態改變而改變的context對象
首先我們定義一個DayContext
類:
type DayContext struct {
today Week
}
func (d *DayContext) Today() {
d.today.Today()
}
func (d *DayContext) Next() {
d.today.Next(d)
}
func NewDayContext() *DayContext {
return &DayContext{
today: &Sunday{},
}
}
其次我們用每一個類來表示每一種狀態的變化,首先是一個通用的接口類型:
type Week interface {
Today()
Next(*DayContext)
}
type Sunday struct{}
func (*Sunday) Today() {
fmt.Printf("Sunday\n")
}
func (*Sunday) Next(ctx *DayContext) {
ctx.today = &Monday{}
}
type Monday struct{}
func (*Monday) Today() {
fmt.Printf("Monday\n")
}
func (*Monday) Next(ctx *DayContext) {
ctx.today = &Tuesday{}
}
type Tuesday struct{}
func (*Tuesday) Today() {
fmt.Printf("Tuesday\n")
}
func (*Tuesday) Next(ctx *DayContext) {
ctx.today = &Wednesday{}
}
type Wednesday struct{}
func (*Wednesday) Today() {
fmt.Printf("Wednesday\n")
}
func (*Wednesday) Next(ctx *DayContext) {
ctx.today = &Thursday{}
}
type Thursday struct{}
func (*Thursday) Today() {
fmt.Printf("Thursday\n")
}
func (*Thursday) Next(ctx *DayContext) {
ctx.today = &Friday{}
}
type Friday struct{}
func (*Friday) Today() {
fmt.Printf("Friday\n")
}
func (*Friday) Next(ctx *DayContext) {
ctx.today = &Saturday{}
}
type Saturday struct{}
func (*Saturday) Today() {
fmt.Printf("Saturday\n")
}
func (*Saturday) Next(ctx *DayContext) {
ctx.today = &Sunday{}
}
func ExampleWeek() {
ctx := NewDayContext()
todayAndNext := func() {
ctx.Today()
ctx.Next()
}
for i := 0; i < 8; i++ {
todayAndNext()
}
// Output:
// Sunday
// Monday
// Tuesday
// Wednesday
// Thursday
// Friday
// Saturday
// Sunday
}
優點:
- 封裝了轉換規則
- 枚舉可能的狀態,在枚舉狀態之前需要確定狀態種類
- 將所有與某個狀態有關的行為放到一個類中,並且可以方便地增加新的狀態,只需要改變對象狀態即可改變對象的行為
- 允許狀態轉換邏輯與狀態對象合為一體,而不是某一個巨大的條件語句塊
- 可以讓多個環境對象共享一個狀態對象,從而減少系統中對象的個數
缺點:
- 狀態模式的使用必然會增加系統類和對象的個數
- 狀態模式的結構與實現都較為復雜,如果使用不當將導致程序結構和代碼的混亂
- 狀態模式對"開閉原則"的支持並不太好,對於可以切換狀態的狀態模式,增加新的狀態類需要修改那些負責狀態轉換的源代碼,否則無法切換到新增狀態,而且修改某個狀態類的行為也需修改對應類的源代碼
3.8 備忘錄模式
備忘錄模式保存一個對象的某個狀態,以便在適當的時候恢復對象
我們需要定義一個備忘錄接口及其具體實現
type Memento interface{}
type gameMemeto struct {
hp, mp int
}
模擬一個游戲場景,需要一個備忘錄來記錄上一個狀態的hp和mp,如果需要記錄多個狀態需要一個數組或者鏈表來存儲gameMemeto
對象即可
type Game struct {
hp, mp int
memo Memento
}
func (g *Game) Play(mpDelta, hpDelta int) {
g.mp += mpDelta
g.hp += hpDelta
}
func (g *Game) Save() {
g.memo = &gameMemeto{
hp: g.hp,
mp: g.mp,
}
}
func (g *Game) Load() {
gm := g.memo.(*gameMemeto)
g.mp = gm.mp
g.hp = gm.hp
}
func (g *Game) Status() {
fmt.Printf("Current HP: %d, MP: %d\n", g.hp, g.mp)
}
優點:
- 給用戶提供了一種可以恢復狀態的機制,可以使用戶能夠比較方便地回道某個歷史的狀態
- 實現了信息的封裝,使得用戶不需要關系狀態的保存細節
缺點:
- 消耗資源,存儲狀態可能需要大量的內存
3.9 解釋器模式
解釋器模式提供了評估語言的語法或者表達式的方式
這種模式實現了一個表達式接口,該接口解釋一個特定的上下文,這種模式被用在SQL解析、符號處理引擎等
首先我們需要定義一個抽象接口和三個負責處理數值的實現類
// Node node抽象接口
type Node interface {
Interpret() int
}
// ValNode 負責賦值的類
type ValNode struct {
val int
}
func (v *ValNode) Interpret() int {
return v.val
}
// AddNode 負責加法運算的類
type AddNode struct {
left, right Node
}
func (a *AddNode) Interpret() int {
return a.left.Interpret() + a.right.Interpret()
}
// MinNode 負責減法運算的類
type MinNode struct {
left, right Node
}
func (m *MinNode) Interpret() int {
return m.left.Interpret() + m.right.Interpret()
}
然后定義一個解釋器,解釋器需要做的事情就是解析一個表達式,然后調用節點相關的方法
// Parser 解釋器類
type Parser struct {
exp []string
index int
prev Node
}
// Parse 解析表達式方法
func (p *Parser) Parse(exp string) {
p.exp = strings.Split(exp, " ")
for {
if p.index >= len(p.exp) {
return
}
switch p.exp[p.index] {
case "+":
p.prev = p.newAddNode()
case "-":
p.prev = p.newMinNode()
default:
p.prev = p.newValNode()
}
}
}
// newAddNode 加法運算
func (p *Parser) newAddNode() Node {
p.index++
return &AddNode{
left: p.prev,
right: p.newValNode(),
}
}
// newMinNode 減法運算
func (p *Parser) newMinNode() Node {
p.index++
return &MinNode{
left: p.prev,
right: p.newValNode(),
}
}
func (p *Parser) newValNode() Node {
v, _ := strconv.Atoi(p.exp[p.index])
p.index++
return &ValNode{v}
}
func (p *Parser) Result() Node {
return p.prev
}
如果不使用解釋器模式,我們在調用上面定義的三個實現類的時候需要探究源碼實現,而現在我們只需要簡單實例化一個解釋器對象,就可以達到這一目的
p := &Parser{}
p.Parse("1 + 2 + 3 - 4 + 5 - 6")
res := p.Result().Interpret()
fmt.Println(res)
// output: 1
優點:
- 可擴展性比較好,靈活
- 增加了新的解釋表達式的方式
- 易於實現簡答文法
缺點:
- 可利用常見比較少
- 對於復雜的文法比較難維護
- 解釋器模式會引起類膨脹
- 解釋器模式通常采用遞歸調用的方法
3.10 職責鏈模式
職責鏈模式為請求創建了一個接收者對象的鏈
這種模式給予請求的類型,對請求的發送者和接受者進行解耦
在這種模式中,通常每個接受者都包含對另一個接受者的引用,如果一個對象不能處理該請求,那么它會把相同的請求傳給下一個接受者
首先我們需要定義一個抽象的經理接口和職責鏈類
type Manager interface {
HaveRight(money int) bool
HandleFeeRequest(name string, money int) bool
}
type RequestChain struct {
Manager
successor *RequestChain
}
// SetSuccessor 設置后驅節點
func (r *RequestChain) SetSuccessor(m *RequestChain) {
r.successor = m
}
func (r *RequestChain) HandleFeeRequest(name string, money int) bool {
if r.Manager.HaveRight(money) {
return r.Manager.HandleFeeRequest(name, money)
}
if r.successor != nil {
return r.successor.HandleFeeRequest(name, money)
}
return false
}
func (r *RequestChain) HaveRight(money int) bool {
return true
}
為了突出職責鏈的特點,我們定義了三個不同權限的經理類,他們分別可以處理不同的請求
// ProjectManager 項目經理類
type ProjectManager struct{}
// HaveRight 項目經理只能處理500以下的金額
func (p *ProjectManager) HaveRight(money int) bool {
return money < 500
}
// HandleFeeRequest 項目經理只能處理 Bob 的請求
func (p *ProjectManager) HandleFeeRequest(name string, money int) bool {
if name == "Bob" {
fmt.Printf("Project manager permit %s %d fee request\n", name, money)
return true
}
fmt.Printf("Project manager don't permit %s %d request\n", name, money)
return false
}
func NewProjectManagerChain() *RequestChain {
return &RequestChain{
Manager: &ProjectManager{},
}
}
// DepManager 部門經理
type DepManager struct{}
// HaveRight 部門經理只能處理5000以下的金額
func (d *DepManager) HaveRight(money int) bool {
return money < 5000
}
// HandleFeeRequest 部門經理只能處理 Tom 的請求
func (d *DepManager) HandleFeeRequest(name string, money int) bool {
if name == "Tom" {
fmt.Printf("Dep manager permit %s %d fee request\n", name, money)
return true
}
fmt.Printf("Dep manager don't permit %s %d request\n", name, money)
return false
}
func NewDeptManagerChain() *RequestChain {
return &RequestChain{
Manager: &DepManager{},
}
}
// GeneralManager 總經理類
type GeneralManager struct{}
// HaveRight 總經理有任何權限
func (g *GeneralManager) HaveRight(money int) bool {
return true
}
// HandleFeeRequest 總經理只能處理 Ada 的請求
func (g *GeneralManager) HandleFeeRequest(name string, money int) bool {
if name == "Ada" {
fmt.Printf("General manager permit %s %d fee request\n", name, money)
return true
}
fmt.Printf("General manager don't permit %s %d request\n", name, money)
return false
}
func NewGeneraltManagerChain() *RequestChain {
return &RequestChain{
Manager: &GeneralManager{},
}
}
職責鏈模式重點不在於其模式的設計和定義,而在於使用,模式的設計可以理解為一個鏈表就好
在使用中,我們定義三個職責鏈的節點,即三個經理的角色,應該注意其中的權限更大的對象應該放在鏈表結尾
當收到一個請求時,如果前面的節點可以處理這個請求就處理,處理不了就推給下一個節點處理
c1 := NewProjectManagerChain()
c2 := NewDepManagerChain()
c3 := NewGeneralManagerChain()
c1.SetSuccessor(c2)
c2.SetSuccessor(c3)
var c Manager = c1
c.HandleFeeRequest("Bob", 400)
c.HandleFeeRequest("Tom", 1400)
c.HandleFeeRequest("Ada", 100000)
c.HandleFeeRequest("Floar", 500)
// output:
// Project manager permit Bob 400 fee request
// Dep manager permit Tom 1400 fee request
// General manager permit Ada 100000 fee request
// Project manager don't permit Floar 500 fee request
優點:
- 降低耦合度,它將請求的發送者和接受者解耦
- 簡化了對象,使得對象不需要知道鏈的結構
- 增強給對象指派責任的靈活性,通過改變鏈內的成員或者調動它們的次序,允許動態地新增或者刪除責任
- 增加新的請求處理類很方便
缺點:
- 不能保證請求一定被接收
- 系統性能將受到一定影響,而且在進行代碼調試時不太方便,可能會造成循環調用
- 可能不容易觀察運行時的特征,有礙於除錯
3.11 訪問者模式
在訪問者模式中,使用一個訪問者類,它改變元素類的執行算法
通過這種方式,元素的執行算法可以隨着訪問者改變而改變
根據模式,元素對象已經接收訪問者對象,這樣訪問者對象就可以處理元素對象上的操作
type Customer interface {
Accept(Visitor)
}
type Visitor interface {
Visit(Customer)
}
type EnterpriseCustomer struct {
name string
}
type CustomerCol struct {
customers []Customer
}
func (c *CustomerCol) Add(customer Customer) {
c.customers = append(c.customers, customer)
}
func (c *CustomerCol) Accept(visitor Visitor) {
for _, customer := range c.customers {
customer.Accept(visitor)
}
}
func NewEnterpriseCustomer(name string) *EnterpriseCustomer {
return &EnterpriseCustomer{
name: name,
}
}
func (c *EnterpriseCustomer) Accept(visitor Visitor) {
visitor.Visit(c)
}
type IndividualCustomer struct {
name string
}
func NewIndividualCustomer(name string) *IndividualCustomer {
return &IndividualCustomer{
name: name,
}
}
func (c *IndividualCustomer) Accept(visitor Visitor) {
visitor.Visit(c)
}
type ServiceRequestVisitor struct{}
func (*ServiceRequestVisitor) Visit(customer Customer) {
switch c := customer.(type) {
case *EnterpriseCustomer:
fmt.Printf("serving enterprise customer %s\n", c.name)
case *IndividualCustomer:
fmt.Printf("serving individual customer %s\n", c.name)
}
}
// only for enterprise
type AnalysisVisitor struct{}
func (*AnalysisVisitor) Visit(customer Customer) {
switch c := customer.(type) {
case *EnterpriseCustomer:
fmt.Printf("analysis enterprise customer %s\n", c.name)
}
}
優點:
- 符合單一職責原則
- 優秀的可擴展性
- 靈活性
缺點:
- 具體元素對訪問者公開細節,違反了迪米特原則
- 具體元素變更比較困難
- 違反了依賴倒置原則,依賴了具體類,沒有依賴抽象