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. 007 linux环境下的伪分布式环境搭建

    本文的配置环境是VMware10+centos2.5. 在学习大数据过程中,首先是要搭建环境,通过实验,在这里简短粘贴书写关于自己搭建大数据伪分布式环境的经验. 如果感觉有问题,欢迎咨询评论. 零:下 ...

  2. yield与yield from

    yield 通过yield返回的是一个生成器,yield既可以产出值又可以生成值,yield可以用next()来启动生成器,同时可以用send向生成器传递值:在初次启动生成器时,需调用next()或s ...

  3. 20165235 实验一 Java开发环境的熟悉

    20165235 实验一 Java开发环境的熟悉 课程:JAVA程序设计 姓名:祁瑛 学号:20165235 指导老师:娄嘉鹏 实验日期: 2018.4.2 实验内容:java开发环境的熟悉 一,实验 ...

  4. TFTP Server的搭建和使用(Fedora)

    一.tftp服务的安装 yum install xinetd tftp tftp-server 表示我安装的已经是最新版本的tftp服务了,不用更新了. 二.配置tftp服务的相关参数(没有就创建新的 ...

  5. 队列queue的一些操作

    1. q = queue.Queue(5) 实例化,5为队列长度 2. q.put("haha") 将数据加入队列,计数器+1 3. q.get() 取出数据,计数器不变 4. q ...

  6. 在Idea中添加自定义补全代码设置(Main方法为例)

    一.打开File->setting->Editor->Live Templates 二.注意右边有“+”.“-”号,点击+号选择第二个Template Group...,并输入新组名 ...

  7. SpringBoot使用事务

    事务是很多项目中需要注意的东西,有些场景如果没有加事务控制就会导致一些脏数据进入数据库,本文简单介绍SpringBoot怎样使用事务. 本文使用的是之前整合JPA的文章,具体可以参考 传送门. 无论是 ...

  8. PAT (Advanced Level) Practise 1004 解题报告

    GitHub markdownPDF 问题描述 解题思路 代码 提交记录 问题描述 Counting Leaves (30) 时间限制 400 ms 内存限制 65536 kB 代码长度限制 1600 ...

  9. 按字典序依次打印只由1~n组成的n位数

    //我的dfs入门.将1~n一次填入数组然后打印. #include<stdio.h> #include<string.h> ]; ]; void dfs(int,int); ...

  10. [OC] 杂项

    使用JSONModel的一个好处是,可以防止 [数据是NSNULL的时候,OC无法直接通过if(XX)来判空 ]引起的错误. 字符串与字符串对比不要使用 str1 != str2 这种写法,而用 ![ ...