10.8 用writeback代替更新索引
我们可以用writeback=True模式打开shelf,这种模式通过保存每个对象的缓存版本追踪所有可变对象的更改。相比于跟踪所有访问过的对象来检测和保存更改这种会给shelve模块带来沉重负担的方法,这里展示的方式会更新一个可变对象然后强制shelf更新这个特定对象的持久化版本。
这对运行时性能只有轻微的影响。例如,add_post()操作会稍微变慢一些,因为它也需要更新Blog对象。如果要添加多个Posts,这些额外的对Blog的更新就会变成一笔不小的开销。但是,通过避免用冗长的搜索在shelf的所有键中查询某个给定Blog的Post对象,我们可以提高生成Blog的性能,这样一来也能平衡writeback所带来的开销。这里展示的设计会避免创建writeback缓存,因为这种缓存在运行时有可能无限制增长。
模式演变
当使用shelve时,我们必须重视模式演变的问题。对象包含动态的状态和静态的类定义,可以很容易地持久化动态的状态。类定义是持久化数据的模式。但是,类并不是完全静态的。如果修改了类定义,我们如何从shelf上获取对象呢?对于这个问题,一个好的设计通常会包含以下几项技术。
改变方法函数和特性不会改变已经保存的对象状态。我们可以将这些改变归类为次要改变,因为已经保存到shelf上的数据与修改后的类定义仍然是兼容的。一个新的软件版本可以包含一个新的次要版本号,用户对这种版本应该有足够的信心,相信它是可以正常工作的。
改变属性会改变已经保存的对象状态,我们可以称这些为主要改变,因为已经保存到shelf上的数据与新的类定义不兼容。这种类型的改变不应该通过修改类定义来完成。这种类型的改变应该通过定义一个新的子类并且提供一个更新过的工厂函数来创建这个类不同版本的实例。
我们可以灵活地支持多个版本,或者可以用一次性转换。为灵活性考虑,必须基于工厂函数创建对象的实例。一个灵活的应用程序会避免直接创建对象。通过使用工厂函数,我们可以保证应用程序的所有部分都可以一致地协作。我们可能会用下面这样的方式支持灵活的模式改变。
def make_blog( args, **kw ):
version= kw.pop('_version',1)
if version == 1: return Blog( args, kw )
elif version == 2: return Blog2( *args, kw )
else: raise Exception( "Unknown Version {0}".format(version) )
这种工厂函数需要一个_version关键字参数来明确指定要使用哪个Blog类的定义,这让我们可以在不破坏现有程序的前提下,重用一个模式在不同的类上。访问层可以基于这种函数实例化正确版本的对象。我们也可以提供一个下面这样的流畅接口。
class Blog:
@staticmethod
def version( self, version ):
self.version= version
@staticmethod
def blog( self, args, **kw ):
if self.version == 1: return Blog1( args, kw )
elif self.version == 2: return Blog2( *args, kw )
else: raise Exception( "Unknown Version {0}".format(self.version) )
我们可以像下面这样使用这个工厂。
blog= Blog.version(2).blog( title=this, other_attribute=that )
shelf应该包含模式版本的信息,可能是保存为一个特殊的version键。这为访问层决定应该使用哪一个版本的类提供了信息。应用程序在打开shelf之后应该首先获取这个对象,当模式版本错误时应该立刻终止程序。
这种级别灵活性的另外一种实现方式是一次新转换,应用程序的这个功能会用原始的类定义获取shelf上的所有对象,转换成新的类,然后用新的格式将它们保存回shelf上。对GUI应用程序而言,这可能是一个打开的文件或者保存的文件的一部分。对于网络服务器而言,这可能是管理员在发布应用程序时需要运行的一个脚本。
