封装(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()方法,就可以正确的被调用。
接口:
接口里有什么方法,继承类就必须有什么方法,接口中不能有任何功能代码。
面向对象中的继承有两种用途:
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()