抽象类和接口类
以下内容大部分摘自博客http://www.cnblogs.com/Eva-J/
继承有两种用途:
一:继承基类的方法,并且做出自己的改变或者扩展(代码重用)
二:声明某个子类兼容于某基类,定义一个接口类Interface,
接口类中定义了一些接口名(就是函数名)且并未实现接口的功能,
子类继承接口类,并且实现接口中的功能。
实践中,继承的第一种含义意义并不很大,甚至常常是有害的。因为它使得子类与基类出现强耦合。
继承的第二种含义非常重要。它又叫“接口继承”。
接口继承实质上是要求“做出一个良好的抽象,这个抽象规定了一个兼容接口,
使得外部调用者无需关心具体细节,可一视同仁的处理实现了特定接口的所有对象”——这在程序设计上,叫做归一化。
归一化使得高层的外部使用者可以不加区分的处理所有接口兼容的对象集合——就好象linux的泛文件概念一样,所有东西都可以当文件处理,不必关心它是内存、磁盘、网络还是屏幕。
(当然,对底层设计者,当然也可以区分出“字符设备”和“块设备”,
然后做出针对性的设计:细致到什么程度,视需求而定)。
题外话:关于java
java也是一个面向对象的编程语言,但java中不能多继承。
java程序员中有两本很好的黑皮书:
设计模式 程序设计,具有里程碑意义的设计方式,从java中演变出来的
例:单例模式—-一个类只有一个实例
算法导论 计算的方法,时间和空间的问题,权威通用
由此有一些编程原则和设计模式对于python编程都是有借鉴作用的。
python编程原则
开放封闭原则
开放 对扩展是开放的
封闭 对修改是封闭的
依赖倒置原则
高层模块不应该依赖低层模块,二者都应该依赖其抽象;抽象不应该应该依赖细节;细节应该依赖抽象。换言之,要针对接口编程,而不是针对实现编程
接口隔离原则
(已经写完并交付的程序代码一般是不允许修改的。)
使用多个专门的接口,而不使用单一的总接口。即客户端不应该依赖那些不需要的接口。
支付功能的例子 支付宝支付 qq支付 apply_pay 微信支付 class Alipay: ''' 支付宝支付 ''' def pay(self,money): print('支付宝支付了%s元'%money) class Applepay: ''' apple pay支付 ''' def pay(self,money): print('apple pay支付了%s元'%money) class Wechatpay: def fuqian(self,money): ''' 实现了pay的功能,但是名字不一样 ''' print('微信支付了%s元'%money) def pay(payment,money): ''' 支付函数,总体负责支付 对应支付的对象和要支付的金额 ''' payment.pay(money) p = Wechatpay() pay(p,200) #执行会报错 AttributeError: 'Wechatpay' object has no attribute 'pay' 使用ABC模块创建一个规范 from abc import ABCMeta,abstractmethod class Payment(metaclass=ABCMeta): # 抽象类 接口类 规范和约束 metaclass指定的是一个元类 @abstractmethod def pay(self):pass # 抽象方法 class Alipay(Payment): def pay(self,money): print('使用支付宝支付了%s元'%money) class QQpay(Payment): def pay(self,money): print('使用qq支付了%s元'%money) class Wechatpay(Payment): def pay(self,money): print('使用微信支付了%s元'%money) def recharge(self):pass def pay(a,money): a.pay(money) # 归一化设计:不管是哪一个类的对象,都调用同一个函数去完成相似的功能 a = Alipay() a.pay(100) pay(a,100) q = QQpay() # q.pay(100) pay(q,100) w = Wechatpay() pay(w,100)
抽象类和接口类做的事情 :建立规范
制定一个类的metaclass是ABCMeta,
那么这个类就变成了一个抽象类(接口类)
这个类的主要功能就是建立一个规范。
抽象类中所有被abstractmethod装饰的方法都必须被继承的子类实现
如果不实现,那么在实例化阶段将会报错
无论是抽象类还是接口类metaclass=ABCMeta 都不可以被实例化
p = Payment() # 报错
什么是抽象类
与java一样,python也有抽象类的概念但是同样需要借助模块实现,抽象类是一个特殊的类,它的特殊之处在于只能被继承,不能被实例化
为什么要有抽象类
如果说类是从一堆对象中抽取相同的内容而来的,那么抽象类就是从一堆类中抽取相同的内容而来的,内容包括数据属性和函数属性。
比如我们有香蕉的类,有苹果的类,有桃子的类,从这些类抽取相同的内容就是水果这个抽象的类,你吃水果时,要么是吃一个具体的香蕉,要么是吃一个具体的桃子。。。。。。你永远无法吃到一个叫做水果的东西。
从设计角度去看,如果类是从现实对象抽象而来的,那么抽象类就是基于类抽象而来的。
从实现角度来看,抽象类与普通类的不同之处在于:抽象类中有抽象方法,该类不能被实例化,只能被继承,且子类必须实现抽象方法。这一点与接口有点类似,但其实是不同的。
例:在python中实现抽象类 # 模拟linux一切皆文件 import abc #利用abc模块实现抽象类 class All_file(metaclass=abc.ABCMeta): all_type='file' @abc.abstractmethod #定义抽象方法,无需实现功能 def read(self): '子类必须定义读功能' pass @abc.abstractmethod #定义抽象方法,无需实现功能 def write(self): '子类必须定义写功能' pass # class Txt(All_file): # pass # # t1=Txt() #报错,子类没有定义抽象方法 class Txt(All_file): #子类继承抽象类,但是必须定义read和write方法 def read(self): print('文本数据的读取方法') def write(self): print('文本数据的读取方法') class Sata(All_file): #子类继承抽象类,但是必须定义read和write方法 def read(self): print('硬盘数据的读取方法') def write(self): print('硬盘数据的读取方法') class Process(All_file): #子类继承抽象类,但是必须定义read和write方法 def read(self): print('进程数据的读取方法') def write(self): print('进程数据的读取方法') wenbenwenjian=Txt() yingpanwenjian=Sata() jinchengwenjian=Process() #这样大家都是被归一化了,也就是一切皆文件的思想 wenbenwenjian.read() yingpanwenjian.write() jinchengwenjian.read() print(wenbenwenjian.all_type) print(yingpanwenjian.all_type) print(jinchengwenjian.all_type)
什么叫接口
python里没有接口的概念,那接口是哪儿来的概念呢?
从java里来的,java类没有多继承,接口可以实现多继承。
例:描述动物园 会游泳的 会走路的 会爬树的 会飞的 老虎 青蛙 天鹅 猴子 from abc import ABCMeta,abstractmethod class FlyAnimal(metaclass=ABCMeta): @abstractmethod def fly(self):pass @abstractmethod def cal_flying_speed(self):pass @abstractmethod def cal_flying_height(self):pass class WalkAnimal(metaclass=ABCMeta): @abstractmethod def walk(self):pass class SwimAnimal(metaclass=ABCMeta): @abstractmethod def walk(self):pass class Tiger(WalkAnimal,SwimAnimal): def walk(self):pass def swim(self):pass class Monkey: def walk(self):pass def climb(self):pass class Swan(FlyAnimal,WalkAnimal,SwimAnimal): def swim(self):pass def walk(self):pass def fly(self):pass def cal_flying_speed(self):pass def cal_flying_height(self):pass class Parrot(FlyAnimal): def fly(self):pass def cal_flying_speed(self):pass def cal_flying_height(self): pass 所有会飞的动物 具有一些会飞的动物的特性 所有会走的动物 具有一些会走的动物的特性 在python中根本就没有一个叫做interface的关键字,上面的代码只是看起来像接口, 其实并没有起到接口的作用,子类完全可以不用去实现接口 , 如果非要去模仿接口的概念,可以借助第三方模块: http://pypi.python.org/pypi/zope.interface twisted的twisted/internet/interface.py里使用zope.interface 文档https://zopeinterface.readthedocs.io/en/latest/ 设计模式:https://github.com/faif/python-patterns
接口类的作用:
在java中,能够满足接口隔离原则,且完成多继承的约束。
而在python中,满足接口隔离原则,由于python本身支持多继承,所以就不需要接口的概念了。
抽象类和接口类
在python中并没有什么不同,都是用来约束子类中的方法。
只要是抽象类和接口类中被abstractmethod装饰的方法,都需要被子类实现。
需要注意的是,当多个类之间有相同的功能也有不同的功能的时候,应该采用多个接口类来进行分别的约束。
抽象类是python中定义类的一种规范。
抽象类的本质还是类,指的是一组类的相似性,包括数据属性(如all_type)和函数属性(如read、write),
而接口只强调函数属性的相似性。
抽象类是一个介于类和接口直接的一个概念,同时具备类和接口的部分特性,可以用来实现归一化设计。
在python中,并没有接口类这种东西,即便不通过专门的模块定义接口,我们也应该有一些基本的概念。
在java中,抽象类和接口截然不同。
抽象类的本质还是一个类,是类就必须遵循单继承的规则,所以一个子类如果被抽象类约束,
那么它只能被一个父类控制,当多个类之间有相同的功能也有不同的功能的时候,java只能用接口来解决问题。
(使用interface关键字)
1.多继承问题
在继承抽象类的过程中,我们应该尽量避免多继承;
而在继承接口的时候,我们反而鼓励你来多继承接口。
2.方法的实现
在抽象类中,我们可以对一些抽象方法做出基础实现;
而在接口类中,任何方法都只是一种规范,具体的功能需要子类实现。
注:在公司类写代码的时候,如果遇到抽象类,记得按照抽象类中的规范一一实现对应的方法。
为何要用接口
接口提取了一群类共同的函数,可以把接口当做一个函数的集合。
然后让子类去实现接口中的函数。
这么做的意义在于归一化,什么叫归一化,就是只要是基于同一个接口实现的类,那么所有的这些类产生的对象在使用时,从用法上来说都一样。
归一化,让使用者无需关心对象的类是什么,只需要的知道这些对象都具备某些功能就可以了,这极大地降低了使用者的使用难度。
比如:我们定义一个动物接口,接口里定义了有跑、吃、呼吸等接口函数,这样老鼠的类去实现了该接口,松鼠的类也去实现了该接口,由二者分别产生一只老鼠和一只松鼠送到你面前,即便是你分别不到底哪只是什么鼠你肯定知道他俩都会跑,都会吃,都能呼吸。
再比如:我们有一个汽车接口,里面定义了汽车所有的功能,然后由本田汽车的类,奥迪汽车的类,大众汽车的类,他们都实现了汽车接口,这样就好办了,大家只需要学会了怎么开汽车,那么无论是本田,还是奥迪,还是大众我们都会开了,开的时候根本无需关心我开的是哪一类车,操作手法(函数调用)都一样。
多态
多态指的是一类事物有多种形态
例如动物有多种形态:人类,狗,猫。
文件有多种形态:文本文件,可执行文件
import abc class File(metaclass=abc.ABCMeta): #同一类事物:文件 @abc.abstractmethod def click(self): pass class Text(File): #文件的形态之一:文本文件 def click(self): print('open file') class ExeFile(File): #文件的形态之二:可执行文件 def click(self): print('execute file')
多态性
一 什么是多态动态绑定(在继承的背景下使用时,有时也称为多态性)
多态性是指在不考虑实例类型的情况下使用实例
在面向对象方法中一般是这样表述多态性:
向不同的对象发送同一条消息(!!!obj.func():是调用了obj的方法func,又称为向obj发送了一条消息func),
不同的对象在接收时会产生不同的行为(即方法)。
也就是说,每个对象可以用自己的方式去响应共同的消息。
所谓消息,就是调用函数,不同的行为就是指不同的实现,即执行不同的函数。
比如:老师.下课铃响了(),学生.下课铃响了(),老师执行的是下班操作,学生执行的是放学操作,
虽然二者消息一样,但是执行的效果不同。
例: peo=People() dog=Dog() pig=Pig() #peo、dog、pig都是动物,只要是动物肯定有talk方法 #于是我们可以不用考虑它们三者的具体是什么类型,而直接使用 peo.talk() dog.talk() pig.talk() #更进一步,我们可以定义一个统一的接口来使用 def func(obj): obj.talk() 题外话: java c++ c# —— 强类型语言 相同数据类型之间做运算 def func(int a):pass func('a') shell语言 —— 弱类型语言 1+'1' def func(a):pass 1 'a' [1,2,3] () 介于 强类型 与 弱类型之间 —— python 动态强类型语言 相同数据类型之间做运算 def func(a):pass 例: class Payment: def pay(self):pass class QQpay(Payment): def pay(self,money): print('使用qq支付了%s元'%money) class Wechatpay(Payment): def pay(self,money): print('使用微信支付了%s元'%money) def recharge(self):pass def pay(Payment pay_obj,int money): # java 多态 在一个类之下发展出来的多个类的对象都可以作为参数传入这里 pay_obj.pay(money) # 无论是python的2.*还是3.* : 天生自带多态效果 # qq_obj = QQpay() # print(type(qq_obj)) # 一个对象的数据类型就是它所在的类
鸭子类型
逗比时刻:
Python崇尚鸭子类型,即‘如果看起来像、叫声像而且走起路来像鸭子,那么它就是鸭子’
python程序员通常根据这种行为来编写程序。
例如,如果想编写现有对象的自定义版本,可以继承该对象,也可以创建一个外观和行为像,但与它无任何关系的全新对象,后者通常用于保存程序组件的松耦合度。
例1:利用标准库中定义的各种‘与文件类似’的对象,尽管这些对象的工作方式像文件,但他们没有继承内置文件对象的方法。
例2:序列类型有多种形态:字符串,列表,元组,但他们直接没有直接的继承关系。
例:下例的QQpay和Wechatpay我们就可以说它们互为鸭子类型。
class QQpay(): def pay(self,money): print('使用qq支付了%s元'%money) class Wechatpay(): def pay(self,money): print('使用微信支付了%s元'%money) def pay(pay_obj,money): pay_obj.pay(money)
多态和鸭子类型
多态 通过继承实现
java 在一个类之下发展出来的多个类的对象都可以作为参数传入一个函数或者方法;
在python中不需要刻意实现多态,因为python本身自带多态效果。
鸭子类型
不是通过具体的继承关系来约束某些类中必须有哪些方法名;
是通过一种约定俗成的概念来保证在多个类中相似的功能叫相同的名字。
end
2018-4-16
原创文章,作者:Maggie-Hunter,如若转载,请注明出处:https://blog.ytso.com/12760.html