golang实现设计模式之享元模式总结-代码、优缺点、适用场景

发布时间 2023-06-02 11:10:26作者: 进击的davis

享元模式是一种结构型的设计模式,通过共享细粒度对象实现对象的复用,从而达到减少对象创建与销毁,减少内存消耗,其本质是本质是缓存共享对象,降低内存消耗。

享元模式的定义提出了两个要求,细粒度和共享对象。因为要求细粒度,所以不可避免地会使对象数量多且性质相近,此时我们就将这些对象的信息分为两个部分:内部状态和外部状态。[1]

  • 内部状态指对象共享出来的信息,存储在享元信息内部,并且不回随环境的改变而改变;
  • 外部状态指对象得以依赖的一个标记,随环境的改变而改变,不可共享。

比如,连接池中的连接对象,保存在连接对象中的用户名、密码、连接URL等信息,在创建对象的时候就设置好了,不会随环境的改变而改变,这些为内部状态。而当每个连接要被回收利用时,我们需要将它标记为可用状态,这些为外部状态。

在使用享元模式时,我们需要注意与单例模式的区别:

  • 1.如果请求对象都一样,可以使用单例模式
  • 2.如果请求对象相似,可以共享多个共同属性

结构

  • 1.抽象享元角色(Flyweight):即享元的接口,定义相关方法
  • 2.具体享元(Concrete Flyweight)角色:实现享元接口
  • 3.享元工厂(Flyweight Factory)角色:管理享元的创建销毁等

优缺点

  • 优点
    1.缩小对象的创立,升高内存中对象的数量,升高零碎的内存,提高效率。
    2.缩小内存之外的其余资源占用。

  • 缺点
    1.为了使对象可以共享,需要将一些不能共享的状态外部化,这将增加程序的复杂性。
    2.读取享元模式的外部状态会使得运行时间稍微变长。

适用场景
1.系统中存在大量相同或相似的对象,这些对象耗费大量的内存资源,如需要缓冲池,数据库连接池。
2.大部分的对象可以按照内部状态进行分组,且可将不同部分外部化,这样每一个组只需保存一个内部状态。
3.由于享元模式需要额外维护一个保存享元的数据结构,所以应当在有足够多的享元实例时才值得使用享元模式。

代码实现
在 golang 实现中,我们可以用 map 结构,也可以用 sync.Pool 来实现,sync.Pool则封装更好,具体看需求。

package main

import (
   "fmt"
   "sync"
)

/*
业务场景描述:
- 租车场景,客户像车行租车,如果车行有车直接租给客户,没有则从车行的其他分部调车分配
场景角色:
- 1.抽象享元,接口,实现产品具有属性
- 2.具体享元,类,实现抽象享元
- 3.享元工厂,控制和管理享元
- 4.客户端,请求获取产品

 */

// 1.抽象享元-便于扩展
type iFlyweight interface {
   drive()
}

// 2.具体享元
type flyweight struct {
   name string
}

func NewFlyweight(name string) *flyweight {
   return &flyweight{
      name: name,
   }
}

func (r *flyweight) drive()  {
   fmt.Printf("the car [%s] is driving on the road.\n", r.name)
}

// 3.享元工厂
type flyweightFactory struct {
   pool map[string]*flyweight
}

func NewFlyweightFactory() *flyweightFactory {
   return &flyweightFactory{
      pool: make(map[string]*flyweight),
   }
}

func (r *flyweightFactory) Get(name string) *flyweight {
   object, ok := r.pool[name]
   if !ok {
      object = r.schedule(name)
      r.pool[name] = object
   }

   return object
}

// 相当于新建对象
func (r *flyweightFactory) schedule(name string) *flyweight {
   fmt.Printf("车行从其他分部调入: [%s]\n", name)
   return &flyweight{name}
}

// 用完之后的还车
func (r *flyweightFactory ) Put(object *flyweight) error {
   r.pool[object.name] = object
   return nil
}

// sync.pool 实现,封装更好,直接get、put即可
type flyweightFactoryV2 struct {
   pool sync.Pool
}

func NewFlyweightFactoryV2() *flyweightFactoryV2 {
   factory := &flyweightFactoryV2{}
   factory.pool.New = func() any {
      return new(flyweight)
   }

   return factory
}

func (r *flyweightFactoryV2) Get(name string) *flyweight {
   // 使用时需要断言
   object := r.pool.Get().(*flyweight)

   return r.warp(name, object)
}

func (r *flyweightFactoryV2) warp(name string, fly *flyweight) *flyweight {
   fmt.Printf("正在租出一辆车: [%s]\n", name)
   fly.name = name
   return fly
}

// 用完之后的还车
func (r *flyweightFactoryV2) Put(object *flyweight) error {
   r.pool.Put(object)
   return nil
}

// 4.客户端
func main()  {
   // map实现共享池
   fac := NewFlyweightFactory()
   // 获取对象
   car := fac.Get("BMW")
   // 使用对象
   car.drive()
   // 用完还车
   _ = fac.Put(car)

   // 再次租车
   car1 := fac.Get("BMW")

   // 租其他车
   car2 := fac.Get("Benz")

   // 直接还车
   fac.Put(car1)
   fac.Put(car2)

   /* result:
   车行从其他分部调入: [BMW]
   the car [BMW] can driving on the road.
   车行从其他分部调入: [Benz]
    */

   // sync.Pool实现共享池
   facV2 := NewFlyweightFactoryV2()
   // rent
   car3 := facV2.Get("RR")

   // use
   car3.drive()

   // return car
   facV2.Put(car3)
}

参考: