[TOC]
面向?qū)ο?/h1>
繼承與派生
繼承
- 什么是繼承?繼承是一種創(chuàng)建新的類(lèi)的方式
class A:
pass
class B(A):
pass
class A:
pass
class B(A):
pass
在python中,新建的類(lèi)可以繼承自一個(gè)或者多個(gè)父類(lèi),原始類(lèi)稱(chēng)為基類(lèi)或者超類(lèi),新建的類(lèi)稱(chēng)為派生類(lèi)或者子類(lèi)
python中類(lèi)的繼承分為,單繼承和多繼承。
查看繼承的方法B.__bases__
如果沒(méi)有指定基類(lèi),python的類(lèi)會(huì)默認(rèn)集成object類(lèi),object是所有python類(lèi)的基類(lèi)。
- 如何繼承-》如何尋找繼承關(guān)系
繼承是一種‘是’的關(guān)系:
人類(lèi)、豬類(lèi)、狗類(lèi)都繼承動(dòng)物類(lèi),因而他們都是動(dòng)物
抽象:抽取類(lèi)似或者說(shuō)比較相似的部分
老師是這樣定義這個(gè)抽象的,并且還介紹了一個(gè)抽象類(lèi)的概念abc
,但是這個(gè)抽象的意思翻譯過(guò)來(lái)就是尋找對(duì)象的相似點(diǎn),將對(duì)象根據(jù)特征進(jìn)行歸納總結(jié)為類(lèi)的過(guò)程。
在找到相似點(diǎn)(也就是“抽象”)之后,其實(shí)對(duì)象往往還有細(xì)分部分的一些特征,并且會(huì)有層級(jí)地可以劃分類(lèi)別
例如:
動(dòng)物>>>
人/豬/狗>>>
奧巴馬/梅西 // 麥兜/豬八戒 // 史努比/丁丁
由此將對(duì)象原本模糊的關(guān)注點(diǎn)隔離開(kāi),降低復(fù)雜度
繼承:基于抽象的結(jié)果,通過(guò)編程語(yǔ)言去實(shí)現(xiàn),經(jīng)歷抽象的過(guò)程,通過(guò)繼承去表達(dá)出抽象的結(jié)構(gòu)
- 為什么要用繼承?
解決代碼重用問(wèn)題:
在了解了“繼承就是‘是’的關(guān)系”之后,我們會(huì)發(fā)現(xiàn),我們定義劃分開(kāi)的父子類(lèi)之間,其實(shí)是有些包含的、并且共有的功能。
例如“人”這個(gè)類(lèi),都是可以“走”、“說(shuō)”的;動(dòng)物這個(gè)個(gè)類(lèi),都可以有“吃”的動(dòng)作。
由此,我們就可以在父類(lèi)中定義共同有的方法、屬性,并且讓所有子類(lèi)都享用。
class hero:
def __init__(self, name,aggressivity, life_value):
self.name = name
self.aggressivity = aggressivity
self.life_value = life_value
def attack(self,enemy):
print('hero attack')
class garen(hero):
def __init__(self,name,aggressivity,life_value,script):
hero.__init__(self, name,aggressivity, life_value)
self.script = script
這段代碼中,我們就定義了一個(gè)英雄類(lèi),他的子類(lèi)蓋倫可以繼承一些他定義的屬性
hero.__init__(self, name,aggressivity, life_value)
這就節(jié)約了蓋倫類(lèi)定義的代碼,并且給以后定義新的子類(lèi)英雄提供了便利。
但實(shí)際上,這種調(diào)用方法跟繼承并沒(méi)有關(guān)系,這種類(lèi)名.函數(shù)的調(diào)用方法其實(shí)可以調(diào)用所有其他類(lèi)的方法。而且當(dāng)子類(lèi)修改父類(lèi)之后,代碼還要隨之更改,非常麻煩,并不推薦這種方式。
這就是代碼的重用
當(dāng)然子類(lèi)也可以有自己的屬性,例如上面代碼中的self.script = script
。
如果說(shuō)子類(lèi)自己定義的屬性或者方法和父類(lèi)中有重名,那么就以子類(lèi)自己定義的為準(zhǔn)。
上面使用hero.__init__(self, name,aggressivity, life_value)
就是在調(diào)用父類(lèi)函數(shù)的方法作為子類(lèi)定義函數(shù)的參數(shù),在使用父類(lèi)函數(shù)的時(shí)候,要記得不能再使用self
,而是跟上要調(diào)用的類(lèi)的名稱(chēng)。
派生:
子類(lèi)繼承了父類(lèi)的屬性,然后衍生出自己新的屬性,
如果子類(lèi)衍生出的新的屬性與父類(lèi)的某個(gè)屬性名字相同,
那么再調(diào)用子類(lèi)的這個(gè)屬性,就以子類(lèi)自己這里的為準(zhǔn)了
繼承順序
class A(object):
def test(self):
print('from A')
class B(A):
def test(self):
print('from B')
class C(A):
def test(self):
print('from C')
class D(B):
def test(self):
print('from D')
class E(C):
def test(self):
print('from E')
class F(D,E):
# def test(self):
# print('from F')
pass
f1=F()
f1.test()
print(F.__mro__) #只有新式才有這個(gè)屬性可以查看線性列表,經(jīng)典類(lèi)沒(méi)有這個(gè)屬性
#新式類(lèi)繼承順序:F->D->B->E->C->A
#經(jīng)典類(lèi)繼承順序:F->D->B->A->E->C
#python3中統(tǒng)一都是新式類(lèi)
#pyhon2中才分新式類(lèi)與經(jīng)典類(lèi)
作業(yè)分析:

- 新式類(lèi)的繼承順序總結(jié):
C3算法非廣度優(yōu)先,利用MRO順序進(jìn)行繼承
?
“不徹底”,“從左往右”
向父類(lèi)遍歷,到達(dá)根類(lèi)的前一個(gè)類(lèi)就往另外的支線遍歷 - 經(jīng)典類(lèi)中的集成順序總結(jié):
深度優(yōu)先
“徹底”,“從左往右”
向父類(lèi)遍歷,到達(dá)根類(lèi)才向另外的支線遍歷
繼承原理
每定義一個(gè)淚,python會(huì)計(jì)算出一個(gè)方法解析順序(MRO)列表,這個(gè)MRO列表就是一個(gè)簡(jiǎn)單的所有基類(lèi)的線性順序列表:
PyObject* tp_mro
Tuple containing the expanded set of base types, starting with the type itself and
ending with object, in Method Resolution Order.
以元組的形式返回所有基類(lèi)的擴(kuò)展的集合,從他本身的類(lèi)開(kāi)始,到object類(lèi)結(jié)束
This field is not inherited; it is calculated fresh by PyType_Ready().
這個(gè)字段并不是通過(guò)繼承得到的,而是通過(guò)by PyType_Ready()方法計(jì)算更新的
>>> class tsr:
... pass
...
>>> str.__mro__
(<class 'str'>, <class 'object'>)
>>> str.mro()
[<class 'str'>, <class 'object'>]
為了實(shí)現(xiàn)繼承,python會(huì)在MRO列表上從左到右開(kāi)始查找基類(lèi),直到找到第一個(gè)匹配這個(gè)屬性的類(lèi)為止。
而這個(gè)MRO列表的構(gòu)造是通過(guò)一個(gè)C3線性化算法來(lái)實(shí)現(xiàn)的。我們不去深究這個(gè)算法的數(shù)學(xué)原理,它實(shí)際上就是合并所有父類(lèi)的MRO列表并遵循如下三條準(zhǔn)則:
- 子類(lèi)會(huì)先于父類(lèi)被檢查
- 多個(gè)父類(lèi)會(huì)根據(jù)它們?cè)诹斜碇械捻樞虮粰z查
- 如果對(duì)下一個(gè)類(lèi)存在兩個(gè)合法的選擇,選擇第一個(gè)父類(lèi)
子類(lèi)調(diào)用父類(lèi)的方法(super)
方法一:
父類(lèi)名.父類(lèi)方法()
不多說(shuō),參考上面的引用
方法二:
super()
當(dāng)你使用super()函數(shù)時(shí),Python會(huì)在MRO列表上繼續(xù)搜索下一個(gè)類(lèi)。只要每個(gè)重定義的方法統(tǒng)一使用super()并只調(diào)用它一次,那么控制流最終會(huì)遍歷完整個(gè)MRO列表,每個(gè)方法也只會(huì)被調(diào)用一次(注意注意注意:使用super調(diào)用的所有屬性,都是從MRO列表當(dāng)前的位置往后找,千萬(wàn)不要通過(guò)看代碼去找繼承關(guān)系,一定要看MRO列表)
思考題:

上圖的代碼以及運(yùn)行結(jié)果已經(jīng)放在圖中,下面的代碼使用的是糾正使用super()的代碼:
class A(object):
def __init__(self):
print("enter A")
super(A, self).__init__() # new
print("leave A")
class B(object):
def __init__(self):
print("enter B")
super(B, self).__init__() # new
print("leave B")
class C(A):
def __init__(self):
print("enter C")
super(C, self).__init__()
print("leave C")
class D(A):
def __init__(self):
print("enter D")
super(D, self).__init__()
print("leave D")
class E(B, C):
def __init__(self):
print("enter E")
super(E, self).__init__() # change
print("leave E")
class F(E, D):
def __init__(self):
print("enter F")
super(F, self).__init__() # change
print ("leave F")
f = F()
小結(jié):
- super并不是一個(gè)函數(shù),是一個(gè)類(lèi)名,形如super(B, self)事實(shí)上調(diào)用了super類(lèi)的初始化函數(shù),產(chǎn)生了一個(gè)super對(duì)象;
- super類(lèi)的初始化函數(shù)并沒(méi)有做什么特殊的操作,只是簡(jiǎn)單記錄了類(lèi)類(lèi)型和具體實(shí)例;
- super(B, self).func的調(diào)用并不是用于調(diào)用當(dāng)前類(lèi)的父類(lèi)的func函數(shù);
- Python的多繼承類(lèi)是通過(guò)mro的方式來(lái)保證各個(gè)父類(lèi)的函數(shù)被逐一調(diào)用,而且保證每個(gè)父類(lèi)函數(shù)只調(diào)用一次(如果每個(gè)類(lèi)都使用super);
- 混用super類(lèi)和非綁定的函數(shù)是一個(gè)危險(xiǎn)行為,這可能導(dǎo)致應(yīng)該調(diào)用的父類(lèi)函數(shù)沒(méi)有調(diào)用或者一個(gè)父類(lèi)函數(shù)被調(diào)用多次。
組合
組合對(duì)比繼承來(lái)說(shuō),也是用來(lái)重用代碼,但是組合描述的是一種“有”的關(guān)系
老師有課程
學(xué)生有成績(jī)
學(xué)生有課程
學(xué)校有老師
學(xué)校有學(xué)生
#定義課程類(lèi)
class Course:
def __init__(self,name,price,period):
self.name=name
self.price=price
self.period=period
#定義老師類(lèi)
class Teacher:
def __init__(name,course):
self.name=name
self.course=course
#定義學(xué)生類(lèi)
class Student:
def __init__(name,course):
self.name=name
self.course=course
#學(xué)生類(lèi)和老師類(lèi)中,都有關(guān)聯(lián)到課程這個(gè)屬性
#所以實(shí)例化一項(xiàng)課程
python=Course('python',15800,'7m')
#在實(shí)例化學(xué)生和老師的時(shí)候, 將實(shí)例化后的課程作為參數(shù)傳入
t1=Teacher('egon',python)
s1=Student('alex',python)
#這樣學(xué)生和老師就獲得了課程實(shí)例的參數(shù),并且可以引用
print(s1.course.name)
print(s1.course.period)
上述代碼就表示了一個(gè)“組合”的過(guò)程。類(lèi)之間通過(guò)實(shí)例化傳參的過(guò)程,來(lái)互相獲取一些需要的數(shù)據(jù),并且建立關(guān)系。
接口
繼承有兩種用途:
一:繼承基類(lèi)的方法,并且做出自己的改變或者擴(kuò)展(代碼重用)
二:聲明某個(gè)子類(lèi)兼容于某基類(lèi),定義一個(gè)接口類(lèi)Interface,接口類(lèi)中定義了一些接口名(就是函數(shù)名)且并未實(shí)現(xiàn)接口的功能,子類(lèi)繼承接口類(lèi),并且實(shí)現(xiàn)接口中的功能
接口的概念:
=================第一部分:Java 語(yǔ)言中的接口很好的展現(xiàn)了接口的含義: IAnimal.java
/*
* Java的Interface很好的體現(xiàn)了我們前面分析的接口的特征:
* 1)是一組功能的集合,而不是一個(gè)功能
* 2)接口的功能用于交互,所有的功能都是public,即別的對(duì)象可操作
* 3)接口只定義函數(shù),但不涉及函數(shù)實(shí)現(xiàn)
* 4)這些功能是相關(guān)的,都是動(dòng)物相關(guān)的功能,但光合作用就不適宜放到IAnimal里面了 */
package com.oo.demo;
public interface IAnimal {
public void eat();
public void run();
public void sleep();
public void speak();
}
=================第二部分:Pig.java:豬”的類(lèi)設(shè)計(jì),實(shí)現(xiàn)了IAnnimal接口
package com.oo.demo;
public class Pig implements IAnimal{ //如下每個(gè)函數(shù)都需要詳細(xì)實(shí)現(xiàn)
public void eat(){
System.out.println("Pig like to eat grass");
}
public void run(){
System.out.println("Pig run: front legs, back legs");
}
public void sleep(){
System.out.println("Pig sleep 16 hours every day");
}
public void speak(){
System.out.println("Pig can not speak"); }
}
=================第三部分:Person2.java
/*
*實(shí)現(xiàn)了IAnimal的“人”,有幾點(diǎn)說(shuō)明一下:
* 1)同樣都實(shí)現(xiàn)了IAnimal的接口,但“人”和“豬”的實(shí)現(xiàn)不一樣,為了避免太多代碼導(dǎo)致影響閱讀,這里的代碼簡(jiǎn)化成一行,但輸出的內(nèi)容不一樣,實(shí)際項(xiàng)目中同一接口的同一功能點(diǎn),不同的類(lèi)實(shí)現(xiàn)完全不一樣
* 2)這里同樣是“人”這個(gè)類(lèi),但和前面介紹類(lèi)時(shí)給的類(lèi)“Person”完全不一樣,這是因?yàn)橥瑯拥倪壿嫺拍?在不同的應(yīng)用場(chǎng)景下,具備的屬性和功能是完全不一樣的 */
package com.oo.demo;
public class Person2 implements IAnimal {
public void eat(){
System.out.println("Person like to eat meat");
}
public void run(){
System.out.println("Person run: left leg, right leg");
}
public void sleep(){
System.out.println("Person sleep 8 hours every dat");
}
public void speak(){
System.out.println("Hellow world, I am a person");
}
}
=================第四部分:Tester03.java
package com.oo.demo;
public class Tester03 {
public static void main(String[] args) {
System.out.println("===This is a person===");
IAnimal person = new Person2();
person.eat();
person.run();
person.sleep();
person.speak();
System.out.println("\n===This is a pig===");
IAnimal pig = new Pig();
pig.eat();
pig.run();
pig.sleep();
pig.speak();
}
}
java中的interface
接口的概念解釋和使用:
這里有一篇解釋接口的文章,其中關(guān)于什么時(shí)候使用繼承,什么時(shí)候用(組合)接口的方面,我看的模模糊糊。但是說(shuō)不定以后能看懂:http://blog.csdn.net/xiaoxiongli/article/details/2791853
其中有一段解釋說(shuō)的很好,用來(lái)描述接口的概念真的妙到毫顫,特意摘下來(lái)整理,留作以后裝逼用:
一馬平川 19:58:54
接口是對(duì)類(lèi)的抽象,我如果直接跟你說(shuō)接口編程,你一定不理解,或者說(shuō)很難理解,因?yàn)榻涌诒旧硎呛艹橄蟮臇|西,現(xiàn)在我舉例跟你說(shuō):
“電源插座就是接口”:
比方說(shuō),插座有兩孔的,有三孔的,不同的插頭需要不同的插座。
接口就描述了能適應(yīng)的插頭范圍現(xiàn)在有一種插座是三孔的,但既可以插三孔的,也可插兩孔的,知道么?
那么,我們可以說(shuō),這個(gè)插座設(shè)計(jì)的好,因?yàn)樗苓m用更廣的范圍。
當(dāng)然,這個(gè)范圍不能超出電源插座這個(gè)概念。
如果是用來(lái)插筆,做筆筒用,那也不適合。
如果電源插座不但能適用兩孔和三孔的插頭,還能適用筆的話,那么我們可以肯定的說(shuō),這個(gè)接口設(shè)計(jì)的太差了。因?yàn)榻涌冢ú遄┑脑O(shè)計(jì)應(yīng)該是對(duì)某一類(lèi)事物的抽象。而且,接口(插座)實(shí)現(xiàn)以后,實(shí)現(xiàn)該接口的類(lèi)(插頭)必須符合接口的定義(插座和插口匹配),而且需要完全符合,一點(diǎn)不符合都不行。
所以實(shí)現(xiàn)某個(gè)接口的類(lèi),必須重寫(xiě)接口中定義的所有方法。如果你覺(jué)得該方法不需要實(shí)現(xiàn),你可以留空,但必須重寫(xiě)。
看我這句話:“接口只定義了方法的原型,即參數(shù)和方法名以及返回值,集成接口的類(lèi)需要實(shí)現(xiàn)它。”
而且,接口(插座)實(shí)現(xiàn)以后,實(shí)現(xiàn)該接口的類(lèi)(插頭)必須符合接口的定義(插座和插口匹配)。其實(shí),你會(huì)發(fā)現(xiàn)插座生產(chǎn)出來(lái)后,如果某電器的插頭和插座不匹配,那么就無(wú)法使用該電器了。實(shí)際上,你在設(shè)計(jì)一個(gè)接口的時(shí)候,很難想到要怎么去設(shè)計(jì),盡管你知道集成這個(gè)接口的類(lèi)是怎么樣的。就像如果你開(kāi)一個(gè)工廠生產(chǎn)插線板,你在不知道電器,或不完全知道電器的插頭如何設(shè)計(jì)的時(shí)候,你是很難生產(chǎn)出能用的插線板的。
那么,如何設(shè)計(jì)插線板呢?或者說(shuō)如何設(shè)計(jì)接口呢?
先看看插線板廠商是如何生產(chǎn)的吧。
某天,有人生產(chǎn)一個(gè)電器是4個(gè)孔的,那就用不了了。這時(shí)候,插線板廠商為了生產(chǎn)出一種插線板,能適用于目前的大部分電器,也能適用于將來(lái)的電器,他找到了一個(gè)機(jī)構(gòu)。機(jī)構(gòu)是專(zhuān)門(mén)指定規(guī)則的,專(zhuān)門(mén)制定協(xié)議的。機(jī)構(gòu)叫來(lái)了大部分的重要電器廠商的頭頭,和插線板老板一起開(kāi)了個(gè)會(huì)。大家為了共同的利益,決定了一份協(xié)議。
協(xié)議是這樣的:電器廠商以后生產(chǎn)的電器的插頭,只能生產(chǎn)三孔的,但為了兼容目前市場(chǎng)上已有的電器,也能生產(chǎn)兩孔的,但是盡量生產(chǎn)三孔的。而且孔的大小和之間的距離有明確的規(guī)定。插線板廠商的插線板也只能有兩孔的和三孔的,而且孔的大小和之間的距離也必須按照協(xié)議來(lái)生產(chǎn)。
于是問(wèn)題解決了,而且插線板廠商老板很聰明,他發(fā)現(xiàn)可以生產(chǎn)出既可以插兩孔,又可以插三孔的插口,于是他的插線板大賣(mài),他發(fā)財(cái)了。優(yōu)秀的接口設(shè)計(jì),給他帶來(lái)了大大的好處,但他很聰明,他沒(méi)忘記如果沒(méi)有規(guī)范協(xié)議的機(jī)構(gòu),一切都是空白。
再補(bǔ)充幾句吧,不然你還是難以理解。
當(dāng)你想設(shè)計(jì)一個(gè)接口的時(shí)候,你最好先寫(xiě)幾個(gè)將要繼承這個(gè)幾口的類(lèi),寫(xiě)幾個(gè)只有框架而無(wú)實(shí)際內(nèi)容的類(lèi),看看他們之間的共性,找到寫(xiě)接口的點(diǎn),這就正如找電器老板來(lái)開(kāi)會(huì)。寫(xiě)接口的時(shí)候,你需要在之前對(duì)接口進(jìn)行說(shuō)明,說(shuō)明接口的適用范圍,以及繼承該接口的注意事項(xiàng),這就好比請(qǐng)機(jī)構(gòu)來(lái)制定協(xié)議規(guī)范。有了這些以后,你的接口在被使用的時(shí)候就不會(huì)錯(cuò)用,在寫(xiě)繼承該接口的類(lèi)的時(shí)候,也會(huì)按照規(guī)范完全的匹配接口。
最后一句話,即使你理解了我今晚所講的每一句話,你還是不會(huì)寫(xiě)接口,因?yàn)槟阈枰獙?shí)踐,實(shí)踐才會(huì)出真知。最后這句話才是至理名言,我說(shuō)的基本都是空話(在你學(xué)會(huì)了寫(xiě)接口后)。
第一次寫(xiě)接口時(shí),第一個(gè)感覺(jué)就是,寫(xiě)接口跟沒(méi)寫(xiě)一樣。定義一個(gè)接口,馬上去寫(xiě)實(shí)現(xiàn)類(lèi)!其實(shí)此時(shí)就是用著面向過(guò)程的思路寫(xiě)程序,然后掛了個(gè)羊頭,說(shuō)起來(lái)怎么也有個(gè)接口了!
今天看了一位老兄寫(xiě)的對(duì)于接口的心得體會(huì),真是太有同感了!
不要為了接口而接口,當(dāng)你把自己不當(dāng)做是個(gè)程序員來(lái)思考時(shí),就能把用人的思想來(lái)思考了,你不會(huì)寫(xiě)程序,就不會(huì)考慮細(xì)節(jié)的實(shí)現(xiàn)了!此時(shí)你所關(guān)注的問(wèn)題就是比較抽象的了,你看這不正符合面向?qū)ο蟮脑瓌t嗎?當(dāng)年張三豐教張無(wú)忌打太極就是要把招式全忘了,你要定義接口前就先忘了自己是個(gè)程序員吧!
當(dāng)然不可能有100%的抽象,最終你還是要回到實(shí)現(xiàn)細(xì)節(jié)上來(lái)的,可此時(shí)你已是學(xué)會(huì)了太極的張無(wú)忌了!
python中的接口:
單純?yōu)榱舜a重用的繼承,在實(shí)際的使用中很容易造成“高耦合”的問(wèn)題。
而接口繼承,根據(jù)接口的概念“給使用接口的對(duì)象一個(gè)很好的抽象”,將對(duì)象的一些特征進(jìn)行歸一化處理,使外部調(diào)用者無(wú)需關(guān)心細(xì)節(jié),可以一視同仁地處理特定接口的所有對(duì)象。
但是在python中沒(méi)有interface的概念。在python中定義接口,僅僅是看起來(lái)像接口:
第一次寫(xiě)接口時(shí),第一個(gè)感覺(jué)就是,寫(xiě)接口跟沒(méi)寫(xiě)一樣。定義一個(gè)接口,馬上去寫(xiě)實(shí)現(xiàn)類(lèi)!其實(shí)此時(shí)就是用著面向過(guò)程的思路寫(xiě)程序,然后掛了個(gè)羊頭,說(shuō)起來(lái)怎么也有個(gè)接口了!
接口提取了一群類(lèi)共同的函數(shù),可以把接口當(dāng)做一個(gè)函數(shù)的集合。
然后讓子類(lèi)去實(shí)現(xiàn)接口中的函數(shù)。
這么做的意義在于歸一化,什么叫歸一化,就是只要是基于同一個(gè)接口實(shí)現(xiàn)的類(lèi),那么所有的這些類(lèi)產(chǎn)生的對(duì)象在使用時(shí),從用法上來(lái)說(shuō)都一樣。
歸一化,讓使用者無(wú)需關(guān)心對(duì)象的類(lèi)是什么,只需要的知道這些對(duì)象都具備某些功能就可以了,這極大地降低了使用者的使用難度。
抽象類(lèi)
定義:
抽象類(lèi)是一個(gè)特殊的類(lèi),他的特殊之處在于:只能被繼承,不能被實(shí)例化為什么要有抽象類(lèi)
類(lèi)是對(duì)相似的對(duì)象進(jìn)行一個(gè)總結(jié)
抽象類(lèi)就是對(duì)有共同點(diǎn)的類(lèi)的一種抽取總結(jié)(不完全說(shuō)是總結(jié),是因?yàn)轭?lèi)和類(lèi)之間一定存在不同,不能完全統(tǒng)一總結(jié)成一類(lèi),真的是總結(jié)關(guān)系的話,應(yīng)該就是子類(lèi)和父類(lèi)的關(guān)系了)。
從實(shí)現(xiàn)的角度來(lái)看,抽象類(lèi)與普通類(lèi)的不同在于:
抽象類(lèi)只能有抽象方法(沒(méi)有實(shí)現(xiàn)功能),這類(lèi)不能被實(shí)例化,只能被繼承,而且子類(lèi)必須實(shí)現(xiàn)方法
從以上描述來(lái)看,抽象類(lèi)非常類(lèi)似接口,但是仍然是有差別的。
#_*_coding:utf-8_*_
__author__ = 'Linhaifeng'
#一切皆文件
import abc #利用abc模塊實(shí)現(xiàn)抽象類(lèi)
class All_file(metaclass=abc.ABCMeta):
all_type='file'
@abc.abstractmethod #定義抽象方法,無(wú)需實(shí)現(xiàn)功能
def read(self):
'子類(lèi)必須定義讀功能' #如果子類(lèi)不實(shí)現(xiàn),就會(huì)報(bào)出此錯(cuò)誤,以此來(lái)實(shí)現(xiàn)類(lèi)似接口的功能
pass
@abc.abstractmethod #定義抽象方法,無(wú)需實(shí)現(xiàn)功能
def write(self):
'子類(lèi)必須定義寫(xiě)功能'
pass
# class Txt(All_file):
# pass
#
# t1=Txt() #報(bào)錯(cuò),子類(lèi)沒(méi)有定義抽象方法
class Txt(All_file): #子類(lèi)繼承抽象類(lèi),但是必須定義read和write方法
def read(self):
print('文本數(shù)據(jù)的讀取方法')
def write(self):
print('文本數(shù)據(jù)的讀取方法')
class Sata(All_file): #子類(lèi)繼承抽象類(lèi),但是必須定義read和write方法
def read(self):
print('硬盤(pán)數(shù)據(jù)的讀取方法')
def write(self):
print('硬盤(pán)數(shù)據(jù)的讀取方法')
class Process(All_file): #子類(lèi)繼承抽象類(lèi),但是必須定義read和write方法
def read(self):
print('進(jìn)程數(shù)據(jù)的讀取方法')
def write(self):
print('進(jìn)程數(shù)據(jù)的讀取方法')
wenbenwenjian=Txt()
yingpanwenjian=Sata()
jinchengwenjian=Process()
#這樣大家都是被歸一化了,也就是一切皆文件的思想
wenbenwenjian.read()
yingpanwenjian.write()
jinchengwenjian.read()
print(wenbenwenjian.all_type)
print(yingpanwenjian.all_type)
print(jinchengwenjian.all_type)
由上面的代碼可以看出,抽象類(lèi)基本上實(shí)現(xiàn)了接口的功能,但是抽象類(lèi)的本質(zhì)還是一個(gè)類(lèi)。指的是一組淚的相似性,包括數(shù)據(jù)屬性和函數(shù)屬性,而接口只強(qiáng)調(diào)函數(shù)屬性的相似性。
所以,抽象類(lèi)是一個(gè)介于類(lèi)和接口之ijede一個(gè)概念,同時(shí)具備類(lèi)和接口的部分特性,可以用來(lái)實(shí)現(xiàn)歸一化設(shè)計(jì)。