这里讨论Python的OOP的设计问题,也就是如何使用类来对有用的对象进行建模。将编写Python中常用的OOP设计模式,例如,继承、组合、委托和工厂。另外介绍一些类设计的概念,例如伪私有属性、多继承等。
================================================================================
Python和OOP
Python的OOP实现与Java类似,可以概括为三个概念:
1.【继承】继承是基于Python中的属性查找(在X.name表达式中)
2.【多态】在X.method方法中,method的意义取决于X的类型
3.【封装】方法和运算符实现行为,数据隐藏默认是一种惯例
================================================================================
OOP和继承:“是一个”关系
举例说明:披萨店团队可以通过文件employees.py中的四个类来定义。最通用的类Employee提供共同行为,例如,加薪(giveRaise)和打印(__repr__)。员工有两种,所以Employee有两个子类:Chef和Server(厨师和服务员)。这两个子类都会覆盖继承的work方法来打印更具体的信息。最后,披萨机器人是由更具体的类来模拟:PizzaRobot是一种Chef,也是一种Employee。以OOP术语来看,这些关系为“是一个”(is-a)链接:机器人是一个主厨,而主厨是一个员工。代码编写如下:
- #File 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 = %s , salary = %s>' %( 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(. 2 )
- print (bob)
- print ()
- for klass in Employee,Chef,Server,PizzaRobot:
- obj = klass(klass.__name__)
- obj.work()
- <Employee : name = bob , salary = 50000 >
- bob makes pizza
- <Employee : name = bob , salary = 60000.0 >
- Employee does stuff
- Chef makes food
- Server interfaces with customer
- PizzaRobot makes pizza
- def __repr__( self ):
- return '<%s : name = %s , salary = %s>' %( self .__class__.__name__, self .name, self .salary)
- <PizzaRobot : name = bob , salary = 50000 >
- bob makes pizza
- <PizzaRobot : name = bob , salary = 60000.0 >
- Employee does stuff
- Chef makes food
- Server interfaces with customer
- PizzaRobot makes pizza
OOP和组合:“有一个”关系
组合涉及把其他对象嵌入容器对象内,并使用其容器方法。组合反映了各组成部分之间的关系,通常称为“有一个”(has-a)关系。
接着上边的例子,既然我们已经有了员工,就把他们放到我们披萨店。披萨店是一个组合对象,有个烤炉,也有服务员和主厨这些员工。当顾客来店里下单时,店里的组件就会开始行动:服务员接单,主厨制作披萨,等等。看下边的例子pizzashop.py.
- #File 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' ) #一个名叫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' ) #模拟Homer的订单
- print ( '...' )
- scene.order( 'Shaggy' ) #模拟Shaggy的订单
- Homer orders from <Server : name = Pat , salary = 40000 >
- Bob makes pizza
- oven bakes
- Homer pays for item to <Server : name = Pat , salary = 40000 >
- ...
- Shaggy orders from <Server : name = Pat , salary = 40000 >
- Bob makes pizza
- oven bakes
- Shaggy pays for item to <Server : name = Pat , salary = 40000 >
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)
你可以使用这个模块包装类的做法,管理任何带有属性的对象的存取:列表、字典甚至是类和实例。在这里,wrapper类只是在每个属性读取时打印跟踪消息,并把属性请求委托给嵌入的对象wrapped
- >>> x = wrapper([ 1 , 2 , 3 ])
- >>> x.append( 4 )
- Trace: append
- >>> x.wrapped
- [ 1 , 2 , 3 , 4 ]
- >>>
- >>> x = wrapper({ 'a' : 1 , 'b' : 2 })
- >>> x.keys()
- Trace: keys
- dict_keys([ 'b' , 'a' ])
================================================================================
类的伪私有属性——变量名压缩
Python支持变量名压缩的概念,让类内的某些变量局部化,但名称压缩并无法阻止类外部代码对它的读取,这种功能主要是为了避免实例内的命名空间的冲突,而不是限制变量名的读取。因此,压缩的变量名最好称为“伪私有”。
当然,伪私有属性是高级且完全可选的功能,但由于你可能在其他人的代码中看见这个功能,所以即使不用,多少还得留意。
【变量名压缩的工作方式是这样的:class语句内开头有两个下划线,但结尾没有两个下划线的变量名,会自动扩张,从而包含了所在类的名称。】
例如,像Spam类内__X这样的变量名会自动变成_Spam__X:原始的变量名会在头部加入一个下划线,然后是所在类的名称。因为修改后的变量名包含了所在类的名称,相当于变得独特。不会和同一层次中其他类所创建的类似变量名冲突。
变量名压缩只发生在class语句内,而且只针对开头有两个下划线的变量名。
为什么要使用伪私有属性,举例如下:
一个程序员编写的一个类,他认为属性X在该实例中:
- class C1:
- def meth1( self ): self .X = 88
- def meth2( self ): print ( self .X)
- class C2:
- def metha( self ): self .X = 99
- def methb( self ): print ( self .X)
- class C3(C1,C2):...
- I = C3()
为了保证属性会属于使用它的类,可在类中任何地方使用,将变量名前加上两个下划线:
- >>> class C1:
- def meth1( self ): self .__X = 88
- def meth2( self ): print ( self .__X)
- >>> class C2:
- def metha( self ): self .__X = 99
- def methb( self ): print ( self .__X)
- >>> class C3(C1,C2): pass
- >>> I = C3()
- >>> I.meth1();I.metha()
- >>> print (I.__dict__)
- { '_C1__X' : 88 , '_C2__X' : 99 }
但这并不是真正的私有,你依然可以使用扩张后的变量名,例如,I._C1__X = 77
================================================================================
多重继承:“混合”类
在class语句中,首行括号内可以列出一个以上的超类。当这么做时,就是在使用所谓的【多重继承】。
搜索属性时,Python会由左至右搜索首行中的超类,直到找到相符者。
通常来说,多重继承是建模属于一个集合以上的对象的好办法。例如,一个人可以是工程师、作家、音乐家等,因此,可继承这些集合的特性。
================================================================================
类是对象:通用对象的工厂
- >>> def factory(aClass,*args):
- return aClass(*args)
- >>> class Spam:
- def doit( self ,message):
- print (message)
- >>> class Person:
- def __init__( self ,name,job):
- self .name = name
- self .job = job
- >>> obj1 = factory(Spam)
- >>> obj2 = factory(Person, 'Guido' , 'gurr' )
然后定义了两个类,并将其传给factory函数以产生两者的实例。这就是Python中编写工厂函数所需要做的事。它适用于任何类以及任何构造函数参数。
这类工厂可以将代码和动态配置对象的构造细节隔离开,也许以后会用到。