介绍编程的各种设计模式,用go语言实现。

1. 单例模式

单例模式的定义是:保证一个类仅有一个实例,并提供一个访问它的全局访问点。简单的说就是希望我们的系统中该类仅仅存在1个或0个该类的实例。

1.1 非并发情况

提供了一个GetInstance函数去获取它的实例,根据是否为空来判断究竟应该创建新的还是沿用旧的。用系统时间作为鉴别依据。编写时主要考虑:

  1. 实例具有全局性
  2. 创建前先判断
type manager struct {
    t time.Time
}

func (p manager) manage(){
    s:=fmt.Sprintf("time=%s, managing...",p.t)
    fmt.Println(s)
}

var m *manager
func GetInstance() *manager{
    if m==nil{
        m=new(manager)
        m.t=time.Now()
    }
    return m
}

func main(){
    a:=GetInstance()
    a.manage()
    b:=GetInstance()
    b.manage()//二者输出相同时间,证明单例成功
}

1.2 线程安全

老问题:if m==nil语句到m=new(manager)之间并不安全,可能创建多次。

方法一,可以用锁锁一下:

var m *manager
var mutex sync.Mutex //注意要import sync
func GetInstance() *manager{
    mutex.Lock()
    defer mutex.Unlock()
    if m==nil{
        m=new(manager)
        m.t=time.Now()
    }
    return m
}

方法二,提高效率可以采用双重锁机制,前者每次都会锁,双重锁机制下只有部分情况才会锁。但双重锁有个问题:new是先创建指针然后分配内存,如果在这中间被if抢先了就会造成误操作,多生成一个manager

var m *Manager
var lock *sync.Mutex = &sync.Mutex {}

func GetInstance() *Manager {
    if m == nil {
        lock.Lock()
        defer lock.Unlock()
        if m == nil {
            m = new(manager)
        }
    }
    return m
}

方法三sync.Once,它有一个Do方法,在它中的函数go会只保证仅仅调用一次。跟C++的`std::call_once函数是一个意思。

var m *manager
var once sync.Once
func GetInstance() *manager{
   lambda:=func(){
      m=new(manager)
   }
   once.Do(lambda)
   return m
}

2. 工厂模式

2.1 简单工厂模式

定义一个工厂类,它可以根据参数的不同返回不同类的实例,被创建的实例通常都具有共同的父类。

下面看个例子:

项目 说明
Phone 接口,只有一个方法来获取电池容量
type Huawei struct 华为手机构造体
type Xiaomi struct 小米手机构造体
func (p* Huawei) get Battery 华为手机的GetBattery实现
func (p* Xiaomi) get Battery 小米手机的GetBattery实现
type PhoneFactory struct 手机工厂的构造体
func (factory PhoneFactory) CreatePhone(brand string) Phone 手机工厂的CreatePhone实现

注意GetBattery函数不是必须的,这里只是方便演示。要注意工厂生产函数的返回类型,phone的接口类型

//phone作为接口,统一方法
type Phone interface{
    getBattery()
}

//华为
type Huawei struct{}
func (p* Huawei) getBattery(){
    fmt.Println("[Huawei battery]:4500mAn")
}

//小米
type Xiaomi struct{}
func (p* Xiaomi) getBattery(){
    fmt.Println("[Xiaomi battery]:4400mAn")
}

//工厂生产
type PhoneFactory struct {
}
func (p* PhoneFactory) Creat(brand string) Phone{
    switch brand {
    case "Huawei":
        return new(Huawei)
    case "Xiaomi":
        return new(Xiaomi)
    default:
        return nil
    }
}
func main(){
    var phone Phone
    factory:=new(PhoneFactory)
    phone=factory.Creat("Huawei")
    phone.getBattery()
    phone=factory.Creat("Xiaomi")
    phone.getBattery()
}

2.2 工厂方法模式

  • 简单工厂需要:
    1. 工厂结构体
    2. 产品接口
    3. 产品结构体

假设餐馆生产面条和米饭,如果只有一个餐馆则简单工厂模式即可解决,但如果有开了分店,有多个工厂,就需要工厂方法模式了。

  • 工厂方法需要:
    1. 工厂接口
    2. 工厂结构体
    3. 产品接口
    4. 产品结构体
//Step1. 添加接口
//工厂接口
type FactoryInterface interface {
    Generate(s string) ProductInterface //继承自产品接口
}
// 产品接口
type ProductInterface interface {
    create()
}

//Step2. 创建产品结构体及方法
type RiceShopA struct {}
type NoodleShopA struct {}
type RiceShopB struct {}
type NoodleShopB struct {}

func (p* RiceShopA) create() {
    fmt.Println("A店米饭")
}
func (p* NoodleShopA) create() {
    fmt.Println("A店面条")
}
func (p* RiceShopB) create() {
    fmt.Println("B店米饭")
}
func (p* NoodleShopB) create() {
    fmt.Println("B店面条")
}

//Step3. 创建工厂及方法
type shopA struct{}
type shopB struct{}
func (p* shopA) Generate(s string)ProductInterface{
    switch s{
    case "rice":
        return new(RiceShopA)
    case "noodle":
        return new(NoodleShopA)
    default:
        return nil
    }
}
func (p* shopB) Generate(s string)ProductInterface{
    switch s{
    case "rice":
        return new(RiceShopB)
    case "noodle":
        return new(NoodleShopB)
    default:
        return nil
    }
}

func main(){
    newshopA:=new(shopA)
    a:=newshopA.Generate("rice")
    a.create()
    newshopB:=new(shopB)
    b:=newshopB.Generate("noodle")
    b.create()
}

2.3 抽象工厂模式

与工厂方法模式的区别在于:工厂方法模式是一个工厂接口创建了一个方法,一个方法对应了两个工厂结构体,而抽象工厂模式是一个工厂接口创建了两个方法生产

假设有广东包子铺和祁山包子铺两家,都生产猪肉和三鲜包子:

type FactoryInterface interface {
       CreatePigMeatBuns() ProductInterface // 创建猪肉馅产品
       Create3SBuns() ProductInterface      // 创建三鲜馅产品
}

type ProductInterface interface {
    Intro()
}

然后实现4种产品:

type GDPigMeatBuns struct {
}

func (p GDPigMeatBuns) Intro() {
    fmt.Println("广东猪肉馅包子")
}
// TODO ... 其他产品实现方法没区别

之后实现工厂:

// 齐市包子铺 
type QSFactory struct {
}

func (qs QSFactory) CreatePigMeatBuns() ProductInterface {
    return QSPigMeatBuns{}
}

func (qs QSFactory) Create3SBuns() ProductInterface {
    return QS3SBuns{}
}
// 广东包子铺
type GDFactory struct {
}

func (gd GDFactory) CreatePigMeatBuns() ProductInterface {
    return GDPigMeatBuns{}
}

func (gd GDFactory) Create3SBuns() ProductInterface {
    return GD3SBuns{}
}

最后实际生产:

var f FactoryInterface  // 特意以这种方式声明,更好的体会抽象工厂模式的好处
f = new(QSFactory)  
b := f.CreatePigMeatBuns()  
b.Intro()

优点: 抽象工厂模式除了具有工厂方法模式的优点外,最主要的优点就是可以在类的内部对产品族进行约束。所谓的产品族,一般或多或少的都存在一定的关联,抽象工厂模式就可以在类内部对产品族的关联关系进行定义和描述,而不必专门引入一个新的类来进行管理。

缺点: 产品族的扩展将是一件十分费力的事情,假如产品族中需要增加一个新的产品,则几乎所有的工厂类都需要进行修改。所以使用抽象工厂模式时,对产品等级结构的划分是非常重要的

3. 策略模式

体现的原则是对扩展开放,对修改关闭(开闭原则) ,如果我们要增加一些规则,完全不用修改主业务流程,只需要增加几个策略即可。

假设创建加减乘除方法:

//策略接口
type Strategier interface {
    Compute(num1, num2 int) int
}
//策略之一:除法
type division struct{}
func (p* division) Compute(num1,num2 int) int{
    if num2 == 0 {
        panic("num2 must not be 0!")
    }
    return num1 / num2
}

func NewStrategy(t string)(res Strategier){
    switch t {
    case "d":
        res=new(division)
    default:
        res=nil
    }
    return
}

func main(){
    Strategy:=NewStrategy("d")
    val:=Strategy.Compute(20,10)
    fmt.Println(val)
}

4. 观察者模式

多个对象同时观察一个对象,当这个被观察的对象发生变化的时候,这些对象都会得到通知,可以做一些操作。

核心要点:

  1. 观察者需要接收观察变化的函数
  2. 被观察者要能创建观察者
  3. 被观察者要能通知观察者
//创建观察者
type IObserver interface{
    Notify()
}
//创建被观察者
type IObject interface{
    AddObserver(observers Observer)
    NotifyObservers()
}

type Observer struct{}
func (p* Observer) Notify(){
    fmt.Println("已触发观察者")
}

type Obeject struct{
    Observers []IObserver
}
func (p* Obeject) AddObserver(observer []IObserver){
    p.Observers=append(p.Observers, observer...)
}
func (p* Obeject) NotifyObserver(){
    for k:=range p.Observers{
        p.Observers[k].Notify()
    }
}

func main(){
    // 创建被观察者
    s := new(Obeject)
    // 创建观察者
    o := make([]IObserver,0)
    o=append(o, new(Observer))
    o=append(o, new(Observer))
    // 为主题添加观察者
    s.AddObserver(o)
    s.NotifyObserver()
}

5. 代理模式

举两个生活化的例子:

  1. 火车票的代理售票点。代售点就是代理,它拥有被代理对象的部分功能 — 售票功能
  2. 明星的经纪人,经纪人就是代理,负责为明星处理一些事务。
type seller interface{
    sell(consumer string)
}

type Station struct{ //真正的销售者
    stock int //存货
}
func (p* Station)sell(consumer string){
    if p.stock>0{
        p.stock--
        fmt.Printf("火车站,客户:%s买了一张票,剩余:%d \n", consumer,p.stock)
    }else{
        fmt.Println("已售空")
    }
}

type Xiecheng struct{ //携程代理
    station *Station //持有一个火车站
}
func (p* Xiecheng)sell(consumer string){
    if p.station.stock>0{
        p.station.stock--
        fmt.Printf("携程网,客户:%s买了一张票,剩余:%d \n", consumer,p.station.stock)
    }else{
        fmt.Println("已售空")
    }
}

func main(){
    sta:=Station{3}
    proxy:=new(Xiecheng)
    proxy.station=&sta
    proxy.sell("Bob")
    sta.sell("Lily")
}

6. 生产者消费者模式

6.1 无缓冲

**

var wg sync.WaitGroup
func producer(ch chan<-int){
    defer wg.Done()
    for i:=0;i<3;i++{
        ch<-i
        fmt.Println("send:",i)
    }
}
func consumer(ch <-chan int){
    defer wg.Done()
    for i:=0;i<3;i++{
        v:=<-ch
        fmt.Println("received:",v)
    }
}

func main(){
    wg.Add(2)
    ch:=make(chan int)
    go producer(ch)
    go consumer(ch)
    wg.Wait()
}

因为channel没有缓冲区,所以当生产者给channel赋值后会进入阻塞状态。消费者取出一次数据后由于没有数据可读,也会进入阻塞。输出如下:

send: 0
received: 0
send: 1
received: 1
send: 2
received: 2

如果没有按这个顺序输出,是print输出缓冲区的问题,加个50ms延时即可。

6.2 有缓冲

只需要在ch创建时改为

ch := make(chan int, 10)

输出:

send: 0
send: 1
send: 2
received: 0
received: 1
received: 2