Table of Contents:

设计模式是啥呢?简单来说,就是将软件开发中需要重复性解决的编码场景,按最佳实践的方式抽象成一个模型,模型描述的解决方法就是设计模式。

创建型模式

它提供了一种在创建对象的同时隐藏创建逻辑的方式,而不是使用 new 运算符直接实例化对象。
这种类型的设计模式里,单例模式(返回一个对象)和工厂模式(返回多个对象,具体包括简单工厂模式、抽象工厂模式和工厂方法模式三种)

单例模式

饿汉方式

实例是在包被导入时初始化的,用的是全局对象,所以如果初始化耗时,会导致程序加载时间比较长。

package singleton
type singleton struct {
}
var ins *singleton = &singleton{}
func GetInsOr() *singleton {
    return ins
}

懒汉方式

  1. 不加锁,非线程安全的情况
package singleton
type singleton struct {
}
var ins *singleton
func GetInsOr() *singleton {
    if ins == nil {
        ins = &singleton{}
    }
    
    return ins
}
  1. 加锁,线程安全
import "sync"
type singleton struct {
}
var ins *singleton
var mu sync.Mutex
func GetIns() *singleton {
  if ins == nil {
    mu.Lock()
    if ins == nil {
      ins = &singleton{}
    }
    mu.Unlock()
  }
  return ins
}
  1. 使用go的sync.Once的方式
// sync.Once的实现也是通过原子操作和锁实现的,sync.Once保证只执行一次,而且保证第一次还没执行完,使后面的调用阻塞起来
// 以避免后面的使用到一个未初始化完成的对象
package singleton
import (
    "sync"
)
type singleton struct {
}
var ins *singleton
var once sync.Once
func GetInsOr() *singleton {
    once.Do(func() {
        ins = &singleton{}
    })
    return ins
}

工厂模式

  1. 简单工厂模式
    传入参数,返回指定的对象(结构体)
type Person struct {
  Name string
  Age int
}
func (p Person) Greet() {
  fmt.Printf("Hi! My name is %s", p.Name)
}
func NewPerson(name string, age int) *Person {
  return &Person{
    Name: name,
    Age: age,
  }
}
  1. 抽象工厂模式
    和简单工厂模式的唯一区别,就是它返回的是接口而不是结构体。
    通过返回接口,可以在你不公开内部实现的情况下,让调用者使用你提供的各种功能,提供了更大的发挥空间
type Person interface {
  Greet()
}
type person struct {
  name string
  age int
}
func (p person) Greet() {
  fmt.Printf("Hi! My name is %s", p.name)
}
// Here, NewPerson returns an interface, and not the person struct itself
func NewPerson(name string, age int) Person {
  return person{
    name: name,
    age: age,
  }
}
  1. 工厂方法模式
    返回的不是一个对象,而是一个能够创建对象的方法
type Person struct {
  name string
  age int
}
func NewPersonFactory(age int) func(name string) Person {
  return func(name string) Person {
    return Person{
      name: name,
      age: age,
    }
  }
}

// Usage
newBaby := NewPersonFactory(1)
baby := newBaby("john")

newTeenager := NewPersonFactory(16)
teen := newTeenager("jill")

结构型模式

它的特点是关注类和对象的组合

策略模式

在项目开发中,我们经常要根据不同的场景,采取不同的措施,也就是不同的策略。
如果通过 if ... else ... 的形式来调用不同的算法或策略,这种方式称之为硬编码。
所以为了解耦,需要使用策略模式,定义一些独立的类来封装不同的算法,每一个类封装一个具体的算法(即策略),是多态的一个使用例子。
has a的关系。策略使用者持有策略的引用,策略使用者调用策略的方法。

package strategy
// 策略模式
// 定义一个策略类
type IStrategy interface {
  do(int, int) int
}
// 策略实现:加
type add struct{}
func (*add) do(a, b int) int {
  return a + b
}
// 策略实现:减
type reduce struct{}
func (*reduce) do(a, b int) int {
  return a - b
}
// 具体策略的执行者
type Operator struct {
  strategy IStrategy
}
// 设置策略
func (operator *Operator) setStrategy(strategy IStrategy) {
  operator.strategy = strategy
}
// 调用策略中的方法
func (operator *Operator) calculate(a, b int) int {
  return operator.strategy.do(a, b)
}

func TestStrategy(t *testing.T) {
  operator := Operator{}
  operator.setStrategy(&add{})
  result := operator.calculate(1, 2)
  fmt.Println("add:", result)
  operator.setStrategy(&reduce{})
  result = operator.calculate(2, 1)
  fmt.Println("reduce:", result)
}

模版模式

将一个类中能够公共使用的方法放置在抽象类中实现,将不能公共使用的方法作为抽象方法,强制子类去实现,这样就做到了将一个类作为一个模板,让开发者去填充需要填充的地方。也是多态的一个使用例子。
is a的关系,子类继承父的抽象类

package template
import "fmt"
type Cooker interface {
  fire()
  cooke()
  outfire()
}
// 类似于一个抽象类
type CookMenu struct {
}
func (CookMenu) fire() {
  fmt.Println("开火")
}
// 做菜,交给具体的子类实现
func (CookMenu) cooke() {
}
func (CookMenu) outfire() {
  fmt.Println("关火")
}
// 封装具体步骤
func doCook(cook Cooker) {
  cook.fire()
  cook.cooke()
  cook.outfire()
}
type XiHongShi struct {
  CookMenu
}
func (*XiHongShi) cooke() {
  fmt.Println("做西红柿")
}
type ChaoJiDan struct {
  CookMenu
}
func (ChaoJiDan) cooke() {
  fmt.Println("做炒鸡蛋")
}

func TestTemplate(t *testing.T) {
  // 做西红柿
  xihongshi := &XiHongShi{}
  doCook(xihongshi)
  fmt.Println("\n=====> 做另外一道菜")
  // 做炒鸡蛋
  chaojidan := &ChaoJiDan{}
  doCook(chaojidan)
}

行为型模式

特点是关注对象之间的通信

代理模式

代理模式 (Proxy Pattern),可以为另一个对象提供一个替身或者占位符,以控制对这个对象的访问。
代理对象在客户端和目标对象之间起到中介的作用,它去掉客户不能看到的内容和服务或者增加客户需要的额外的新服务。
下面代码中,StationProxy 代理了 Station,代理类中持有被代理类对象,并且和被代理类对象实现了同一接口。

package proxy
import "fmt"
type Seller interface {
  sell(name string)
}
// 火车站
type Station struct {
  stock int //库存
}
func (station *Station) sell(name string) {
  if station.stock > 0 {
    station.stock--
    fmt.Printf("代理点中:%s买了一张票,剩余:%d \n", name, station.stock)
  } else {
    fmt.Println("票已售空")
  }
}
// 火车代理点
type StationProxy struct {
  station *Station // 持有一个火车站对象
}
func (proxy *StationProxy) sell(name string) {
  if proxy.station.stock > 0 {
    proxy.station.stock--
    fmt.Printf("代理点中:%s买了一张票,剩余:%d \n", name, proxy.station.stock)
  } else {
    fmt.Println("票已售空")
  }
}

选项模式

适用的场景
结构体参数很多,创建结构体时,我们期望创建一个携带默认值的结构体变量,并选择性修改其中一些参数的值。
结构体参数经常变动,变动时我们又不想修改创建实例的函数。例如:结构体新增一个 retry 参数,但是又不想在 NewConnect 入参列表中添加retry int这样的参数声明。

选项模式的朴素版

package options
import (
  "time"
)
const (
  defaultTimeout = 10
  defaultCaching = false
)
type Connection struct {
  addr    string
  cache   bool
  timeout time.Duration
}
type ConnectionOptions struct {
  Caching bool
  Timeout time.Duration
}
func NewDefaultOptions() *ConnectionOptions {
  return &ConnectionOptions{
    Caching: defaultCaching,
    Timeout: defaultTimeout,
  }
}
// 增加一个opts结构体选项参数,需要创建差异化对象的话,直接修改opts选项即可
func NewConnect(addr string, opts *ConnectionOptions) (*Connection, error) {
  return &Connection{
    addr:    addr,
    cache:   opts.Caching,
    timeout: opts.Timeout,
  }, nil
}
package options
import (
  "time"
)
type Connection struct {
  addr    string
  cache   bool
  timeout time.Duration
}
const (
  defaultTimeout = 10
  defaultCaching = false
)
type options struct {
  timeout time.Duration
  caching bool
}
// Option overrides behavior of Connect.
type Option interface {
  apply(*options)
}
type optionFunc func(*options)
func (f optionFunc) apply(o *options) {
  f(o)
}
func WithTimeout(t time.Duration) Option {
  return optionFunc(func(o *options) {
    o.timeout = t
  })
}
func WithCaching(cache bool) Option {
  return optionFunc(func(o *options) {
    o.caching = cache
  })
}
// Connect creates a connection.
func NewConnect(addr string, opts ...Option) (*Connection, error) {
  options := options{
    timeout: defaultTimeout,
    caching: defaultCaching,
  }
  for _, o := range opts {
    o.apply(&options)
  }
  return &Connection{
    addr:    addr,
    cache:   options.caching,
    timeout: options.timeout,
  }, nil
}