【故事剧情】
大学的室友兼死党 Sam 首次来杭州,作为东道主的 Tony 自然得悉心招待,不敢怠慢。这不,不仅要陪吃陪喝还得陪玩,哈哈!
第一次来杭州,西湖必然是非去不可的。正值周末,风和日丽,最适合游玩。上午 9 点出发,Tony 和 Sam 打一辆滴滴快车从滨江到西湖的南山路,然后从大华饭店步行到断桥,之后是穿越断桥,漫步白堤,游走孤山岛,就这样一路走走停停,闲聊、拍照,很快就到了中午。中午在岳王庙附近找了一家生煎,简单解决午餐(大餐留着晚上吃)。因为拍照拍的比较多,手机没电了,正好看到店里有共享充电宝,便借了一个给手机充满电,也多休息了一个小时。 下午,他们准备骑行最美西湖路;吃完饭,找了两辆共享自行车,从杨公堤开始骑行,路过太子湾、雷峰塔,然后再到柳浪闻莺。之后就是沿湖步行走到龙翔桥,找了一家最具杭州特色的饭店解决晚餐……
这一路行程他们从共享汽车(滴滴快车)到共享自行车,再到共享充电宝,共享的生活方式已如影随形地渗透到了生活的方方面面。共享,不仅让我们出行更便捷,而且资源更节约!
共享经济的飞速发展真的是改变了我们的生活方式,共享自行车、共享雨伞、共享充电宝、共享 KTV 等,共享让我们的生活更便利,你可以不用带充电宝,却可以随时用到它;共享让我们的资源更节约,你可以不用买自行车,但每个人都能骑到自行车(一辆车可以为多个人服务)。我们以共享充电宝为例,用程序来模拟一下它是怎样做到资源节约和共享的。
源码示例:
class PowerBank:
"移动电源"
def __init__(self, serialNum, electricQuantity):
self.__serialNum = serialNum
self.__electricQuantity = electricQuantity
self.__user = ''
def getSerialNum(self):
return self.__serialNum
def getElectricQuantity(self):
return self.__electricQuantity
def setUser(self, user):
self.__user = user
def getUser(self):
return self.__user
def showInfo(self):
print("序列号:" + str(self.__serialNum) + " 电量:" + str(self.__electricQuantity) + "% 使用者:" + self.__user)
class ObjectPack:
"对象的包装类,封装指定的对象(如充电宝)是否被使用中"
def __init__(self, obj, inUsing = False):
self.__obj = obj
self.__inUsing = inUsing
def inUsing(self):
return self.__inUsing
def setUsing(self, isUsing):
self.__inUsing = isUsing
def getObj(self):
return self.__obj
class PowerBankBox:
"存放移动电源的智能箱盒"
def __init__(self):
self.__pools = {}
self.__pools['0001'] = ObjectPack(PowerBank('0001', 100))
self.__pools['0002'] = ObjectPack(PowerBank('0002', 100))
def borrow(self, serialNum):
"使用移动电源"
item = self.__pools.get(serialNum)
result = None
if(item is None):
print("没有可用的电源!")
elif(not item.inUsing()):
item.setUsing(True)
result = item.getObj()
else:
print(str(serialNum) + "电源已被借用!")
return result
def giveBack(self, serialNum):
"归还移动电源"
item = self.__pools.get(serialNum)
if(item is not None):
item.setUsing(False)
print(str(serialNum) + "电源已归还!")
测试代码:
def testPowerBank():
box = PowerBankBox()
powerBank1 = box.borrow('0001')
if(powerBank1 is not None):
powerBank1.setUser('Tony')
powerBank1.showInfo()
powerBank2 = box.borrow('0002')
if(powerBank2 is not None):
powerBank2.setUser('Sam')
powerBank2.showInfo()
powerBank3 = box.borrow('0001')
box.giveBack('0001')
powerBank3 = box.borrow('0001')
if(powerBank3 is not None):
powerBank3.setUser('Aimee')
powerBank3.showInfo()
输出结果:
序列号:0001 电量:100% 使用者:Tony
序列号:0002 电量:100% 使用者:Sam
0001电源已被借用!
0001电源已归还!
序列号:0001 电量:100% 使用者:Aimee
在共享充电宝这个示例中,如果还有未被借用的设备,我们就能借到充电宝给自己的手机充电;用完之后把充电宝还回去,继续让下一个人借用,这样就能让充电宝的利用率达到最大。如共享充电宝一样,在程序中也有一种对应的机制,可以让对象重复地被使用,这就是对象池。
对象池其实就是一个集合,里面包含了我们需要的已经过初始化且可以使用的对象,我们称这些对象都被池化了,也就是被对象池所管理,想要这样的对象,从池子里取一个就行,但是用完得归还。
可以理解对象池为单例模式的延展,多例模式,就那么几个对象实例,再多没有了;要用可以,但用完必须归还,这样其他人才能再使用。可以用下面一张图来形象的表示:
上面共享充电定的示例就能非常形象地类比对象池的概念:对象池就如同存放充电宝的智能箱盒,对象就量充电定,而对象的借用、使用、归还分别对应充电宝的借用、使用、归还。
在《[第17课:生活中的享元模式——颜料很贵必须充分利用]》这一篇文章中我们知道享元模式可以实现对象的共享,通过使用享元模式可以节约内存空间,提高系统的性能。但这个模式也存在一个问题,那就是享元对象的内部状态和属性,一经创建后不会被随意改变。因为如果可以改变,则 A 取得这个对象 obj 后,改变了其状态;B 再去取这个对象 obj 时就已经不是原来的状态了。
对象池机制正好可以解决享元模式的这个缺陷。它通过借、还的机制,让一个对象在某段时间内被一个使用者独占,用完之后归还该对象,在独占的这段时间内使用者可以修改对象的部分属性(因为这段时间内其他用户不会使用这个对象);而享元模式因为没有这种机制,享元对象在整个生命周期都是被所有使用者共享的。
什么就独占?就是你用着这个充电宝,(同一时刻)别人就不能用了,因为只有一个接口,只能给一个手机充电。
什么叫共享?就是深夜中几个人围一圆桌坐着,头顶上挂着一盏电灯,大家都享受着这盏灯带来的光明,这盏电灯就是共享的。而且一定范围内来讲它是无限共享的,因为圆桌上坐着 5 个人和坐着 10 个人,他们感觉到的光亮是一样的。
对象池机制是享元模式的一个延伸,可以理解为享元模式的升级版。
池子、借用、归还是对象池机制的核心思想,我们可以基于这一思想逐步抽象出一个简单可用的实现模型。
from abc import ABCMeta, abstractmethod
# 引入ABCMeta和abstractmethod来定义抽象类和抽象方法
import logging
# 引入logging模块用于输出日志信息
import time
# 引入时间模块
class PooledObject:
"池对象,也称池化对象"
def __init__(self, obj):
self.__obj = obj
self.__busy = False
def getObject(self):
return self.__obj
def setObject(self, obj):
self.__obj = obj
def isBusy(self):
return self.__busy
def setBusy(self, busy):
self.__busy = busy
class ObjectPool(metaclass=ABCMeta):
"对象池"
"对象池初始化大小"
InitialNumOfObjects = 10
"对象池最大的大小"
MaxNumOfObjects = 50
def __init__(self):
self.__pools = []
for i in range(0, ObjectPool.InitialNumOfObjects):
obj = self.createPooledObject()
self.__pools.append(obj)
@abstractmethod
def createPooledObject(self):
"子类提供创建对象的方法"
pass
def borrowObject(self):
# 如果找到空闲对象,直接返回
obj = self._findFreeObject()
if(obj is not None):
logging.info("%s对象已被借用, time:%d", id(obj), time.time())
return obj
# 如果对象池未满,则添加新的对象
if(len(self.__pools) < ObjectPool.MaxNumOfObjects):
pooledObj = self.addObject()
if (pooledObj is not None):
pooledObj.setBusy(True)
logging.info("%s对象已被借用, time:%d", id(pooledObj.getObject()), time.time())
return pooledObj.getObject()
# 对象池已满且没有空闲对象,则返回None
return None
def returnObject(self, obj):
for pooledObj in self.__pools:
if(pooledObj.getObject() == obj):
pooledObj.setBusy(False)
logging.info("%s对象已归还, time:%d", id(pooledObj.getObject()), time.time())
break
def addObject(self):
obj = None
if(len(self.__pools) < ObjectPool.MaxNumOfObjects):
obj = self.createPooledObject()
self.__pools.append(obj)
logging.info("添加新对象%s, time:%d", id(obj), time.time())
return obj
def clear(self):
self.__pools.clear()
def _findFreeObject(self):
"查找空闲的对象"
obj = None
for pooledObj in self.__pools:
if(not pooledObj.isBusy()):
obj = pooledObj.getObject()
pooledObj.setBusy(True)
break
return obj
上面的代码框架可用类图表示如下:
ObjectPool 的一个抽象的对象池,PooledObject 是池对象。实际使用时要实现一个 ObjectPool 的子类并实现 createPooledObject 创建对象的方法;PooledObject 其实是对真实对象的一个包装类,用于控制其是否被占用状态。
有了上面的代码框架之后,我们要实现示例代码的功能就会更简单了。最开始的示例代码假设它为 version 1.0,那么再看看基于框架的 version 2.0 吧。
class PowerBank:
"移动电源"
def __init__(self, serialNum, electricQuantity):
self.__serialNum = serialNum
self.__electricQuantity = electricQuantity
self.__user = ""
def getSerialNum(self):
return self.__serialNum
def getElectricQuantity(self):
return self.__electricQuantity
def setUser(self, user):
self.__user = user
def getUser(self):
return self.__user
def showInfo(self):
print("序列号:%03d 电量:%d%% 使用者:%s" % (self.__serialNum, self.__electricQuantity, self.__user))
class PowerBankPool(ObjectPool):
__serialNum = 0
@classmethod
def getSerialNum(cls):
cls.__serialNum += 1
return cls.__serialNum
def createPooledObject(self):
powerBank = PowerBank(PowerBankPool.getSerialNum(), 100)
return PooledObject(powerBank)
测试代码得稍微改一下:
def testObjectPool():
powerBankPool = PowerBankPool()
powerBank1 = powerBankPool.borrowObject()
if (powerBank1 is not None):
powerBank1.setUser("Tony")
powerBank1.showInfo()
powerBank2 = powerBankPool.borrowObject()
if (powerBank2 is not None):
powerBank2.setUser("Sam")
powerBank2.showInfo()
powerBankPool.returnObject(powerBank1)
# powerBank1归还后,不能再对其进行相关操作
powerBank3 = powerBankPool.borrowObject()
if (powerBank3 is not None):
powerBank3.setUser("Aimee")
powerBank3.showInfo()
powerBankPool.returnObject(powerBank2)
powerBankPool.returnObject(powerBank3)
powerBankPool.clear()
输出结果:
序列号:001 电量:100% 使用者:Tony
序列号:002 电量:100% 使用者:Sam
序列号:001 电量:100% 使用者:Aimee
对象池机制有两个核心对象和三个关键动作。
对象池机制通过借用、归还的思想,实现了对象的重复利用,能有效地节约内存,提升程序性能。
但同时也带来一个问题,就是使用者必须自己去负责对象的借用和归还,这里需要注意两点:
这就类似于 C 语言中对象内存的分配和释放,程序员必须自己负责内存的申请和释放,给程序带来了很大的负担。
要解决这个问题,就要使用引用计数的技术。引用计数的核心相思是:这个对象每多一个使用者(如对象的赋值和传递时),引用就自动加 1;每少一个使用者(如 del 一个变量,或退出作用域),引用就自动减 1。
当引用为1时(只有对象池指向这个对象),自动归还(returnObject)给对象池,这样使用者只需要申请一个对象(borrowObject),而不用关心什么时候归还。
这一部分的实现方式比较复杂,这里将不再详细讲述。引用计数每一门机算机语言的实现方式都各不相同,如 Java 的 Commons-pool 库中就有 SoftReferenceObjectPool 类就是用来解决这个问题的;而 C++ 则可以使用智能指针的方式来实现;Python 的引用计数则是内置了,你可以通过 sys 包中的 getrefcount() 来获得一个对象被引用的数量。
对象池机制特别适用于那些初始化和销毁的代价高,且需要经常被实例化的对象;如大对象、需占用 IO 的对象,这些在创建和销毁时会非常耗时,或对象本身非常占内存的对象。如果是简单的对象,对象的创建和销毁都非常快速,也不吃内存;把它进行池化的时间比自己构建还多,就不划算了。因为对象池的管理本身也是需要占用资源的,如对象的创建、借用、归还这些都是需要消耗资源的。我们经常听到的(数据库)连接池、线程池用到的都是对象池的思想。
这一课讲的是对象池技术中最核心部分的一种实现,在实际的项目开发中,也有很多成熟的开源项目可以用,比如 Java 语言有 Apache 的 commons-pool 库,就提供了种类多样、功能强大的对象池实现;C++ 语言,也有 Boost 库提供了相应的对象池的功能。