Flyweight 享元模式

设计模式 2020-05-04 2220 字 1074 浏览 点赞

起步

享元模式属于结构型。“元”有单元,或者内容单元的意思,基本上你可以认为是“同一个对象”。所以享元模式可以理解为共享同一个对象。

听起来是不是很像单例模式?实则不同!

享元模式

现在系统需要增添一个加载配置文件的功能,就是把磁盘里的配置文件内容写入到内存中,供系统使用。功能很简单,代码很容易,话不说多:

class JsonParser(object):
    def parse(self, path) -> Dict:
        ...  # 解析配置文件
        return {  # 返回解析后的配置信息
            "DB": ...,
            "Base": ...,
        }

class ConfigSource(object):
    def load(self, file) -> Dict:
        parser = JsonParser()
        return parser.parse(file)

某个功能需要用到配置信息,就实例化 ConfigSource,然后调用 load 即可。但这里有个问题,如果系统到处都要需要获取配置信息,每需要一次就调用 load,就会发生一次磁盘读事件;其次,如果调用期间配置文件发生了变动,前后调用 load 拿到的配置内容就不一样了,可能会造成系统瞎运行,严重的甚至崩溃;而如果运气好,一切正常运行,但是每个需要读取配置的对象都持有一个单独的配置信息,只是这些配置信息都是相同的,那就造成了内存浪费。

为解决上述问题,我们就可以使用享元模式了。最简单的享元模式仅需对 ConfigSource 做稍稍修改。

class ConfigSource(object):

    config = {}

    def load(self, file) -> Dict:
        # 如果没有配置
        if not self.config:
            parser = JsonParser()
            config = parser.parse(file)
            self.__class__.config = config

        return self.config

现在就能保证系统各处使用的配置信息为同一份,既节约了内存,又减少了 IO 事件。

与单例的区别

就上述代码来说,乍一看,这不就是单例模式吗?!

设计模式之间的区别不在于代码实现,而在于通过什么方式解决了什么问题。单例模式强调一个类只能有一个实例对象,而享元模式则没有此限制;前者是为了限制实例对象的数量,后者是为了节省内存。

譬如现在已知系统每个小时配置信息就会更变一次,不同日期的相同时间配置信息相同。则代码可以如下设计:

class ConfigSource(object):

    config = {}

    def load(self, file) -> Dict:
        # 获取当前小时
        cur_hour = datetime.now().hour
        # 如果没有相关磁盘信息,从磁盘获取
        if cur_hour not in self.config:
            parser = JsonParser()
            self.config[cur_hour] = parser.parse(file)

        return self.config[cur_hour]

如果 24 小时过去,config 里边就存放了 24 个配置信息,这跟单例模式有明显的不同。

与“池化”的区别

我们常见的“池化”有线程池,连接池等等,也是先实例化一定数量的对象,等需要的时候取出来用。好像也是有点享元的意思。

然而,享元模式是允许同一时间点上,被多个客户端持有。拿上述的配置文件实例来说,假设当前配置信息为 config\_a,对于客户端 clientA, clientB,clientC 等等,都可以同时拥有 config\_a,这没毛病。但对于“池化”来说,在一个时间点上,一个对象只能被一个客户端持有。譬如连接池中的连接对象,只有用完以后在放回连接池,才能被其他客户端使用。

总结

享元模式是为了复用对象,节省内存而存在的。是否使用享元模式还有一个限制条件:享元对象是不可变对象。如果配置信息允许被客户端改变,则很可能造成其他客户端持有的配置信息也被改变,在某些时候我们不需要这种改变,就不应该选择享元模式。

参考



本文由 Guan 创作,采用 知识共享署名 3.0,可自由转载、引用,但需署名作者且注明文章出处。

还不快抢沙发

添加新评论