接口:從協議到抽象基類

 

  封裝(Encapsulation)、繼承(Inheritance)、多態(Polymorphism)python

 

(1)封裝(Encapsulation):類包含了數據和方法,將數據和方法放在一個類中就構成了封裝。mysql

(2)繼承(Inheritance):Java是單繼承的(這點和C++有區別),意味着一個類只能繼承於一個類,被繼承的類叫父類(或者叫基類,base class),繼承的類叫子類。Java中的繼承使用關鍵字extends。可是,一個類能夠實現多個接口,多個接口之間用逗號進行分割。實現接口使用關鍵字implements。sql

(3)多態(Polymorphism):多態最核心的思想就是,父類的引用能夠指向子類的對象,或者接口類型的引用能夠指向實現該接口的類的實例。數據庫

 

「 開閉 」原則:編程

  對擴展開放:容許新增子類;對修改封閉:不須要修改依賴該類型的函數。 flask

  把不一樣的子類對象都看成父類來看,能夠屏蔽不一樣子類對象之間的差別,寫出通用的代碼,作出通用的編程,以適應需求的不斷變化。oracle

 

鴨子類型:app

  不關注對象的類型,而關注對象的行爲(方法)。它的行爲是鴨子的行爲,那麼能夠認爲它是鴨子。ide

  調用不一樣的子類將會產生不一樣的行爲,而無須明確知道這個子類其實是什麼,這是多態的重要應用場景函數

  鴨子類型是動態類型的一種風格。在這種風格中,一個對象有效的語義,不是由繼承自特定的類或實現特定的接口,而是由"當前方法和屬性的集合"決定。

  這個概念的名字來源於由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 ...')
abc模塊

▲ 在其餘的語言裏,好比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()
沒有使用DI
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()
使用DI
相關文章
相關標籤/搜索