接口:从协议到抽象基类
封装(Encapsulation)、继承(Inheritance)、多态(Polymorphism)
(1)封装(Encapsulation):类包含了数据和方法,将数据和方法放在一个类中就构成了封装。
(2)继承(Inheritance):Java是单继承的(这点和C++有区别),意味着一个类只能继承于一个类,被继承的类叫父类(或者叫基类,base class),继承的类叫子类。Java中的继承使用关键字extends。但是,一个类可以实现多个接口,多个接口之间用逗号进行分割。实现接口使用关键字implements。
(3)多态(Polymorphism):多态最核心的思想就是,父类的引用可以指向子类的对象,或者接口类型的引用可以指向实现该接口的类的实例。
“ 开闭 ”原则:
对扩展开放:允许新增子类;对修改封闭:不需要修改依赖该类型的函数。
把不同的子类对象都当作父类来看,可以屏蔽不同子类对象之间的差异,写出通用的代码,做出通用的编程,以适应需求的不断变化。
鸭子类型:
不关注对象的类型,而关注对象的行为(方法)。它的行为是鸭子的行为,那么可以认为它是鸭子。
调用不同的子类将会产生不同的行为,而无须明确知道这个子类实际上是什么,这是多态的重要应用场景
鸭子类型是动态类型的一种风格。在这种风格中,一个对象有效的语义,不是由继承自特定的类或实现特定的接口,而是由"当前方法和属性的集合"决定。
这个概念的名字来源于由James Whitcomb Riley提出的鸭子测试。
“鸭子测试”可以这样表述:“当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子。”
class Duck(): def walk(self): print('I walk like a duck') def swim(self): print('i swim like a duck') class Person(): def walk(self): print('this one walk like a duck') def swim(self): print('this man swim like a duck')
Person类拥有跟Duck类一样的方法,当有一个函数调用Duck类,并利用到了两个方法walk()和swim()。
我们传入Person类也一样可以运行,函数并不会检查对象的类型是不是Duck,只要他拥有walk()和swim()方法,就可以正确的被调用。
在Java中的多态,子类需要继承与同一个父类并且覆盖掉父类的一些方法才能定义多态,这一点与python不同。
在python中我们不需要继承同一个父类只需要实现同一个方法就行了。
接口:
接口里有什么方法,继承类就必须有什么方法,接口中不能有任何功能代码。
面向对象中的继承有两种用途:
1)可以通过继承做到代码重用,并完成扩展;
2)接口继承。定义一个接口类 Interface,接口类中定义了一些接口(函数,但这些函数都没有具体的实现),子类继承接口类,并且实现接口中的功能
class Operate_database(): # 接口类 def query(self, sql): raise NotImplementedError def update(self, sql): raise NotImplementedError class Operate_mysql(Operate_database): def query(self, sql): print('query mysql : %s' % sql) def update(self, sql): print('query mysql : %s' % sql) class Operate_pg(Operate_database): def query(self, sql): print('query postgresql : %s' % sql) def update(self, sql): print('update postgresql : %s' % sql) def query_data(operate_obj, sql): operate_obj.query(sql) def update_data(operate_obj, sql): operate_obj.update(sql) query_data(Operate_mysql(), 'select ...') # query mysql : select ... update_data(Operate_pg(), 'update...') # update postgresql : update...
若子类继承了Operate_database 接口类,但是没有实现其中的某一个方法的功能,调用时就会报错
子类覆盖父类中的方法时,要注意方法名需要与父类中的方法名相同,且方法的参数个数与参数名也要相同
这里更好的方式是通过 abc模块 来实现接口
from abc import ABCMeta,abstractmethod class Operate_database(metaclass=ABCMeta): # 接口类 @abstractmethod def query(self, sql): pass @abstractmethod def update(self, sql): pass class Operate_oracle(Operate_database): # 没有实现 query 方法 def update(self, sql): print('update oracle : %s' % sql) def query_data(operate_obj, sql): operate_obj.query(sql) oracle = Operate_oracle() # 由于没有实现接口中的所有方法,在这一步就会报错 query_data(oracle, 'select ...')
▲ 在其他的语言里,比如Java,继承类没有重写接口方法是会报错的,而在python里不会,就是因为python没这个类型,所以只是在我们编程过程的一个规定,以I开头的类视为接口
抽象类:
抽象类和接口类一样是一种规范,规定子类应该具备的功能。
在Python中,抽象类和接口类没有明确的界限。
若是类中所有的方法都没有实现,则认为这是一个接口类,若是有部分方法实现,则认为这是一个抽象类。
抽象类和接口类都仅用于被继承,不能被实例化
声明抽象基类最简单的方式是继承 abc.ABC 或其他抽象基类。
然而,abc.ABC 是 Python 3.4 新增的类,因此如果你使用的是旧版Python,那么无法继承现有的抽象基类。
此时,必须在 class 语句中使用 metaclass= 关键字,把值设为 abc.ABCMeta(不是 abc.ABC)
class Tombola(metaclass=abc.ABCMeta): # Python 3 # ... class Tombola(object): # Python 2 __metaclass__ = abc.ABCMeta # ...
抽象类,可以说是类和接口的混合体,既可以定义常规方法,也可以约束子类的方法(抽象方法)
from abc import ABCMeta,abstractmethod class Operate_database(metaclass=ABCMeta): # 抽象类 log_path = '/tmp/db.log' def connect(self): print('connect db ...') @abstractmethod def query(self, sql): pass @abstractmethod def update(self, sql): pass
协议:
(1)协议是非正式的接口,是一组方法,Python没有interface 关键字,定义接口只是一个人为约定。
(2)Python中存在多种协议,用于实现鸭子类型(对象的类型无关紧要,只要实现了特定的协议(一组方法)即可)。
(3)需要成为相对应的鸭子类型,那就实现相关的协议,即相关的__method__。例如实现序列协议(__len__和getitem),这个类就表现得像序列。
(4)可以根据具体场景实现一个具体协议的一部分。例如,为了支持迭代,只需实现__getitem__,不需要实现__len__。
(5)在Python文档中,如果看到“文件类对象“(表现得像文件的对象),通常说的就是协议,这个对象就是鸭子类型。这是一种简短的说法,意思是:“行为基本与文件一致,实现了部分文件接口,满足上下文相关需求的东西。”
import collections Card = collections.namedtuple('Card', ['rank','suit']) class FrenchDeck: ranks = [str(n) for n in range(2, 11)] + list('JQKA') suits = 'spades diamonds clubs hearts'.split() def __init__(self): self._cards = [Card(rank, suit) for suit in self.suits for rank in self.ranks] def __len__(self): return len(self._cars) def __getitem__(self, position): return self._cards[position] # Python的序列协议只需要__len__和__getitem__两个方法。
DI(依赖注入):
在系统运行中,动态的向某个对象提供它所需要的其他对象。这一点是通过DI(Dependency Injection,依赖注入)来实现的。
比如对象A需要操作数据库,以前我们总是要在A中自己编写代码来获得一个Connection对象;
A中需要一个Connection,至于这个Connection怎么构造,何时构造,A不需要知道。
在系统运行时,会在适当的时候制造一个Connection,然后像打针一样,注射到A当中,这样就完成了对各个对象之间关系的控制。(以参数的形式传入)
A需要依赖 Connection才能正常运行,而这个Connection是通过注入到A中的,依赖注入的名字就这么来的。
from flask import Flask app = Flask("example") class DAO: def __init__(self): self.data = [] dao = DAO() @app.route("/") def m(): return dao.data if __name__ == "__main__": app.run()
from flask import Flask class DAO: def __init__(self): self.data = [] def App(dao): app = Flask("example") @app.route("/") def m(): return dao.data return app if __name__ == "__main__": app = App(DAO()) app.run()