接口类
继承有两种用途:
一:继承基类的方法,并且做出自己的改变或者扩展(代码重用)
二:声明某个子类兼容于某基类,定义一个接口类Interface,接口类中定义了一些接口名(就是函数名)且并未实现接口的功能,子类继承接口类,并且实现接口中的功能
1 # 一:这样不好,我要统一一下支付的规则. 2 3 class QQpay: 4 def pay(self,money): 5 print('使用qq支付%s元' % money) 6 7 class Alipay: 8 def pay(self,money): 9 print('使用阿里支付%s元' % money) 10 11 a = Alipay() 12 a.pay(100) 13 14 b = QQpay() 15 b.pay(200) 16 17 # 二,统一支付的规则 归一化设计,统一 pay接口 18 class QQpay: 19 def pay(self,money): 20 print('使用qq支付%s元' % money) 21 22 class Alipay: 23 def pay(self,money): 24 print('使用阿里支付%s元' % money) 25 26 def pay(obj,money): 27 obj.pay(money) 28 29 a = Alipay() 30 b = QQpay() 31 32 pay(a,100) 33 pay(b,200) 34 35 # 三,但是,来了一个野生程序员,他不知道你的约定俗成的规则,就会出问题 36 37 class QQpay: 38 def pay(self,money): 39 print('使用qq支付%s元' % money) 40 41 class Alipay: 42 def pay(self,money): 43 print('使用阿里支付%s元' % money) 44 45 class Wechatpay: 46 def fuqian(self,money): 47 print('使用微信支付%s元' % money) 48 49 def pay(obj,money): 50 obj.pay(money) 51 52 a = Alipay() 53 b = QQpay() 54 55 pay(a,100) 56 pay(b,200) 57 58 c = Wechatpay() 59 c.fuqian(300) 60 61 # 四,解决方式 62 # 定义一个父类,什么都不写,只是要求继承我的所有类有一个pay方法,这样就制定了一个规范,这就叫做接口类,后者抽象类. 63 class Payment: 64 def pay(self):pass 65 66 class QQpay(Payment): 67 def pay(self,money): 68 print('使用qq支付%s元' % money) 69 70 class Alipay(Payment): 71 def pay(self,money): 72 print('使用阿里支付%s元' % money) 73 74 class Wechatpay(Payment): 75 def fuqian(self,money): 76 print('使用微信支付%s元' % money) 77 78 79 def pay(obj,money): 80 obj.pay(money) 81 82 a = Alipay() 83 b = QQpay() 84 85 pay(a,100) 86 pay(b,200) 87 88 c = Wechatpay() 89 c.fuqian(300) 90 91 #五,他还是不知道看你这些都继承了一个类,所以你要制定一个规范,强制他执行. 92 # 创建一个规范 93 from abc import ABCMeta,abstractmethod 94 class Payment(metaclass=ABCMeta): # 抽象类 接口类 规范和约束 metaclass指定的是一个元类 95 @abstractmethod 96 def pay(self):pass # 抽象方法 97 98 class Alipay(Payment): 99 def pay(self,money): 100 print('使用支付宝支付了%s元'%money) 101 102 class QQpay(Payment): 103 def pay(self,money): 104 print('使用qq支付了%s元'%money) 105 106 class Wechatpay(Payment): 107 # def pay(self,money): 108 # print('使用微信支付了%s元'%money) 109 def recharge(self):pass 110 111 def pay(a,money): 112 a.pay(money) 113 114 a = Alipay() 115 a.pay(100) 116 pay(a,100) # 归一化设计:不管是哪一个类的对象,都调用同一个函数去完成相似的功能 117 q = QQpay() 118 q.pay(100) 119 pay(q,100) 120 w = Wechatpay() 121 pay(w,100) # 到用的时候才会报错 122 123 124 125 # 抽象类和接口类做的事情 :建立规范 126 # 制定一个类的metaclass是ABCMeta, 127 # 那么这个类就变成了一个抽象类(接口类) 128 # 这个类的主要功能就是建立一个规范 129 130 接口类示例
实践中,继承的第一种含义意义并不很大,甚至常常是有害的。因为它使得子类与基类出现强耦合。
继承的第二种含义非常重要。它又叫“接口继承”。
接口继承实质上是要求“做出一个良好的抽象,这个抽象规定了一个兼容接口,使得外部调用者无需关心具体细节,可一视同仁的处理实现了特定接口的所有对象”——这在程序设计上,叫做归一化。
归一化使得高层的外部使用者可以不加区分的处理所有接口兼容的对象集合——就好象linux的泛文件概念一样,所有东西都可以当文件处理,不必关心它是内存、磁盘、网络还是屏幕(当然,对底层设计者,当然也可以区分出“字符设备”和“块设备”,然后做出针对性的设计:细致到什么程度,视需求而定)。
依赖倒置原则: 高层模块不应该依赖低层模块,二者都应该依赖其抽象;抽象不应该应该依赖细节;细节应该依赖抽象。换言之,要针对接口编程,而不是针对实现编程
在python中根本就没有一个叫做interface的关键字,上面的代码只是看起来像接口,其实并没有起到接口的作用,子类完全可以不用去实现接口 ,如果非要去模仿接口的概念,可以借助第三方模块:
http://pypi.python.org/pypi/zope.interface
twisted的twistedinternetinterface.py里使用zope.interface
文档https://zopeinterface.readthedocs.io/en/latest/
设计模式:https://github.com/faif/python-patterns
1 接口提取了一群类共同的函数,可以把接口当做一个函数的集合。 2 3 然后让子类去实现接口中的函数。 4 5 这么做的意义在于归一化,什么叫归一化,就是只要是基于同一个接口实现的类,那么所有的这些类产生的对象在使用时,从用法上来说都一样。 6 7 归一化,让使用者无需关心对象的类是什么,只需要的知道这些对象都具备某些功能就可以了,这极大地降低了使用者的使用难度。 8 9 比如:我们定义一个动物接口,接口里定义了有跑、吃、呼吸等接口函数,这样老鼠的类去实现了该接口,松鼠的类也去实现了该接口,由二者分别产生一只老鼠和一只松鼠送到你面前,即便是你分别不到底哪只是什么鼠你肯定知道他俩都会跑,都会吃,都能呼吸。 10 11 再比如:我们有一个汽车接口,里面定义了汽车所有的功能,然后由本田汽车的类,奥迪汽车的类,大众汽车的类,他们都实现了汽车接口,这样就好办了,大家只需要学会了怎么开汽车,那么无论是本田,还是奥迪,还是大众我们都会开了,开的时候根本无需关心我开的是哪一类车,操作手法(函数调用)都一样
抽象类
什么是抽象类
与java一样,python也有抽象类的概念但是同样需要借助模块实现,抽象类是一个特殊的类,它的特殊之处在于只能被继承,不能被实例化
为什么要有抽象类
如果说类是从一堆对象中抽取相同的内容而来的,那么抽象类就是从一堆类中抽取相同的内容而来的,内容包括数据属性和函数属性。
比如我们有香蕉的类,有苹果的类,有桃子的类,从这些类抽取相同的内容就是水果这个抽象的类,你吃水果时,要么是吃一个具体的香蕉,要么是吃一个具体的桃子。。。。。。你永远无法吃到一个叫做水果的东西。
从设计角度去看,如果类是从现实对象抽象而来的,那么抽象类就是基于类抽象而来的。
从实现角度来看,抽象类与普通类的不同之处在于:抽象类中有抽象方法,该类不能被实例化,只能被继承,且子类必须实现抽象方法。这一点与接口有点类似,但其实是不同的,即将揭晓答案
在python中实现抽象类
抽象类与接口类
抽象类的本质还是类,指的是一组类的相似性,包括数据属性(如all_type)和函数属性(如read、write),而接口只强调函数属性的相似性。
抽象类是一个介于类和接口直接的一个概念,同时具备类和接口的部分特性,可以用来实现归一化设计
在python中,并没有接口类这种东西,即便不通过专门的模块定义接口,我们也应该有一些基本的概念。
1.多继承问题
在继承抽象类的过程中,我们应该尽量避免多继承;
而在继承接口的时候,我们反而鼓励你来多继承接口
接口隔离原则: 使用多个专门的接口,而不使用单一的总接口。即客户端不应该依赖那些不需要的接口。
2.方法的实现
在抽象类中,我们可以对一些抽象方法做出基础实现;
而在接口类中,任何方法都只是一种规范,具体的功能需要子类实现
ps:组合
面向对象的组合用法
软件重用的重要方式除了继承之外还有另外一种方式,即:组合
组合指的是,在一个类中以另外一个类的对象作为数据属性,称为类的组合
1 class Person: 2 3 def __init__(self,nickname,sex,hp,ad): 4 self.nickname = nickname 5 self.sex = sex 6 self.hp = hp 7 self.ad = ad 8 9 def attack(self,p1): 10 p1.hp -= self.ad 11 print('%s攻击了%s,%s还剩%s血量'%(self.nickname,p1.nickname,p1.nickname,p1.hp)) 12 13 def weapon_attack(self,wea): 14 # 武器类的对象封装到人的对象中当做一个属性.就叫做组合. 15 self.weapon = wea 16 17 class Weapon: 18 19 def __init__(self,name,att): 20 self.name = name 21 self.att = att 22 23 def Aux_attack(self,p,p1): 24 p1.hp -= self.att 25 print('%s利用%s打了%s%s滴血,%s还剩%s滴血' %(p.nickname,self.name,p1.nickname,self.att,p1.nickname,p1.hp)) 26 27 28 # alex = Person('alex','男',100,20) 29 # barry = Person('太白','男',200,50) 30 # axe = Weapon('斧子',30) 31 # barry.weapon_attack(axe) 32 # barry.weapon.Aux_attack(barry,alex) 33 34 # axe.Aux_attack(alex) 35 # alex.attack(barry) 36 # alex.attack(barry)
圆环是由两个圆组成的,圆环的面积是外面圆的面积减去内部圆的面积。圆环的周长是内部圆的周长加上外部圆的周长。
这个时候,我们就首先实现一个圆形类,计算一个圆的周长和面积。然后在"环形类"中组合圆形的实例作为自己的属性来用
1 from math import pi 2 3 class Circle: 4 ''' 5 定义了一个圆形类; 6 提供计算面积(area)和周长(perimeter)的方法 7 ''' 8 def __init__(self,radius): 9 self.radius = radius 10 11 def area(self): 12 return pi * self.radius * self.radius 13 14 def perimeter(self): 15 return 2 * pi *self.radius 16 17 18 circle = Circle(10) #实例化一个圆 19 area1 = circle.area() #计算圆面积 20 per1 = circle.perimeter() #计算圆周长 21 print(area1,per1) #打印圆面积和周长 22 23 class Ring: 24 ''' 25 定义了一个圆环类 26 提供圆环的面积和周长的方法 27 ''' 28 def __init__(self,radius_outside,radius_inside): 29 self.outsid_circle = Circle(radius_outside) 30 self.inside_circle = Circle(radius_inside) 31 32 def area(self): 33 return self.outsid_circle.area() - self.inside_circle.area() 34 35 def perimeter(self): 36 return self.outsid_circle.perimeter() + self.inside_circle.perimeter() 37 38 39 ring = Ring(10,5) #实例化一个环形 40 print(ring.perimeter()) #计算环形的周长 41 print(ring.area()) #计算环形的面积
用组合的方式建立了类与组合的类之间的关系,它是一种‘有’的关系,比如教授有生日,教授教python课程
1 class BirthDate: 2 def __init__(self,year,month,day): 3 self.year=year 4 self.month=month 5 self.day=day 6 7 class Couse: 8 def __init__(self,name,price,period): 9 self.name=name 10 self.price=price 11 self.period=period 12 13 class Teacher: 14 def __init__(self,name,gender,birth,course): 15 self.name=name 16 self.gender=gender 17 self.birth=birth 18 self.course=course 19 def teach(self): 20 print('teaching') 21 p1=Teacher('egon','male', 22 BirthDate('1995','1','27'), 23 Couse('python','28000','4 months') 24 ) 25 26 print(p1.birth.year,p1.birth.month,p1.birth.day) 27 28 print(p1.course.name,p1.course.price,p1.course.period) 29 ''' 30 运行结果: 31 27 32 python 28000 4 months 33 '''
当类之间有显著不同,并且较小的类是较大的类所需要的组件时,用组合比较好
多态
Pyhon不支持Java和C#这一类强类型语言中多态的写法,但是原生多态,其Python崇尚“鸭子类型”。
1 class F1: 2 pass 3 4 5 class S1(F1): 6 7 def show(self): 8 print 'S1.show' 9 10 11 class S2(F1): 12 13 def show(self): 14 print 'S2.show' 15 16 17 # 由于在Java或C#中定义函数参数时,必须指定参数的类型 18 # 为了让Func函数既可以执行S1对象的show方法,又可以执行S2对象的show方法,所以,定义了一个S1和S2类的父类 19 # 而实际传入的参数是:S1对象和S2对象 20 21 def Func(F1 obj): 22 """Func函数需要接收一个F1类型或者F1子类的类型""" 23 24 print obj.show() 25 26 s1_obj = S1() 27 Func(s1_obj) # 在Func函数中传入S1类的对象 s1_obj,执行 S1 的show方法,结果:S1.show 28 29 s2_obj = S2() 30 Func(s2_obj) # 在Func函数中传入Ss类的对象 ss_obj,执行 Ss 的show方法,结果:S2.show 31 32 Python伪代码实现Java或C#的多态 33 34 Python伪代码实现java C#多态
1 class F1: 2 pass 3 4 5 class S1(F1): 6 7 def show(self): 8 print 'S1.show' 9 10 11 class S2(F1): 12 13 def show(self): 14 print 'S2.show' 15 16 def Func(obj): 17 print obj.show() 18 19 s1_obj = S1() 20 Func(s1_obj) 21 22 s2_obj = S2() 23 Func(s2_obj) 24 25 Python “鸭子类型” 26 27 鸭子类型
封装
封装,顾名思义就是将内容封装到某个地方,以后再去调用被封装在某处的内容。
所以,在使用面向对象的封装特性时,需要:
- 将内容封装到某处
- 从某处调用被封装的内容
第一步:将内容封装到某处
self 是一个形式参数,当执行 obj1 = Foo('wupeiqi', 18 ) 时,self 等于 obj1
当执行 obj2 = Foo('alex', 78 ) 时,self 等于 obj2
所以,内容其实被封装到了对象 obj1 和 obj2 中,每个对象中都有 name 和 age 属性,在内存里类似于下图来保存。
第二步:从某处调用被封装的内容
调用被封装的内容时,有两种情况:
- 通过对象直接调用
- 通过self间接调用
1、通过对象直接调用被封装的内容
上图展示了对象 obj1 和 obj2 在内存中保存的方式,根据保存格式可以如此调用被封装的内容:对象.属性名
1 class Foo: 2 3 def __init__(self, name, age): 4 self.name = name 5 self.age = age 6 7 obj1 = Foo('wupeiqi', 18) 8 print obj1.name # 直接调用obj1对象的name属性 9 print obj1.age # 直接调用obj1对象的age属性 10 11 obj2 = Foo('alex', 73) 12 print obj2.name # 直接调用obj2对象的name属性 13 print obj2.age # 直接调用obj2对象的age属性
2. 通过self间接调用被封装的内容
执行类中的方法时,需要通过self间接调用被封装的内容
1 class Foo: 2 3 def __init__(self, name, age): 4 self.name = name 5 self.age = age 6 7 def detail(self): 8 print self.name 9 print self.age 10 11 obj1 = Foo('wupeiqi', 18) 12 obj1.detail() # Python默认会将obj1传给self参数,即:obj1.detail(obj1),所以,此时方法内部的 self = obj1,即:self.name 是 wupeiqi ;self.age 是 18 13 14 obj2 = Foo('alex', 73) 15 obj2.detail() # Python默认会将obj2传给self参数,即:obj1.detail(obj2),所以,此时方法内部的 self = obj2,即:self.name 是 alex ; self.age 是 78
综上所述,对于面向对象的封装来说,其实就是使用构造方法将内容封装到 对象 中,然后通过对象直接或者self间接获取被封装的内容。
封装与扩展性
封装在于明确区分内外,使得类实现者可以修改封装内的东西而不影响外部调用者的代码;而外部使用用者只知道一个接口(函数),只要接口(函数)名、参数不变,使用者的代码永远无需改变。这就提供一个良好的合作基础——或者说,只要接口这个基础约定不变,则代码改变不足为虑。
1 #类的设计者 2 class Room: 3 def __init__(self,name,owner,width,length,high): 4 self.name=name 5 self.owner=owner 6 self.__width=width 7 self.__length=length 8 self.__high=high 9 def tell_area(self): #对外提供的接口,隐藏了内部的实现细节,此时我们想求的是面积 10 return self.__width * self.__length 11 12 13 #使用者 14 >>> r1=Room('卧室','egon',20,20,20) 15 >>> r1.tell_area() #使用者调用接口tell_area 16 17 18 #类的设计者,轻松的扩展了功能,而类的使用者完全不需要改变自己的代码 19 class Room: 20 def __init__(self,name,owner,width,length,high): 21 self.name=name 22 self.owner=owner 23 self.__width=width 24 self.__length=length 25 self.__high=high 26 def tell_area(self): #对外提供的接口,隐藏内部实现,此时我们想求的是体积,内部逻辑变了,只需求修该下列一行就可以很简答的实现,而且外部调用感知不到,仍然使用该方法,但是功能已经变了 27 return self.__width * self.__length * self.__high 28 29 30 #对于仍然在使用tell_area接口的人来说,根本无需改动自己的代码,就可以用上新功能 31 >>> r1.tell_area()