python 初識面向對象

一.初識面向對象

面向過程的程序設計的核心是過程(流水線式思惟),過程即解決問題的步驟,面向過程的設計就比如精心設計好一條流水線,考慮周全何時處理什麼東西。java

優勢是:極大的下降了寫程序的複雜度,只須要順着要執行的步驟,堆疊代碼便可。python

缺點是:一套流水線或者流程就是用來解決一個問題,代碼牽一髮而動全身。linux

應用場景:一旦完成基本不多改變的場景,著名的例子有Linux內核,git,以及Apache HTTP Server等。git

 

面向對象的程序設計的核心是對象(上帝式思惟),要理解對象爲什麼物,必須把本身當成上帝,上帝眼裏世間存在的萬物皆爲對象,不存在的也能夠創造出來。面向對象的程序設計比如如來設計西遊記,如來要解決的問題是把經書傳給東土大唐,如來想了想解決這個問題須要四我的:唐僧,沙和尚,豬八戒,孫悟空,每一個人都有各自的特徵和技能(這就是對象的概念,特徵和技能分別對應對象的屬性和方法),然而這並很差玩,因而如來又安排了一羣妖魔鬼怪,爲了防止師徒四人在取經路上被搞死,又安排了一羣神仙保駕護航,這些都是對象。而後取經開始,師徒四人與妖魔鬼怪神仙互相纏鬥着直到最後取得真經。如來根本不會管師徒四人按照什麼流程去取。程序員

面向對象的程序設計的github

優勢是:解決了程序的擴展性。對某一個對象單獨修改,會馬上反映到整個體系中,如對遊戲中一我的物參數的特徵和技能修改都很容易。編程

缺點:可控性差,沒法向面向過程的程序設計流水線式的能夠很精準的預測問題的處理流程與結果,面向對象的程序一旦開始就由對象之間的交互解決問題即使是上帝也沒法預測最終結果。因而咱們常常看到一個遊戲人某一參數的修改極有可能致使陰霸的技能出現,一刀砍死3我的,這個遊戲就失去平衡。設計模式

應用場景:需求常常變化的軟件,通常需求的變化都集中在用戶層,互聯網應用,企業內部軟件,遊戲等都是面向對象的程序設計大顯身手的好地方。微信

在python 中面向對象的程序設計並非所有。網絡

面向對象編程可使程序的維護和擴展變得更簡單,而且能夠大大提升程序開發效率 ,另外,基於面向對象的程序可使它人更加容易理解你的代碼邏輯,從而使團隊開發變得更從容。

瞭解一些名詞:類、對象、實例、實例化

類:具備相同特徵的一類事物(人、狗、老虎)

對象/實例:具體的某一個事物(隔壁阿花、樓下旺財)

實例化:類——>對象的過程(這在生活中表現的不明顯,咱們在後面再慢慢解釋)

1.1類的相關知識

聲明

def functionName(args):
     '函數文檔字符串'
      函數體 


'''
class 類名:
    '類的文檔字符串'
    類體
'''

#咱們建立一個類
class Data:
    pass

屬性

class Person:   #定義一我的類
    role = 'person'  #人的角色屬性都是人
    def walk(self):  #人均可以走路,也就是有一個走路方法
        print("person is walking...")


print(Person.role)  #查看人的role屬性
print(Person.walk)  #引用人的走路方法,注意,這裏不是在調用

實例化:類名加括號就是實例化,會自動觸發__init__函數的運行,能夠用它來爲每一個實例定製本身的特徵

class Person:   #定義一我的類
    role = 'person'  #人的角色屬性都是人
    def __init__(self,name):
        self.name = name  # 每個角色都有本身的暱稱;
        
    def walk(self):  #人均可以走路,也就是有一個走路方法
        print("person is walking...")


print(Person.role)  #查看人的role屬性
print(Person.walk)  #引用人的走路方法,注意,這裏不是在調用

 

實例化的過程就是類——>對象的過程

本來咱們只有一個Person類,在這個過程當中,產生了一個egg對象,有本身具體的名字、攻擊力和生命值。

語法:對象名 = 類名(參數)

  self

self:在實例化時自動將對象/實例自己傳給__init__的第一個參數,你也能夠給他起個別的名字,可是正常人都不會這麼作。
由於你瞎改別人就不認識

  類屬性的補充

一:咱們定義的類的屬性到底存到哪裏了?有兩種方式查看
dir(類名):查出的是一個名字列表
類名.__dict__:查出的是一個字典,key爲屬性名,value爲屬性值

二:特殊的類屬性
類名.__name__# 類的名字(字符串)
類名.__doc__# 類的文檔字符串
類名.__base__# 類的第一個父類(在講繼承時會講)
類名.__bases__# 類全部父類構成的元組(在講繼承時會講)
類名.__dict__# 類的字典屬性
類名.__module__# 類定義所在的模塊
類名.__class__# 實例對應的類(僅新式類中)

1.2對象的相關知識

class 類名:
    def __init__(self,參數1,參數2):
        self.對象的屬性1 = 參數1
        self.對象的屬性2 = 參數2

    def 方法名(self):pass

    def 方法名2(self):pass

對象名 = 類名(1,2)  #對象就是實例,表明一個具體的東西
                  #類名() : 類名+括號就是實例化一個類,至關於調用了__init__方法
                  #括號裏傳參數,參數不須要傳self,其餘與init中的形參一一對應
                  #結果返回一個對象
對象名.對象的屬性1   #查看對象的屬性,直接用 對象名.屬性名 便可
對象名.方法名()     #調用類中的方法,直接用 對象名.方法名() 便可

1.3類名稱空間,對象名稱空間

建立一個類就會建立一個類的名稱空間,用來存儲類中定義的全部名字,這些名字稱爲類的屬性

而類有兩種屬性:靜態屬性和動態屬性

  • 靜態屬性就是直接在類中定義的變量
  • 動態屬性就是定義在類中的方法

其中類的數據屬性是共享給全部對象的

>>>id(egg.role)
4341594072
>>>id(Person.role)
4341594072

而類的動態屬性是綁定到全部對象的

>>>egg.attack
<bound method Person.attack of <__main__.Person object at 0x101285860>>
>>>Person.attack
<function Person.attack at 0x10127abf8> 

建立一個對象/實例就會建立一個對象/實例的名稱空間,存放對象/實例的名字,稱爲對象/實例的屬性

在obj.name會先從obj本身的名稱空間裏找name,找不到則去類中找,類也找不到就找父類...最後都找不到就拋出異常

 

二.面向對象三大特性,繼承,多態,封裝

2.1繼承

繼承是一種建立新類的方式,在python中,新建的類能夠繼承一個或多個父類,父類又可稱爲基類或超類,新建的類稱爲派生類或子類

python中類的繼承分爲:單繼承和多繼承

lass ParentClass1: #定義父類
    pass

class ParentClass2: #定義父類
    pass

class SubClass1(ParentClass1): #單繼承,基類是ParentClass1,派生類是SubClass
    pass

class SubClass2(ParentClass1,ParentClass2): #python支持多繼承,用逗號分隔開多個繼承的類
    pass

查看繼承

>>> SubClass1.__bases__ #__base__只查看從左到右繼承的第一個子類,__bases__則是查看全部繼承的父類
(<class '__main__.ParentClass1'>,)
>>> SubClass2.__bases__
(<class '__main__.ParentClass1'>, <class '__main__.ParentClass2'>)

提示:若是沒有指定基類,python的類會默認繼承object類,object是全部python類的基類,它提供了一些常見方法(如__str__)的實現。

>>> ParentClass1.__bases__
(<class 'object'>,)
>>> ParentClass2.__bases__
(<class 'object'>,)

繼承的重要性

==========================第一部分
例如

  貓能夠:爬樹、吃、喝、拉、撒

  狗能夠:看門、吃、喝、拉、撒

若是咱們要分別爲貓和狗建立一個類,那麼就須要爲 貓 和 狗 實現他們全部的功能,僞代碼以下:
 

#貓和狗有大量相同的內容
class 貓:

    def爬樹(self):
        print '爬樹'

    def 吃(self):
        # do something

    def 喝(self):
        # do something

    def 拉(self):
        # do something

    def 撒(self):
        # do something

class 狗:

    def 看門(self):
        print '看門'

    def 吃(self):
        # do something

    def 喝(self):
        # do something

    def 拉(self):
        # do something

    def 撒(self):
        # do something



==========================第二部分
上述代碼不難看出,吃、喝、拉、撒是貓和狗都具備的功能,而咱們卻分別的貓和狗的類中編寫了兩次。若是使用 繼承 的思想,以下實現:

  動物:吃、喝、拉、撒

     貓:爬樹(貓繼承動物的功能)

     狗:看門(狗繼承動物的功能)

僞代碼以下:
class 動物:

    def 吃(self):
        # do something

    def 喝(self):
        # do something

    def 拉(self):
        # do something

    def 撒(self):
        # do something

# 在類後面括號中寫入另一個類名,表示當前類繼承另一個類
class 貓(動物):

    def爬樹(self):
        print '爬樹'

# 在類後面括號中寫入另一個類名,表示當前類繼承另一個類
class 狗(動物):

     def 看門(self):
        print '看門'




==========================第三部分
#繼承的代碼實現
class Animal:

    def eat(self):
        print("%s 吃 " %self.name)

    def drink(self):
        print ("%s 喝 " %self.name)

    def shit(self):
        print ("%s 拉 " %self.name)

    def pee(self):
        print ("%s 撒 " %self.name)


class Cat(Animal):

    def __init__(self, name):
        self.name = name
        self.breed = ''

    def爬樹(self):
        print '爬樹'

class Dog(Animal):

    def __init__(self, name):
        self.name = name
        self.breed=''
  
    def 看門(self):
        print '看門'



# ######### 執行 #########

c1 = Cat('小白家的小黑貓')
c1.eat()

c2 = Cat('小黑的小白貓')
c2.drink()

d1 = Dog('胖子家的小瘦狗')
d1.eat()

使用繼承來重用代碼比較好的例子
減小代碼重複,提升代碼複用性

那麼問題又來了,多繼承呢?

  • 是否能夠繼承多個類
  • 若是繼承的多個類每一個類中都定了相同的函數,那麼那一個會被使用呢?

一、Python的類能夠繼承多個類,Java和C#中則只能繼承一個類

二、Python的類若是繼承了多個類,那麼其尋找方法的方式有兩種,分別是:深度優先廣度優先

  • 當類是經典類時,多繼承狀況下,會按照深度優先方式查找
  • 當類是新式類時,多繼承狀況下,會按照廣度優先方式查找

經典類和新式類,從字面上能夠看出一個老一個新,新的必然包含了跟多的功能,也是以後推薦的寫法,從寫法上區分的話,若是 當前類或者父類繼承了object類,那麼該類即是新式類,不然即是經典類。

class D:

    def bar(self):
        print 'D.bar'


class C(D):

    def bar(self):
        print 'C.bar'


class B(D):

    def bar(self):
        print 'B.bar'


class A(B, C):

    def bar(self):
        print 'A.bar'

a = A()
# 執行bar方法時
# 首先去A類中查找,若是A類中沒有,則繼續去B類中找,若是B類中麼有,則繼續去D類中找,若是D類中麼有,則繼續去C類中找,若是仍是未找到,則報錯
# 因此,查找順序:A --> B --> D --> C
# 在上述查找bar方法的過程當中,一旦找到,則尋找過程當即中斷,便不會再繼續找了
a.bar()

經典類多繼承
經典類多繼承
class D(object):

    def bar(self):
        print 'D.bar'


class C(D):

    def bar(self):
        print 'C.bar'


class B(D):

    def bar(self):
        print 'B.bar'


class A(B, C):

    def bar(self):
        print 'A.bar'

a = A()
# 執行bar方法時
# 首先去A類中查找,若是A類中沒有,則繼續去B類中找,若是B類中麼有,則繼續去C類中找,若是C類中麼有,則繼續去D類中找,若是仍是未找到,則報錯
# 因此,查找順序:A --> B --> C --> D
# 在上述查找bar方法的過程當中,一旦找到,則尋找過程當即中斷,便不會再繼續找了
a.bar()

新式類多繼承
新式類多繼承

經典類:首先去A類中查找,若是A類中沒有,則繼續去B類中找,若是B類中麼有,則繼續去D類中找,若是D類中麼有,則繼續去C類中找,若是仍是未找到,則報錯

新式類:首先去A類中查找,若是A類中沒有,則繼續去B類中找,若是B類中麼有,則繼續去C類中找,若是C類中麼有,則繼續去D類中找,若是仍是未找到,則報錯

注意:在上述查找過程當中,一旦找到,則尋找過程當即中斷,便不會再繼續找了

 

抽象類與接口類

接口類

繼承有兩種用途:

一:繼承基類的方法,而且作出本身的改變或者擴展(代碼重用)  

二:聲明某個子類兼容於某基類,定義一個接口類Interface,接口類中定義了一些接口名(就是函數名)且並未實現接口的功能,子類繼承接口類,而且實現接口中的功能

# 一:這樣很差,我要統一一下支付的規則.

class QQpay:
    def pay(self,money):
        print('使用qq支付%s元' % money)

class Alipay:
    def pay(self,money):
        print('使用阿里支付%s元' % money)

a = Alipay()
a.pay(100)

b = QQpay()
b.pay(200)

# 二,統一支付的規則 歸一化設計,統一 pay接口
class QQpay:
    def pay(self,money):
        print('使用qq支付%s元' % money)

class Alipay:
    def pay(self,money):
        print('使用阿里支付%s元' % money)

def pay(obj,money):
    obj.pay(money)

a = Alipay()
b = QQpay()

pay(a,100)
pay(b,200)

# 三,可是,來了一個野生程序員,他不知道你的約定俗成的規則,就會出問題

class QQpay:
    def pay(self,money):
        print('使用qq支付%s元' % money)

class Alipay:
    def pay(self,money):
        print('使用阿里支付%s元' % money)

class Wechatpay:
    def fuqian(self,money):
        print('使用微信支付%s元' % money)

def pay(obj,money):
    obj.pay(money)

a = Alipay()
b = QQpay()

pay(a,100)
pay(b,200)

c = Wechatpay()
c.fuqian(300)

# 四,解決方式
# 定義一個父類,什麼都不寫,只是要求繼承個人全部類有一個pay方法,這樣就制定了一個規範,這就叫作接口類,後者抽象類.
class Payment:
    def pay(self):pass

class QQpay(Payment):
    def pay(self,money):
        print('使用qq支付%s元' % money)

class Alipay(Payment):
    def pay(self,money):
        print('使用阿里支付%s元' % money)

class Wechatpay(Payment):
    def fuqian(self,money):
        print('使用微信支付%s元' % money)


def pay(obj,money):
    obj.pay(money)

a = Alipay()
b = QQpay()

pay(a,100)
pay(b,200)

c = Wechatpay()
c.fuqian(300)

#五,他仍是不知道看你這些都繼承了一個類,因此你要制定一個規範,強制他執行.
# 建立一個規範
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,
# 那麼這個類就變成了一個抽象類(接口類)
# 這個類的主要功能就是創建一個規範
接口類事例

實踐中,繼承的第一種含義意義並不很大,甚至經常是有害的。由於它使得子類與基類出現強耦合。

繼承的第二種含義很是重要。它又叫「接口繼承」。
接口繼承實質上是要求「作出一個良好的抽象,這個抽象規定了一個兼容接口,使得外部調用者無需關心具體細節,可一視同仁的處理實現了特定接口的全部對象」——這在程序設計上,叫作歸一化。

歸一化使得高層的外部使用者能夠不加區分的處理全部接口兼容的對象集合——就好象linux的泛文件概念同樣,全部東西均可以當文件處理,沒必要關心它是內存、磁盤、網絡仍是屏幕(固然,對底層設計者,固然也能夠區分出「字符設備」和「塊設備」,而後作出針對性的設計:細緻到什麼程度,視需求而定)。

依賴倒置原則:
高層模塊不該該依賴低層模塊,兩者都應該依賴其抽象;抽象不該該應該依賴細節;細節應該依賴抽象。換言之,要針對接口編程,而不是針對實現編程

在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中實現抽象類

抽象類與接口類

抽象類的本質仍是類,指的是一組類的類似性,包括數據屬性(如all_type)和函數屬性(如read、write),而接口只強調函數屬性的類似性。

抽象類是一個介於類和接口直接的一個概念,同時具有類和接口的部分特性,能夠用來實現歸一化設計 

在python中,並無接口類這種東西,即使不經過專門的模塊定義接口,咱們也應該有一些基本的概念。

1.多繼承問題

在繼承抽象類的過程當中,咱們應該儘可能避免多繼承;
而在繼承接口的時候,咱們反而鼓勵你來多繼承接口

接口隔離原則:
使用多個專門的接口,而不使用單一的總接口。即客戶端不該該依賴那些不須要的接口。

2.方法的實現

在抽象類中,咱們能夠對一些抽象方法作出基礎實現;
而在接口類中,任何方法都只是一種規範,具體的功能須要子類實現

ps:組合

面向對象的組合用法

軟件重用的重要方式除了繼承以外還有另一種方式,即:組合

組合指的是,在一個類中以另一個類的對象做爲數據屬性,稱爲類的組合

class Person:

    def __init__(self,nickname,sex,hp,ad):
        self.nickname = nickname
        self.sex = sex
        self.hp = hp
        self.ad = ad

    def attack(self,p1):
        p1.hp -= self.ad
        print('%s攻擊了%s,%s還剩%s血量'%(self.nickname,p1.nickname,p1.nickname,p1.hp))

    def weapon_attack(self,wea):
        # 武器類的對象封裝到人的對象中當作一個屬性.就叫作組合.
        self.weapon = wea

class Weapon:

    def __init__(self,name,att):
        self.name = name
        self.att = att

    def Aux_attack(self,p,p1):
        p1.hp -= self.att
        print('%s利用%s打了%s%s滴血,%s還剩%s滴血' %(p.nickname,self.name,p1.nickname,self.att,p1.nickname,p1.hp))


# alex = Person('alex','男',100,20)
# barry = Person('太白','男',200,50)
# axe = Weapon('斧子',30)
# barry.weapon_attack(axe)
# barry.weapon.Aux_attack(barry,alex)

# axe.Aux_attack(alex)
# alex.attack(barry)
# alex.attack(barry)

圓環是由兩個圓組成的,圓環的面積是外面圓的面積減去內部圓的面積。圓環的周長是內部圓的周長加上外部圓的周長。
這個時候,咱們就首先實現一個圓形類,計算一個圓的周長和麪積。而後在"環形類"中組合圓形的實例做爲本身的屬性來用

from math import pi

class Circle:
    '''
    定義了一個圓形類;
    提供計算面積(area)和周長(perimeter)的方法
    '''
    def __init__(self,radius):
        self.radius = radius

    def area(self):
         return pi * self.radius * self.radius

    def perimeter(self):
        return 2 * pi *self.radius


circle =  Circle(10) #實例化一個圓
area1 = circle.area() #計算圓面積
per1 = circle.perimeter() #計算圓周長
print(area1,per1) #打印圓面積和周長

class Ring:
    '''
    定義了一個圓環類
    提供圓環的面積和周長的方法
    '''
    def __init__(self,radius_outside,radius_inside):
        self.outsid_circle = Circle(radius_outside)
        self.inside_circle = Circle(radius_inside)

    def area(self):
        return self.outsid_circle.area() - self.inside_circle.area()

    def perimeter(self):
        return  self.outsid_circle.perimeter() + self.inside_circle.perimeter()


ring = Ring(10,5) #實例化一個環形
print(ring.perimeter()) #計算環形的周長
print(ring.area()) #計算環形的面積

用組合的方式創建了類與組合的類之間的關係,它是一種‘有’的關係,好比教授有生日,教授教python課程

lass BirthDate:
    def __init__(self,year,month,day):
        self.year=year
        self.month=month
        self.day=day

class Couse:
    def __init__(self,name,price,period):
        self.name=name
        self.price=price
        self.period=period

class Teacher:
    def __init__(self,name,gender,birth,course):
        self.name=name 
        self.gender=gender
        self.birth=birth
        self.course=course
    def teach(self): 
        print('teaching')
p1=Teacher('egon','male', 
            BirthDate('1995','1','27'), 
            Couse('python','28000','4 months')
           ) 

print(p1.birth.year,p1.birth.month,p1.birth.day) 

print(p1.course.name,p1.course.price,p1.course.period)
''' 
運行結果: 
1 27 
python 28000 4 months 
'''

當類之間有顯著不一樣,而且較小的類是較大的類所須要的組件時,用組合比較好

 

2.2多態

Pyhon不支持Java和C#這一類強類型語言中多態的寫法,可是原生多態,其Python崇尚「鴨子類型」

python到處是多態

class F1:
    pass


class S1(F1):

    def show(self):
        print 'S1.show'


class S2(F1):

    def show(self):
        print 'S2.show'

def Func(obj):
    print obj.show()

s1_obj = S1()
Func(s1_obj) 

s2_obj = S2()
Func(s2_obj) 

Python 「鴨子類型」
鴨子類型

2.3 封裝

封裝,顧名思義就是將內容封裝到某個地方,之後再去調用被封裝在某處的內容。

因此,在使用面向對象的封裝特性時,須要:

  • 將內容封裝到某處
  • 從某處調用被封裝的內容

第一步:將內容封裝到某處

self 是一個形式參數,當執行 obj1 = Foo('wupeiqi', 18 ) 時,self 等於 obj1

                              當執行 obj2 = Foo('alex', 78 ) 時,self 等於 obj2

因此,內容其實被封裝到了對象 obj1 和 obj2 中,每一個對象中都有 name 和 age 屬性,在內存裏相似於下圖來保存。

第二步:從某處調用被封裝的內容

調用被封裝的內容時,有兩種狀況:

  • 經過對象直接調用
  • 經過self間接調用

一、經過對象直接調用被封裝的內容

上圖展現了對象 obj1 和 obj2 在內存中保存的方式,根據保存格式能夠如此調用被封裝的內容:對象.屬性名

class Foo:
 
    def __init__(self, name, age):
        self.name = name
        self.age = age
 
obj1 = Foo('wupeiqi', 18)
print obj1.name    # 直接調用obj1對象的name屬性
print obj1.age     # 直接調用obj1對象的age屬性
 
obj2 = Foo('alex', 73)
print obj2.name    # 直接調用obj2對象的name屬性
print obj2.age     # 直接調用obj2對象的age屬性

二、經過self間接調用被封裝的內容

執行類中的方法時,須要經過self間接調用被封裝的內容

class Foo:
  
    def __init__(self, name, age):
        self.name = name
        self.age = age
  
    def detail(self):
        print self.name
        print self.age
  
obj1 = Foo('wupeiqi', 18)
obj1.detail()  # Python默認會將obj1傳給self參數,即:obj1.detail(obj1),因此,此時方法內部的 self = obj1,即:self.name 是 wupeiqi ;self.age 是 18
  
obj2 = Foo('alex', 73)
obj2.detail()  # Python默認會將obj2傳給self參數,即:obj1.detail(obj2),因此,此時方法內部的 self = obj2,即:self.name 是 alex ; self.age 是 78

綜上所述,對於面向對象的封裝來講,其實就是使用構造方法將內容封裝到 對象 中,而後經過對象直接或者self間接獲取被封裝的內容。

封裝與擴展性

封裝在於明確區份內外,使得類實現者能夠修改封裝內的東西而不影響外部調用者的代碼;而外部使用用者只知道一個接口(函數),只要接口(函數)名、參數不變,使用者的代碼永遠無需改變。這就提供一個良好的合做基礎——或者說,只要接口這個基礎約定不變,則代碼改變不足爲慮。

#類的設計者
class Room:
    def __init__(self,name,owner,width,length,high):
        self.name=name
        self.owner=owner
        self.__width=width
        self.__length=length
        self.__high=high
    def tell_area(self): #對外提供的接口,隱藏了內部的實現細節,此時咱們想求的是面積
        return self.__width * self.__length


#使用者
>>> r1=Room('臥室','egon',20,20,20)
>>> r1.tell_area() #使用者調用接口tell_area


#類的設計者,輕鬆的擴展了功能,而類的使用者徹底不須要改變本身的代碼
class Room:
    def __init__(self,name,owner,width,length,high):
        self.name=name
        self.owner=owner
        self.__width=width
        self.__length=length
        self.__high=high
    def tell_area(self): #對外提供的接口,隱藏內部實現,此時咱們想求的是體積,內部邏輯變了,只需求修該下列一行就能夠很簡答的實現,並且外部調用感知不到,仍然使用該方法,可是功能已經變了
        return self.__width * self.__length * self.__high


#對於仍然在使用tell_area接口的人來講,根本無需改動本身的代碼,就能夠用上新功能
>>> r1.tell_area()
相關文章
相關標籤/搜索