`
yezongbo
  • 浏览: 30012 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

FlyintoMixin

阅读更多
1. 引子
嗯,为什么要谈Mixin啊?
因为出现了Mixin这样一个东西呀,就像C++社区很多人谈论template一样啊,Python社区也很多会谈论Mixin的(以后会的,嘻嘻),所以我就来凑凑热闹了。

嗯,为什么要Mixin呀?
这个,基本上已经是我这篇文章里要讲的东西了,所以,我会用本文的大部分篇幅来回答你这个超有深度的问题。现在,就开始吧~

小时候,我家开百货店,当然,也兼营水果蔬菜啊。

小时候的事,总是特别有趣,也特别的有意义,所以今天我们就以水果店开始吧~

记得,以前很多人买水果的时候,都会问我妈一个问题,就是价格了啦~但还有两个问题也经常问到哦,就是送人应该买什么水果和什么水果可以用来送人?

嗯,年青人大多都不懂这些礼节。送水果也是很讲兆头的,比如梨和香蕉一般不用来送人,因为它们意味着离别和焦躁哦;而苹果和桔子就很受欢迎,因为它们意味着平安和吉利哦~


1.1. 以此为开始
那,这跟Python有什么关系吗?
当然有啦,不然我扯那么多皮干嘛咧?现在,国产凌凌漆接到一个任务,要求他为一个水果连锁店开发一套软件;显然这个不关心国计民生这些鸡毛蒜皮的小事的武夫是搞不定这项艰巨任务的了,他就找到了你。

通过调研,客户发现两件事实:一是现在的年青人还是不懂送人应该买什么水果和什么水果可以用来送人这两个问题;二是水果连锁店的营业员100%都是年青人,他们中大部分人也不懂。

所以,客户要求在软件中必须提供一个这样的功能--可以查询一种水果是否适宜送人。


1.1.1. 最初
最初,你可能这样设计:

class Fruit(object):
pass
把fruit类作为一切水果的基类,嗯,这相当明智。代码中去除了一些无需关注的代码,如价格、产地等。
现在你打算实现最受顾客欢迎的苹果:

切换行号显示
   1 class Apple(Fruit):
   2  def is_gift_fruit(self):
   3   return True
同样的,我又去除了一些无需关注的代码,并且打算在接下来的行文中不再提醒这一点。
Apple是一种Fruit。所以上面的实现挺符合OO的原则。
接下来让我们实现梨子吧:

切换行号显示
   1 class Pear(Fruit):
   2  def is_gift_fruit(self):
   3   return False
解决问题了。如果水果连锁店只卖苹果和梨子两种水果的话。
可惜,需求很多,你还要实现桔子和香蕉呢。你写下了这几行代码:

切换行号显示
   1 class Orange(Fruit):
   2  def is_gift_fruit(self):
   3   return True
   4 class Banana(Fruit):
   5  def is_gift_fruit(self):
   6   return False
好臭啊,代码的坏味道!

类Apple和类Orange除了类名不同,几乎是完全重复的代码;类Pear和类Banana也是一样。 更进一层的说,这四个类都差不多啊,所以我们有必要重构一下已有代码,改善它们的设计。


1.1.2. 改善已有代码
阅读代码,你可以发现水果只分两类:一类是可以作为礼品的,一类是不可以的。所以希望可以这样设计:

  Fruit
  / \
GiftFruit NotGiftFruit
/ \ / \
Apple    Orange Pear Banana
嗯,加了两个中间类,看起来不错:

切换行号显示
   1 class GiftFruit(Fruit):
   2  def is_gift_fruit(self):
   3   return True
   4 class NotGiftFruit(Fruit):
   5  def is_gift_fruit(self):
   6   return False
   7 class Apple(GiftFruit):pass
   8 class Orange(GiftFruit):pass
   9 class Pear(NotGiftFruit):pass
  10 class Banana(NotGiftFruit):pass
好啦,看上去很不错哦,代码精简了不少,任务完成~


1.1.3. 新的烦恼
接下来我们来完成另一项功能:提供水果食用方法咨询。 表笑这个需求,这是真实的市场需求。比如相当部分一辈子生活在北方的朋友就没有吃过龙眼荔枝香蕉;而南方虽然水果丰富,但不知道山竹榴莲等洋水果的也大有人在。我们这个水果连锁店业务简单,水果的食用方法也只分两种:一种是剥皮的,如桔子和香蕉;另一种是削皮的,如苹果和梨子。让我们修改原有的设计:

   Fruit
   / \
  GiftFruit NotGiftFruit
  / \ /  \
PareG...   HuskG... PareNot... HuskNot...
/  / /  /
Apple     Orange  Pear  Banana
不得已,我们添加了四个类:

切换行号显示
   1 class PareGiftFruit(GiftFruit):
   2  def eat_method(self):
   3   return 'Pare'
   4 class HustGiftFruit(GiftFruit):
   5  def eat_method(self):
   6   return 'Husk'
   7 class PareNotGiftFruit(NotGiftFruit):
   8  def eat_method(self):
   9   return 'Pare'
  10 class HuskNotGiftFruit(NotGiftFruit):
  11  def eat_method(self):
  12   return 'Husk'
怎么这四个类这么像啊?汗。。。。
先忍忍,把AOPB四种水果的实现改改:

切换行号显示
   1 class Apple(PareGiftFruit):pass
   2 class Orange(HuskGiftFruit):pass
   3 class Pear(PareNotGiftFruit):Pass
   4 class Banana(HuskNotGiftFruit):pass
我已经忍无可忍了。这个设计不仅仅又引入了好不容易消除的重复代码,而且还修改了AOPB这四个类的实现。这种设计的扩展性也不好,如果以后要提供水果的其它特点,比如是进口水果还是国产水果。天啊,这还了得!加上这个特性,我要实现NativePareGiftFruit、NativeHuskGiftFruit等类共8个(2的三次方)啊。水果的特征多得很,随便算算可能超过16种啊,65536个类?叫我去死吧~单是长达16个单词的类名我就崩溃了!

现在,你们都应该意识到这种实现方法实在是一种龌龊的设计了。那,我们应该怎么样设计呢?

1.1.4. Pythonic的方案
该是Mixin出场的时候了! 先来看看Mixin的实现吧:

切换行号显示
   1 class Fruit(object):
   2  pass
   3 class GiftMixin(object):
   4  def is_gift_fruit(self):
   5   return True
   6 class NotGiftMixin(object):
   7  def is_gift_fruit(self):
   8   return False
   9 class PareMixin(object):
  10  def eat_method(self):
  11   return 'Pare'
  12 class HuskMixin(object):
  13  def eat_method(self):
  14   return 'Husk'
  15 class Apple(GiftMixin, PareMixin, Fruit):pass
  16 class Orange(GiftMixin, HuskMixin, Fruit):pass
  17 class Pear(NotGiftMixin, PareMixin, Fruit):pass
  18 class Banana(NotGiftMixin, HuskMixin, Fruit):pass
编码完成!这就是Mixin,就是这么简单,以致我无法再说出任何言语,因为我觉得上面的代码已经完整地表达了我想要表达的思想。

注意, 因为 Python 里面多重继承时如果被调用的成员函数只存在于父类中,则按类声明的父类从左到右查找调用的, 所以主类被放在右边, MixIn 被放在左边,才能正确地调用到Mixin的成员函数。


1.1.5. Mixin的好处
Mixin的好处是可以为主类(如Fruit)添加任意多的Mixin来实现多态,比如刚才说的水果有进口和国产两个特征,现在相当容易实现:

切换行号显示
   1 class NativeMixin(object):
   2  def Locality(self):
   3   return 'Native'
   4 class ForeignMixin(object):
   5  def Locality(self):
   6   return 'Foreign'
   7 class Apple(ForeignMixin, GiftMixin, PareMixin, Fruit):pass #进口红富士
   8 class Orange(NativeMixin, GiftMixin, HuskMixin, Fruit):pass
   9 class Pear(NativeMixin, NotGiftMixin, PareMixin, Fruit):pass
  10 class Banana(NativeMixin, NotGiftMixin, HuskMixin, Fruit):pass
简单多了,只加了两个类,对AOPB的实现也只是增加了一个基类(增加总是胜过修改)。
利用Mixin我们还可以增加无数总特征,而无需对已有代码作太大改动。
另外,我们还获得了可重用性。比如NativeMixin和ForeignMixin跟主类Human结合,可以做出国人和老外两个类哦~也许水果连锁店软件以后会考虑记录关于客户是否外国人的信息呢。


1.1.6. 除此之外
这时候,你可能会说:水果连锁店软件只是你杜撰的一个项目,Mixin有什么实际用处吗?
当然有啦!其实Mixin并不是什么高阶的Python技巧,早有就很多开源项目使用这个技巧了,典型的,比如Python项目啊!在Python自带的SocketServer.py里就应用了Mixin来实现基于进程和基于线程的两种TCP/UDP服务模型,在Tkinter和Python的其它模块也可以见到它的踪迹,如果你留意的话。


# SocketServer.py 里的Mixin
class ForkingUDPServer(ForkingMixIn, UDPServer): pass
class ForkingTCPServer(ForkingMixIn, TCPServer): pass
class ThreadingUDPServer(ThreadingMixIn, UDPServer): pass
class ThreadingTCPServer(ThreadingMixIn, TCPServer): pass
确切来说,我对Mixin来实现的水果连锁店的实现仍然相当不满意,但如果我们想要足够面向对象,也就基本上只能接受如此解决方案了。
如果有一天你不能忍受每增加一种特征你就必须编写N(N>=2)个Mixin,然后都必须给已经存在的AOPB代码增加一个基类(想想,水果店卖的可不止四种水果,你会更头大),那,就考虑把OO抛弃吧!


鸣谢
在本文成文过程中,
沈崴(http://eishn.blog.163.com)给我很大帮助,特此鸣谢。

2. 反馈

2.1. 原稿泄漏版本

切换行号显示
   1 #!/usr/bin/env python
   2 # 仿黄毅大师的版本
   3
   4 class Instance:
   5        def __init__(self, *args, **kw):
   6                self.__dict__.update(kw)
   7                for m in args:
   8                        m(self)
   9
  10        def config(self, *args, **kw):
  11                self.__dict__.update(kw)
  12                for m in args:
  13                        m(self)
  14
  15                return self
  16
  17 def i_am_gift(self):
  18        # self.is_gift = True # Why not
  19
  20        self.is_gift = lambda: True
  21
  22 def i_am_not_gift(self):
  23        self.is_gift = lambda: False
  24
  25 def eatable(eat_method = ''):
  26        def config_eat_method(self):
  27                self.eat_method = lambda: eat_method
  28
  29        return config_eat_method
  30
  31 def Apple():
  32        return Instance(i_am_gift, eatable('Bare'))
  33
  34 def Banana():
  35        return Instance(i_am_not_gift, eatable('Hust'))
  36
  37 if __name__ == '__main__':
  38        apple = Apple()
  39        print apple.is_gift()
  40
  41        apple.config(i_am_not_gift)
  42        print apple.is_gift()
  43
  44        banana = Banana()
  45        print apple.eat_method()
  46        print banana.eat_method()
分享到:
评论

相关推荐

    基于matlab实现实现了基于项目的协同过滤代码,MATLAB实现.rar

    基于matlab实现实现了基于项目的协同过滤代码,MATLAB实现.rar

    各地区年末城镇登记失业人员及失业率.xls

    数据来源:中国劳动统计NJ-2023版

    企业固定资产信息管理系统设计与实现.doc

    企业固定资产信息管理系统设计与实现.doc

    node-v11.14.0-darwin-x64.tar.xz

    Node.js,简称Node,是一个开源且跨平台的JavaScript运行时环境,它允许在浏览器外运行JavaScript代码。Node.js于2009年由Ryan Dahl创立,旨在创建高性能的Web服务器和网络应用程序。它基于Google Chrome的V8 JavaScript引擎,可以在Windows、Linux、Unix、Mac OS X等操作系统上运行。 Node.js的特点之一是事件驱动和非阻塞I/O模型,这使得它非常适合处理大量并发连接,从而在构建实时应用程序如在线游戏、聊天应用以及实时通讯服务时表现卓越。此外,Node.js使用了模块化的架构,通过npm(Node package manager,Node包管理器),社区成员可以共享和复用代码,极大地促进了Node.js生态系统的发展和扩张。 Node.js不仅用于服务器端开发。随着技术的发展,它也被用于构建工具链、开发桌面应用程序、物联网设备等。Node.js能够处理文件系统、操作数据库、处理网络请求等,因此,开发者可以用JavaScript编写全栈应用程序,这一点大大提高了开发效率和便捷性。 在实践中,许多大型企业和组织已经采用Node.js作为其Web应用程序的开发平台,如Netflix、PayPal和Walmart等。它们利用Node.js提高了应用性能,简化了开发流程,并且能更快地响应市场需求。

    node-v8.9.1-sunos-x64.tar.xz

    Node.js,简称Node,是一个开源且跨平台的JavaScript运行时环境,它允许在浏览器外运行JavaScript代码。Node.js于2009年由Ryan Dahl创立,旨在创建高性能的Web服务器和网络应用程序。它基于Google Chrome的V8 JavaScript引擎,可以在Windows、Linux、Unix、Mac OS X等操作系统上运行。 Node.js的特点之一是事件驱动和非阻塞I/O模型,这使得它非常适合处理大量并发连接,从而在构建实时应用程序如在线游戏、聊天应用以及实时通讯服务时表现卓越。此外,Node.js使用了模块化的架构,通过npm(Node package manager,Node包管理器),社区成员可以共享和复用代码,极大地促进了Node.js生态系统的发展和扩张。 Node.js不仅用于服务器端开发。随着技术的发展,它也被用于构建工具链、开发桌面应用程序、物联网设备等。Node.js能够处理文件系统、操作数据库、处理网络请求等,因此,开发者可以用JavaScript编写全栈应用程序,这一点大大提高了开发效率和便捷性。 在实践中,许多大型企业和组织已经采用Node.js作为其Web应用程序的开发平台,如Netflix、PayPal和Walmart等。它们利用Node.js提高了应用性能,简化了开发流程,并且能更快地响应市场需求。

    node-v12.10.0-linux-armv7l.tar.xz

    Node.js,简称Node,是一个开源且跨平台的JavaScript运行时环境,它允许在浏览器外运行JavaScript代码。Node.js于2009年由Ryan Dahl创立,旨在创建高性能的Web服务器和网络应用程序。它基于Google Chrome的V8 JavaScript引擎,可以在Windows、Linux、Unix、Mac OS X等操作系统上运行。 Node.js的特点之一是事件驱动和非阻塞I/O模型,这使得它非常适合处理大量并发连接,从而在构建实时应用程序如在线游戏、聊天应用以及实时通讯服务时表现卓越。此外,Node.js使用了模块化的架构,通过npm(Node package manager,Node包管理器),社区成员可以共享和复用代码,极大地促进了Node.js生态系统的发展和扩张。 Node.js不仅用于服务器端开发。随着技术的发展,它也被用于构建工具链、开发桌面应用程序、物联网设备等。Node.js能够处理文件系统、操作数据库、处理网络请求等,因此,开发者可以用JavaScript编写全栈应用程序,这一点大大提高了开发效率和便捷性。 在实践中,许多大型企业和组织已经采用Node.js作为其Web应用程序的开发平台,如Netflix、PayPal和Walmart等。它们利用Node.js提高了应用性能,简化了开发流程,并且能更快地响应市场需求。

    基于VB实现的学生成绩管理系统(源代码+系统+开题报告+答辩PPT).zip

    【作品名称】:基于VB实现的学生成绩管理系统(源代码+系统+开题报告+答辩PPT) 【适用人群】:适用于希望学习不同技术领域的小白或进阶学习者。可作为毕设项目、课程设计、大作业、工程实训或初期项目立项。

    银行信贷管理系统设计与实现-(毕业设计)1.docx

    银行信贷管理系统设计与实现-(毕业设计)1.docx

    基于VB实现的银行代扣代发工资系统(源代码+系统+开题报告).zip

    【作品名称】:基于VB实现的银行代扣代发工资系统(源代码+系统+开题报告) 【适用人群】:适用于希望学习不同技术领域的小白或进阶学习者。可作为毕设项目、课程设计、大作业、工程实训或初期项目立项。

    (更新至2022年)全国镇分年龄、性别的人口数.xls

    数据来源:中国人口与就业统计NJ-2023版

    基于Java的考试管理系统

    java,大学课后作业

    数据更新至2020年分地区发电装机容量增速(风电).xls

    数据来源:中国电力统计NJ-2021版

    基于VB实现的网上餐饮管理系统设计(论文+源代码+开题报告+英文文献).zip

    【作品名称】:基于VB实现的网上餐饮管理系统设计(论文+源代码+开题报告+英文文献) 【适用人群】:适用于希望学习不同技术领域的小白或进阶学习者。可作为毕设项目、课程设计、大作业、工程实训或初期项目立项。

    基于matlab实现烟花算法进行函数最小-最大值寻优,对十多个测试函数进行了寻优,效果良好,代码附带详细说明.rar

    基于matlab实现烟花算法进行函数最小_最大值寻优,对十多个测试函数进行了寻优,效果良好,代码附带详细说明.rar

    数据更新至2020年分地区单机6000千瓦及以上 水力发电机组分类情况(合计).xls

    数据来源:中国电力统计NJ-2021版

    基于VB实现的商场管理系统设计(源代码+系统).zip

    【作品名称】:基于VB实现的商场管理系统设计(源代码+系统) 【适用人群】:适用于希望学习不同技术领域的小白或进阶学习者。可作为毕设项目、课程设计、大作业、工程实训或初期项目立项。

    (更新至2022年)全国按年龄、性别分的就业人员受教育程度构成.xls

    数据来源:中国劳动统计NJ-2023版

    数据更新至2020年分地区6000千瓦及以上电厂发电量(太阳能发电).xls

    数据来源:中国电力统计NJ-2021版

    数据更新至2020年分地区发电装机容量增速(合计).xls

    数据来源:中国电力统计NJ-2021版

    各地区劳动能力鉴定情况(2022年).xls

    数据来源:中国劳动统计NJ-2023版

Global site tag (gtag.js) - Google Analytics