Go语言设计模式(一)
单例模式
单例模式说白了,就是在整个golang单体服务中,如果说把所有博客比喻成一个服务,那么所有原创的文章都是单例的。转载出去的就不是单例的,如果转载后由文章加上对应的用户,如果将其封装起来,也是独一无二的。现在来看看集中golang单例模式的实现,或者说,当一个类只有一个实例时,这就可以理解为单例模式:
- (包导入,整个项目包只会加载一次init)单实例只会被使用不会被赋值的时候。可以在对应package中定义实例对象,然后再init函数中对其赋初始值。注意对象名首字母需要大写,否则其他模块无法使用。实现可以如下:
package tsingleton func init() { TestSingleton = &Singleton{"keqian"} } type Singleton struct { Name string } var TestSingleton *Singleton
调用的时候直接调用包里边的变量即可:
import "tsingleton" psttestsigleton := tsingleton.TestSingleton
- (代码实现)单例实例在使用过程中,有可能会被赋值(理论上认为是有可能发生的,比如编码时候的失误),这时候需要提供一个方法来获取单例实例。实现可以如下:
package tsingleton import ( "fmt" "sync" ) func init() { TestSingleton = &Singleton{"keqian"} } type Singleton struct { Name string } var TestSingleton *Singleton var singletonMutex sync.Mutex func NewSingleton() *Singleton { if TestSingleton == nil { singletonMutex.Lock() if TestSingleton == nil { TestSingleton = &Singleton{"keqian"} } singletonMutex.Unlock() } return TestSingleton }
在使用时:
tsingleton.NewSingleton().Name //这里直接获取变量,实际应用中可能就是调用对应方法等其他操作。
补充:
package main import ( "fmt" "sync" ) type singleton map[string]string var ( once sync.Once instance singleton ) func New() singleton { once.Do(func() { instance = make(singleton) }) return instance } func main() { s := New() s["this"] = "that" s2 := New() fmt.Println("This is ", s2["this"]) // This is that }
工厂模式
工厂模式根据条件产生不同功能类,工厂模式在解耦方面将使用者和产品之间的依赖推给了工厂,让工厂承担这种依赖关系。工厂模式分简单工厂模式、方法工厂模式和抽象工厂模式。Golang实现工厂模式,主要通过interface实现。实现方法也很简单,只要具体功能结构体实现对应工厂接口就可以了。以几个例子来看看golang中工厂模式的实现:
- 简单工厂模式
package main import ( "fmt" ) type Animal interface { Saying() } type Cat struct { } type Programer struct { } type Factory struct { } func (c *Cat) Saying() { fmt.Println("喵喵喵~~~") } func (p *Programer) Saying() { fmt.Println("hello world") } func (f *Factory) CreateAnimal(name string) Animal { switch name { case "Cat": return new(Cat) case "Programer": return new(Programer) default: panic("animal name not exist in the factory") } return nil } func main() { var factory = new(Factory) cat := factory.CreateAnimal("Cat") cat.Saying() programer := factory.CreateAnimal("Programer") programer.Saying() }
从代码可以看到,调用者无需知道具体的产品(Animal)构造产生的细节,只需要知道产品接口就可以了。这使得调用者(main函数)的代码相对逻辑比较单一,不用随产品的变化和做太大的调整(降低了调用者和产品之间的耦合),但是引入了和产品具有强耦合的类(工厂类)。在实际开发过程中根据具体情况看是否需要封装一个工厂类吧,如果有多个产品,且可以抽象出一个一致的接口,采用简单工厂方法模式可以让调用者的逻辑无须发生变化,在调用工厂类中创建具体产品类的时候做点小设置即可,这样就不用有多套代码了。
- 工厂方法模式实现:先来看看golang中方法工厂和抽象工厂模式的大致写法:
package main import ( "fmt" ) type Server interface { GetServerType() string } type Factory interface { CreateServer() Server } type RestfulFactory struct { } func (r *RestfulFactory) CreateServer() Server { return &RestfulServer{} } type SoaFactory struct { } func (s *SoaFactory) CreateServer() Server { return &SoaServer{} } type RestfulServer struct { } func (r *RestfulServer) GetServerType() string { return "微服务" } type SoaServer struct { } func (s *SoaServer) GetServerType() string { return "传统企业服务" } func main() { var factory Factory var server Server factory = &RestfulFactory{} server = factory.CreateServer() fmt.Println("server type is: ", server.GetServerType()) //... factory = &SoaFactory{} server = factory.CreateServer() fmt.Println("server type is: ", server.GetServerType()) }
其实工厂方法模式的实现就是将简单工厂方法中工厂类获取具体产品的方法再做一层抽象。减少了产品抽象和工厂之间的耦合关系,具体的工厂只和具体的产品有耦合关系。这样的话每次增加、减少产品的时候就不用修改工厂类获取具体产品的方法了,将产品的创建延迟到工厂子类中,但是这样也会产生多几个具体工厂类。
- 抽象工厂模式:先来看看golang中方法工厂和抽象工厂模式的大致写法:
package main import ( "fmt" ) type Gatway interface { CreateInputParser() Parser CreeteOutPutEncoder() Encoder } type Parser interface { ParseData() } type Encoder interface { EncodeData() } type XmlParser struct { } func (x *XmlParser) ParseData() { fmt.Println("parse xml data ok") } type JsonParser struct { } func (j *JsonParser) ParseData() { fmt.Println("parse Json data ok") } type XmlEncoder struct { } func (x *XmlEncoder) EncodeData() { fmt.Println("Encode xml data ok") } type JsonEncoder struct { } func (j *JsonEncoder) EncodeData() { fmt.Println("Encode Json data ok") } type GatwayHuawei struct { } func (g *GatwayHuawei) CreateInputParser() Parser { return &XmlParser{} } func (g *GatwayHuawei) CreeteOutPutEncoder() Encoder { return &XmlEncoder{} } type GatwayTencent struct { } func (g *GatwayTencent) CreateInputParser() Parser { return &JsonParser{} } func (g *GatwayTencent) CreeteOutPutEncoder() Encoder { return &JsonEncoder{} } func main() { var gatway Gatway gatway = &GatwayHuawei{} gatway.CreateInputParser().ParseData() gatway.CreeteOutPutEncoder().EncodeData() gatway = &GatwayTencent{} gatway.CreateInputParser().ParseData() gatway.CreeteOutPutEncoder().EncodeData() return }
从例子中可以看到,当一个功能需要多个接口的时候,可以优先考虑使用抽象工厂方法,抽象工厂模式可以看成两个或者多个工厂方法模式的组合。
总结:工厂模式总体上来看,可以分为简单工厂模式和工厂方法模式。当有几个产品,且产品比较固定的时候,使用简单工厂方法,在实现上来看,会比较的简单且逻辑比较的清晰。当产品比较多样化且可能会变化的时候,建议使用工厂方法模式和抽象工厂模式
组合模式
组合模式,使我们在树形结构问题中,使用者可以忽略简单元素和复杂元素的概念。客户程序可以像处理简单元素一样来处理复杂元素,从而使得客户程序与复杂元素内部结构解耦。当应用场景出现分级、分层的时候,使用组合模式就会相对合适。
定义(Go《设计模式》):将对象组合成树形结构以表示“部分整体”的层次结构。组合模式使得用户对单个对象和使用具有一致性。
1.Component 是组合中的对象声明接口,在适当的情况下,实现所有类共有接口的默认行为。声明一个接口用于访问和管理Component
子部件。
2.Leaf 在组合中表示叶子结点对象,叶子结点没有子结点。
3.Composite 定义有枝节点行为,用来存储子部件,在Component接口中实现与子部件有关操作。
可以理解为类似于map嵌套数据结构一样,每个子数据都有相应的属性方法,下面是一个类似于Gin路由的代码,可以进行简单分析:
package webapi import ( "errors" "fmt" "net/http" "strings" "sync" ) type NodeHandler func(http.ResponseWriter, *http.Request) type DirComponent interface { Add(DirComponent) bool Remove(string) bool GetChild() ([]DirComponent, bool) GetName() string Handler(w http.ResponseWriter, req *http.Request) IsDir() bool } type Node struct { Name string Url string NodeHandler NodeHandler } func NewNode(name string, handler NodeHandler) *Node { return &Node{Name: name, NodeHandler: handler} } func (pN *Node) Add(iDirComponent DirComponent) bool { return false } func (pN *Node) Remove(name string) bool { return false } func (pN *Node) GetChild() (sziDirComponent []DirComponent, b bool) { return } func (pN *Node) Handler(w http.ResponseWriter, req *http.Request) { pN.NodeHandler(w, req) } func (pN *Node) GetName() string { return pN.Name } func (pN *Node) IsDir() bool { return false } type Dir struct { Name string Sons []DirComponent mutex sync.Mutex } func NewDir(name string) *Dir { return &Dir{Name: name} } func (pD *Dir) Add(iDirComponent DirComponent) bool { pD.mutex.Lock() for _, dirComponent := range pD.Sons { if iDirComponent.GetName() == dirComponent.GetName() { return false } } pD.Sons = append(pD.Sons, iDirComponent) pD.mutex.Unlock() return true } func (pD *Dir) Remove(name string) bool { pD.mutex.Lock() for i, dirComponent := range pD.Sons { if name == dirComponent.GetName() { pD.Sons = append(pD.Sons[:i], pD.Sons[i+1:]...) pD.mutex.Unlock() return true } } pD.mutex.Unlock() return false } //dir in web must have child func (pD *Dir) GetChild() (dirs []DirComponent, b bool) { if len(pD.Sons) == 0 { return dirs, false } return pD.Sons, true } func (pD *Dir) GetName() string { return pD.Name } func (pD *Dir) IsDir() bool { return true } //return error func (pD *Dir) Handler(w http.ResponseWriter, req *http.Request) { return } func AddDir(url string, node DirComponent, root DirComponent) bool { if node == nil || root == nil || node.IsDir() { return false } dirs := strings.Split(url, "/") dirDeth := len(dirs) rootTmp := root for i, d := range dirs { if i == 0 { continue } if i == dirDeth-1 { sons, ok := rootTmp.GetChild() if ok { for _, dir := range sons { if d == dir.GetName() { return false } } } fmt.Println("bbb") rootTmp.Add(node) return true } //获取子节点 sons, ok := rootTmp.GetChild() if !ok { newdir := NewDir(d) rootTmp.Add(newdir) sons, _ = rootTmp.GetChild() } for _, dir := range sons { if d == dir.GetName() { rootTmp = dir break } } } return true } func DelDir(url string, root DirComponent) bool { if root == nil { return false } dirs := strings.Split(url, "/") dirDeth := len(dirs) rootTmp := root for i, d := range dirs { if i == 0 { continue } sons, ok := rootTmp.GetChild() if !ok { return false } for _, dir := range sons { if d == dir.GetName() { if i == dirDeth-1 { fmt.Println("last") return rootTmp.Remove(d) } rootTmp = dir break } } } return false } func UpdateDir(url string, node DirComponent, root DirComponent) bool { if DelDir(url, root) { return AddDir(url, node, root) } return false } func FindNode(url string, root DirComponent) (DirComponent, error) { if root == nil { return nil, errors.New("root is nil") } dirs := strings.Split(url, "/") dirDeth := len(dirs) rootTmp := root for i, d := range dirs { if i == 0 { continue } sons, ok := rootTmp.GetChild() if !ok { return nil, errors.New("dir no exist!") } for _, dir := range sons { if d == dir.GetName() { if i == dirDeth-1 { return dir, nil } rootTmp = dir break } } } return nil, errors.New("no find!") }