Mixin 扫盲班 -- 赖勇浩(http://blog.csdn.net/lanphaday)

CPUG联盟::

CPUG::门户plone

BPUG

SPUG

ZPUG

SpreadPython Python宣传

声明:本文适合初中级Python程序员阅读,另外本文措词可能会导致阅读者不适,敬请PG。

1. 引子

因为出现了Mixin这样一个东西呀,就像C++社区很多人谈论template一样啊,Python社区也很多会谈论Mixin的(以后会的,嘻嘻),所以我就来凑凑热闹了。

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

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

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

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

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

1.1. 以此为开始

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

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

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

1.1.1. 最初

最初,你可能这样设计:

class Fruit(object):
 pass

现在你打算实现最受顾客欢迎的苹果:

   1 class Apple(Fruit):
   2  def is_gift_fruit(self):
   3   return True

接下来让我们实现梨子吧:

   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

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,就是这么简单,以致我无法再说出任何言语,因为我觉得上面的代码已经完整地表达了我想要表达的思想。

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

1.1.6. 除此之外

当然有啦!其实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

鸣谢
在本文成文过程中,
沈崴(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()

2.2. 沈崴补遗

From: 沈崴 <[email protected]>
Date: Mon, 18 Jun 2007 05:31:36 -0000
Local: Mon, Jun 18 2007 1:31 pm
Subject: Re: Mixin 扫盲班

勇浩兄之前和我聊过, 他感兴趣的 MixIn 好像是下面这个样子的。

   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() 

::-- ZoomQuiet [2007-06-18 05:00:53]

FlyintoMixin (last edited 2010-05-11 09:55:52 by ZoomQuiet)