github地址:https://github.com/cheesezh/python_design_patterns

背景

有6个客户想做产品展示网站,其中3个想做成天猫商城那样的“电商风格”展示页面,其中3个想做成博客园那样的“博客风格”展示博客。应该如何实现?

class WebSite():

    def __init__(self, name):
self.name = name def use(self):
print("网站风格:", self.name) def main():
web1 = WebSite("电商风格")
web1.use() web2 = WebSite("电商风格")
web2.use() web3 = WebSite("电商风格")
web3.use() web4 = WebSite("博客风格")
web4.use() web5 = WebSite("博客风格")
web5.use() web6 = WebSite("博客风格")
web6.use() main()
网站风格: 电商风格
网站风格: 电商风格
网站风格: 电商风格
网站风格: 博客风格
网站风格: 博客风格
网站风格: 博客风格

点评

根据上边的代码,如果要做三个“电商风格”,三个“博客风格”的网站,需要六个网站类的实例,而实际上它们本质上都是一样的代码,如果网站增多,实例数量也会增多,这对服务器的资源浪费很严重。

现在各个大型博客网站,电子商务网站,每一个博客或者商家都是一个小型网站,它们根据用户ID号的不同,来区分不同的用户,具体数据和模版可以不同,但是代码核心和数据库却是共享的。

这就需要用到享元模式。

享元模式

享元模式,运用共享技术有效的支持大量细粒度的对象。主要包括以下几个类:

from abc import ABCMeta, abstractmethod

class Flyweight():
"""
Flyweight类,它是所有具体享元类的超类或接口,通过这个接口,Flyweight可以接受并作用于外部状态。
"""
__metaclass__ = ABCMeta @abstractmethod
def operation(self, extrinsicstate):
pass class ConcreteFlyweight(Flyweight):
"""
ConcreteFlyweight是继承Flyweight超类或者是想Flyweight接口,并为内部状态增加存储空间
"""
def operation(self, extrinsicstate):
print("具体Flyweight:", extrinsicstate) class UnsharedConcreteFlyweight(Flyweight):
"""
UnsharedConcreteFlyweight是指那些不需要共享的Flyweight子类。因为Flyweight接口,共享成为可能,但它并不强制共享
"""
def operation(self, extrinsicstate):
print("不共享的具体Flyweight:", extrinsicstate) class FlyweightFactory():
"""
FlyweightFactory是一个享元工厂,用来创建并管理Flyweight对象。它主要是用来确保合理地共享Flyweight,当用户请求一个
Flyweight时,FlyweightFactory对象提供一个已创建的实例或者创建一个(如果不存在的话)。
"""
def __init__(self):
self.flyweights = dict()
self.flyweights['X'] = ConcreteFlyweight()
self.flyweights['Y'] = ConcreteFlyweight()
self.flyweights['Z'] = ConcreteFlyweight() def get_flyweight(self, key):
return self.flyweights[key] def main():
# 代码外部状态
extrinsicstate = 22 f = FlyweightFactory() fx = f.get_flyweight("X")
extrinsicstate -= 1
fx.operation(extrinsicstate) fy = f.get_flyweight("Y")
extrinsicstate -= 1
fy.operation(extrinsicstate) fz = f.get_flyweight("Z")
extrinsicstate -= 1
fy.operation(extrinsicstate) uf = UnsharedConcreteFlyweight()
extrinsicstate -= 1
uf.operation(extrinsicstate) main()
具体Flyweight: 21
具体Flyweight: 20
具体Flyweight: 19
不共享的具体Flyweight: 18

点评

上述代码中,FlyweightFactory根据客户需求返回早已生成好的对象,但是实际上不一定需要,完全可以初始化时什么也不做,到需要时,再判断对象是否为null来决定是否实例化;

为什么会有UnsharedConcreteFlyweight存在呢?这是因为尽管我们大部分时间都需要共享对象来降低内存损耗,但个别时候也有可能不需要共享的,那么此时的UnsharedConcreteFlyweight就有存在的必要了,它可以解决那些不需要共享对象的问题。

网站共享代码

from abc import ABCMeta, abstractmethod

class WebSite():
"""
网站抽象类:Flyweight类,它是所有具体享元类的超类或接口,通过这个接口,Flyweight可以接受并作用于外部状态。
"""
__metaclass__ = ABCMeta @abstractmethod
def use(self):
pass class ConcreteWebSite(WebSite):
"""
具体网站类:ConcreteFlyweight是继承Flyweight超类或者是想Flyweight接口,并为内部状态增加存储空间
"""
def __init__(self, name):
self.name = name def use(self):
print("网站风格:", self.name) class WebSiteFactory():
"""
网站工厂类:FlyweightFactory是一个享元工厂,用来创建并管理Flyweight对象。它主要是用来确保合理地共享Flyweight,
当用户请求一个Flyweight时,FlyweightFactory对象提供一个已创建的实例或者创建一个(如果不存在的话)。
"""
def __init__(self):
self.flyweights = dict() def get_website(self, key):
if key not in self.flyweights:
self.flyweights[key] = ConcreteWebSite(key)
return self.flyweights[key] def main():
f = WebSiteFactory() fx = f.get_website("电商风格")
fx.use() fy = f.get_website("电商风格")
fy.use() fz = f.get_website("电商风格")
fz.use() fa = f.get_website("博客风格")
fa.use() fb = f.get_website("博客风格")
fb.use() fc = f.get_website("博客风格")
fc.use()
print("网站风格总数:", len(f.flyweights)) main()
网站风格: 电商风格
网站风格: 电商风格
网站风格: 电商风格
网站风格: 博客风格
网站风格: 博客风格
网站风格: 博客风格
网站风格总数: 2

点评

这样写基本实现了享元模式共享对象的目的,也就是,无论创建多少个网站,只要是“电商风格”,那就都一样,只要是“博客风格”,也都一样。但是给不同企业创建网站,它们的数据肯定会不同,所以上述代码没有体香对象间的不同,只体现了共享的部分。

内部状态和外部状态

在享元对象内部并且不会随环境改变而改变的共享部分,可以成为是享元对象的内部状态;

随着环境改变而改变,不可共享的状态就是享元对象的外部状态;

享元模式可以避免大量非常相似类的开销。在程序设计中,有时需要生成大量细粒度的类实例来表示数据。如果能发现这些实例除了几个参数外基本上都是相同的,有时候就能够大幅度地减少需要实例化的类的数量。如果能把那些参数转移到类实例的外面,在方法调用时将它们传递进来,就可以通过共享大幅度地减少单个实例的数目。

享元模式Flyweight执行时所需的状态是有内部的,也可能有外部的,内部状态存储于ConcreteFlyweight对象之中,而外部状态则应该考虑有客户端对象存储或计算,当调用Flyweight对象的操作时,将该状态传递给它。

在网站的例子中,客户账号就是外部状态,应该由专门的对象来处理。

带有外部状态的版本

from abc import ABCMeta, abstractmethod

class User():
"""
用户类,网站的客户账号,是“网站类”的外部状态
"""
def __init__(self, name):
self.name = name class WebSite():
"""
网站抽象类:Flyweight类,它是所有具体享元类的超类或接口,通过这个接口,Flyweight可以接受并作用于外部状态。
"""
__metaclass__ = ABCMeta @abstractmethod
def use(self, user):
pass class ConcreteWebSite(WebSite):
"""
具体网站类:ConcreteFlyweight是继承Flyweight超类或者是想Flyweight接口,并为内部状态增加存储空间
"""
def __init__(self, name):
self.name = name def use(self, user):
print(user.name, "- 网站风格:", self.name) class WebSiteFactory():
"""
网站工厂类:FlyweightFactory是一个享元工厂,用来创建并管理Flyweight对象。它主要是用来确保合理地共享Flyweight,
当用户请求一个Flyweight时,FlyweightFactory对象提供一个已创建的实例或者创建一个(如果不存在的话)。
"""
def __init__(self):
self.flyweights = dict() def get_website(self, key):
if key not in self.flyweights:
self.flyweights[key] = ConcreteWebSite(key)
return self.flyweights[key] def main():
f = WebSiteFactory() fx = f.get_website("电商风格")
fx.use(User("贺贺")) fy = f.get_website("电商风格")
fy.use(User("曼曼")) fz = f.get_website("电商风格")
fz.use(User("云云")) fa = f.get_website("博客风格")
fa.use(User("灵灵")) fb = f.get_website("博客风格")
fb.use(User("依依")) fc = f.get_website("博客风格")
fc.use(User("灵依"))
print("网站风格总数:", len(f.flyweights)) main()
贺贺 - 网站风格: 电商风格
曼曼 - 网站风格: 电商风格
云云 - 网站风格: 电商风格
灵灵 - 网站风格: 博客风格
依依 - 网站风格: 博客风格
灵依 - 网站风格: 博客风格
网站风格总数: 2

总结

什么时候需要考虑使用享元模式呢?

  • 如果一个应用程序使用了大量的对象,而大量的这些对象造成了很大的存储开销时就应该考虑使用
  • 对象的大多数状态都是内部状态,如果可以删除对象的外部状态,那么可以用相对较少的共享对象取代很多组对象

在实际使用中,享元模式到底能达到什么效果呢?

因为使用了享元模式,所以有了共享对象,实例总数就大大减少了,如果共享的对象越多,存储节约也就越多,节约量随着共享状态的增多而增大。

需要注意的是,享元模式需要维护一个记录了系统已有的所有享元的列表,而这本身需要耗费资源,另外享元模式会使系统变得更加复杂。

[Python设计模式] 第26章 千人千面,内在共享——享元模式的更多相关文章

  1. [Python设计模式] 第7章 找人帮忙追美眉——代理模式

    github地址:https://github.com/cheesezh/python_design_patterns 题目1 Boy追求Girl,给Girl送鲜花,送巧克力,送洋娃娃. class ...

  2. python设计模式之享元模式

    python设计模式之享元模式 由于对象创建的开销,面向对象的系统可能会面临性能问题.性能问题通常在资源受限的嵌入式系统中出现,比如智能手机和平板电脑.大型复杂系统中也可能会出现同样的问题,因为要在其 ...

  3. 享元模式 FlyWeight 结构型 设计模式(十五)

    享元模式(FlyWeight)  “享”取“共享”之意,“元”取“单元”之意. 意图 运用共享技术,有效的支持大量细粒度的对象. 意图解析 面向对象的程序设计中,一切皆是对象,这也就意味着系统的运行将 ...

  4. 开年巨制!千人千面回放技术让你“看到”Flutter用户侧问题

    导语 发布app后,开发者最头疼的问题就是如何解决交付后的用户侧问题的还原和定位,是业界缺乏一整套系统的解决方案的空白领域,闲鱼技术团队结合自己业务痛点在flutter上提出一套全新的技术思路解决这个 ...

  5. Quick BI独创千人千面的行级权限管控机制

    摘要 就数据访问权限而言,阿里巴巴以“被动式授权”为主,你需要什么权限就申请什么权限.但是,在客户交流过程中,我们发现绝大多数企业都是集中式授权,尤其是面向个人的行级权限管控,管理复杂度往往呈几何增长 ...

  6. 设计模式之第12章-享元模式(Java实现)

    设计模式之第12章-享元模式(Java实现) “怎么回事,竟然出现了OutOfMemory的错误.鱼哥,来帮我看看啊.”“有跟踪错误原因么?是内存泄露么?”“不是内存泄露啊,具体原因不知道啊.对了,有 ...

  7. 浅谈Python设计模式 - 享元模式

    声明:本系列文章主要参考<精通Python设计模式>一书,并且参考一些资料,结合自己的一些看法来总结而来. 享元模式: 享元模式是一种用于解决资源和性能压力时会使用到的设计模式,它的核心思 ...

  8. 大话设计模式Python实现- 享元模式

    享元模式(Flyweight Pattern):运用共享技术有效地支持大量细粒度的对象. 下面是一个享元模式的demo: #!/usr/bin/env python # -*- coding:utf- ...

  9. 设计模式-创建型模式,python享元模式 、python单例模式(7)

    享元模式(Flyweight Pattern)主要用于减少创建对象的数量,以减少内存占用和提高性能.这种类型的设计模式属于结构型模式,它提供了减少对象数量从而改善应用所需的对象结构的方式. 享元模式尝 ...

随机推荐

  1. CodeForces 516A Drazil and Factorial 动态规划

    原文链接http://www.cnblogs.com/zhouzhendong/p/8990592.html 题目传送门 - CodeForces 516A 题意 对于一个正整数$x$,$f(x)=x ...

  2. Python re模块, xpath 用法

    1.re正则的用法总结 (1). ^ 表示以哪个字符为开头      eg:  '^g' 表示以g开头的字符串      . 表示任意字符 '^g.d'  表示以g开头第二个为任意字符,第三个为b的字 ...

  3. liunx系统虚拟机下安装tomcat9以及访问tomcat案例

    在liunx系统虚拟机下安装tomcat9 首先下载好压缩包 liunx系统环境的安装包 将其解压到虚拟机自己创建的目录下 解压命令   tar -zxvf   文件名 解压好如图所示 然后修改配置文 ...

  4. 最短路(bellman)-hdu2066

    题目链接:https://vjudge.net/problem/HDU-2066 题目描述: 代码实现: #include <cstdio> #include <cstring> ...

  5. node 简单的爬虫

    基于express爬虫, 1,node做爬虫的优势 首先说一下node做爬虫的优势 第一个就是他的驱动语言是JavaScript.JavaScript在nodejs诞生之前是运行在浏览器上的脚本语言, ...

  6. 【python】函数式编程

    No1: 函数式编程:即函数可以作为参数传递,也可以作为返回值  No2: map()函数接收两个参数,一个是函数,一个是Iterable,map将传入的函数依次作用到序列的每个元素,并把结果作为新的 ...

  7. HDU1029 Ignatius and the Princess IV (水题)

    <题目链接> 题目大意:给你一段序列,问你在这个序列中出现次数至少为 (n+1)/2 的数是哪个. 解题分析: 本题是一道水题,如果用map来做的话,就非常简单,但是另一个做法还比较巧妙. ...

  8. AM335X启动(转)

    AM335x启动   参考文件: 1.TI.Reference_Manual_1.pdf http://pan.baidu.com/s/1c1BJNtm 2.TI_AM335X.pdf http:// ...

  9. Unity容器的简单AOP与DI的应用Demo(基于asp.net mvc框架)

    转发请注明出处:https://home.cnblogs.com/u/zhiyong-ITNote/ 整个Demo是基于Controller-Service-Repository架构设计的,每一层之间 ...

  10. XamarinSQLite教程Xamarin.iOS项目中打开数据库文件

    XamarinSQLite教程Xamarin.iOS项目中打开数据库文件 以下是打开MyDocuments.db数据库的具体操作步骤: (1)将Mac电脑上的MyDocuments.db数据库移动到W ...