1.17类的设计模式

一、抽象父类

首先看一个例子specialize.py:

class Super:
    def method(self):
        print('in Super.method')

    def delegate(self):
        self.action()


class Provider(Super):
    def action(self):
        print('in Provider.action')


if __name__ == "__main__":
    x = Provider()
    x.method()
    x.delegate()

%python specialize.py

in Super.method
in Provider.action

我们来思考一下,当我们通过Provider实例调用delegate方法时,会发生两次独立的继承搜索:

  1. 在x.delegate调用一开始,Python会搜索Provider实例和类树中更上层的类对象,直到在Super中找到delegate的方法。实例x会照常传给该方法的self参数。
  2. 在Super.delegate方法中,self.action会对self以及它上层的对象发起另一次新的继承搜索。因为self引用了一个Provider的实例,所以action方法会在Provider子类中找到。

我们从delegate方法的角度来看,这个例子的父类就可以被称为抽象父类,也就是类的部分行为预期由其子类来提供。如果所预期的方法没有在子类中定义,那么当继承搜索失败的python就会引发名称未定义的异常。

class Super:
    def method(self):
        print('in Super.method')

    def delegate(self):
        self.action()


class Provider(Super):
    pass


if __name__ == "__main__":
    x = Provider()
    x.method()
    x.delegate()

%python specialize.py

Traceback (most recent call last):
  File "/Users/unity/projects/python-node/specialize.py", line 16, in <module>
    x.delegate()
  File "/Users/unity/projects/python-node/specialize.py", line 6, in delegate
    self.action()
AttributeError: 'Provider' object has no attribute 'action'
in Super.method

在python中提供特殊的语法来实现抽象父类(抽象基类)
Python3的实现:abstract.py

from abc import ABCMeta, abstractmethod


class Super(metaclass=ABCMeta):
    def delegate(self):
        self.action()
    
    @abstractmethod
    def action(self):
        pass


class Sub(Super):
    pass


if __name__=="__main__":
    x = Sub()

% python abstract.py

Traceback (most recent call last):
  File "D:/WeChatProjects/pythonCode/simplecode/abstract.py", line 18, in <module>
    x = Sub()
TypeError: Can't instantiate abstract class Sub with abstract methods action

子类中没有定义action方法就不能进行实例化操作。

from abc import ABCMeta, abstractmethod


class Super(metaclass=ABCMeta):
    def delegate(self):
        self.action()

    @abstractmethod
    def action(self):
        pass


class Sub(Super):
    def action(self):
        print("sub.action")


if __name__=="__main__":
    x = Sub()
    x.delegate()

% python abstract.py

sub.action

采用这种方式编写代码后,除非子类中的所有抽象方法都已经定义,否则带有抽象方法的类是不能进行实例化的。

二、OOP设计模式:继承。“is-a”关系。

故事:开一家披萨店,需要员工为顾客服务、准备食物等。当然,作为一名程序员,我们要高级一点,除了普通员工外决定开发一个机器人来制作披萨,出于礼貌和正确控制,我们决定把机器人做成有薪水成本的功能齐全的员工。
设计:最通用的类Employee提供共同行为,例如加薪(giveRaise)和打印(__repr__)。员工有两种,所以Employee有两个子类:Chef和Server。这两个子类都会继承work方法来打印更详细的信息。而我们的披萨机器人PizzaRobot是一种chef,也是一种Employee。我们称为“is-a”链:机器人是一个主厨,而主厨是一个员工。
以下是employees.py文件:

class Employee:
   def __init__(self, name, salary=0):
       self.name = name
       self.salary = salary

   def giveRaise(self, percent):
       self.salary = self.salary + (self.salary * percent)

   def work(self):
       print(self.name, "does stuff")

   def __repr__(self):
       return "<Employee:name={},salary={}>".format(self.name, self.salary)


class Chef(Employee):
   def __init__(self, name):
       Employee.__init__(self, name, 50000)

   def work(self):
       print(self.name, "makes food")


class Server(Employee):
   def __init__(self,name):
       Employee.__init__(self, name, 40000)

   def work(self):
       print(self.name, "interfaces with customer")


class PizzaRobot(Chef):
   def __init__(self,name):
       Chef.__init__(self, name)

   def work(self):
       print(self.name, "makes pizza")


if __name__ == "__main__":
   bob = PizzaRobot("bob")
   print(bob)
   bob.work()
   bob.giveRaise(0.20)
   print(bob)

% python employees.py

<Employee:name=bob,salary=50000>
bob makes pizza
<Employee:name=bob,salary=60000.0>

创建一个名为bob的机器人,打印bob会执行Employee.__repr__方法,而给bob加薪则会运行Employee.giveRaise方法,因为继承会在employee中找到方法。
从程序员的角度看,继承是由属性点号操作启动的,并由此触发实例、类以及任何父类中的变量名搜索。从类的设计者的角度看,继承是一种指明集合成员关系的方式:类定义了一个属性集合,可由更具体的集合(子类)继承和定制。

三、OOP设计模式:组合。“has-a”关系

故事:披萨店是一个组合对象:有一个烤炉,也有服务生和主厨这些员工。当顾客来店里下单时,店里的组件就会开始行动:服务生接单,主厨制作披萨。
设计:PizzaShop类是容器和控制器。它的构造函数创建并嵌入员工类,以及定义Oven类的实例。
以下是pizzashop.py文件:

from employees import PizzaRobot, Server


class Customer:
    def __init__(self, name):
        self.name = name

    def order(self, server):
        print(self.name, "orders from", server)

    def pay(self, server):
        print(self.name, "pays for item to", server)


class Oven:
    def bake(self):
        print("oven bakes")


class PizzaShop:
    def __init__(self):
        self.server = Server('Pat')
        self.chef = PizzaRobot('Bob')
        self.oven = Oven()

    def order(self, name):
        customer = Customer(name)
        customer.order(self.server)
        self.chef.work()
        self.oven.bake()
        customer.pay(self.server)


if __name__ == "__main__":
    scene = PizzaShop()
    scene.order("Homer")
    print("...")
    scene.order("shaggy")

% python pizzashop.py

Homer orders from <Employee:name=Pat,salary=40000>
Bob makes pizza
oven bakes
Homer pays for item to <Employee:name=Pat,salary=40000>
...
shaggy orders from <Employee:name=Pat,salary=40000>
Bob makes pizza
oven bakes
shaggy pays for item to <Employee:name=Pat,salary=40000>

在测试程序中,调用PizzaShop的order方法时,内嵌对象会按照顺序进行工作。每份工作订单都会创建一个新的Customer对象,并且把内嵌的Server对象传给Customer的方法。虽然顾客是流动的,但是服务员是披萨店的组件。
从程序员的角度看,组合涉及把其他对象嵌入容器对象内,并促使其实现容器方法。对类的设计者来说,组合是在一个问题领域中另一呈现关系的方式。但是,组合不是集合的成员关系,而是组件,也是整体的组成部分。

四、OOP设计模式:委托。“包装器”代理对象

委托通常是指控器对象内嵌其他对象,并把操作请求传递那些内嵌的对象。控制器能够负责管理类活动,例如记录日志和验证访问,为接口组件添加额外步骤,或监视活跃实例。
在python中,通常使用__getattr__方法钩子来实现委托。因为这个运算符重载方法会拦截对不存在属性的访问,所以包装器类可以使用__getattr__方法来把任意的访问转发给被包装的对象。因为该方法允许一般性地路由属性请求,所以包装器类保有被包装对象的接口,并可以添加其他的附加操作。

class Wrapper:
    def __init__(self, object):
        self.wrapped = object

    def __getattr__(self, attrname):
        print("Trace:" + attrname)
        return getattr(self.wrapped, attrname)


if __name__ == "__main__":
    x = Wrapper([1, 2, 3])
    x.append(4)
    print(x.wrapped)

% python trace.py

Trace:append
[1, 2, 3, 4]

对x.append(4)操作会被__getattr__拦截,getattr内置函数——通过名称字符串获取被包装对象的属性:getattr(X,N)就像X.N,只不过这里N时在运行时求值为字符串的表达式,而不是变量。

小结

委托:把对象包装在代理类内
组合:控制内嵌的对象
继承:从其他类中获取行为

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,686评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,668评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,160评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,736评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,847评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,043评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,129评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,872评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,318评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,645评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,777评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,470评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,126评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,861评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,095评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,589评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,687评论 2 351