本来想五一期间休息下停更几天,但是既然有点时间就抓紧时间写吧,早点把设计模式这一系列写完,k8s系列已在排队阻塞中😜。
我们继续讲解结构模式中的享元模式。
享元模式中的享指的的是分享,享元模式就是把一部分属性进行互相分享,从而减少内存的占用。
我们还是从一个实例出发,假设模拟三国时期的战争,两军对垒,各方有百万大军。如何通过程序实现百万大军呢?仍然从反例代码看起,可能就会定义如下一个结构,然后实例化一百万个对象。
type Soldier struct {
Name string // 姓名
Age int // 年龄
Suit string // 制服
Arms string // 武器
}
对于一百万个 Soldier,其中每个 Soldier 的 Name 肯定不同;部分 Age 相同(在18至30之间);在古代打仗一方的战斗服(Suit)肯定是一样的,武器(Arms)不同工种也不同,有弓箭、长矛、盾牌等。
由于对象的的量比较大,为了节约内存,我们是否可以把共有的属性抽离出来,这样就可以减少内存占用了。一个Age字段占8bit(1byte,假设在64位操作系统)。那么一百万个Age字段将会占用约1M的内存空间,在实际的案例中共用属性特别会多得多,相同值的属性占用内存就更多。
我们来看下把相同属性抽离出来的代码:
type Soldier struct {
Name string // 姓名
Age AgeInfo // 年龄
Arms ArmsInfo // 武器
}
type AgeInfo struct {
Age int
}
type ArmsInfo struct {
Arms string
Suit string
}
func (a AgeInfo) GetAge() int {
return a.Age
}
func (a ArmsInfo) GetArms() (string, string) {
return a.Arms, a.Suit
}
虽然需要定义一百万个 Solidier对象,但是只用定义 13个 AgeInfo对象,同时 ArmsInfo 只需要三个对象(如果只有3种兵种), 很明显这已经大大的减少了内存的占用。
但是这里我们提一下字符串的内存使用优化并没有你想象的那么多,这里顺便给大家提一下Go字符串的实现。
Go的字符串同常量一样在堆里申请的内存空间,相同的字符串,是不同字符串对象指针,所以这里我们只是节省了生成的string对象个数,并没有实际减少对string内容的优化。具体的string原理,大家可以自行阅读源码或者搜资料了解。
享元模式就是把公共的属性抽离出来进行复用,减少大批量的相同属性在内存的占用。但是我们同样要注意,享元属性一定是不变的,如果发生变更将会影响一批,甚至所有的对象。
接下来我们来了解下结构模式中的代理模式。
代理模式的概念大家应该都比较清楚了,例如Nginx的反向代理模型。我们的Web服务中使用的一些中间键(权限认证、缓存判断、日志记录等)都是通过代理模式实现的。在真正的请求到服务之前请求先到代理层完成上述工作。我们来看下一个简单的代码的实现:
type API struct{}
func (API) Do() string {
return "do api work"
}
type Proxy struct {
api API
}
func (p Proxy) Do() string {
p.Before()
res := p.api.Do()
p.After()
return res
}
func (p Proxy) Before() {
fmt.Println("before work")
}
func (p Proxy) After() {
fmt.Println("after work")
}
案例中的API比较简单,在真正的web服务中会根据路由抉择请求的方法,案例中省略了这个步骤。
近期回顾: