Python面向對象編程之封裝的藝術你瞭解嗎

1. 面向對象編程 

OOP ( Object Oriented Programming) 即面向對象編程。

面向對象編程是一種 編碼思想 ,或是一種 代碼組織方式 。如同編輯文章時,可以選擇 分段、分節 的方式讓文章看起來有層次、更方便閱讀或修改。

編碼時可以選擇使用 OOP 方案,也可以選擇不使用。如同行文一樣,使用或不使用都不會對 核心邏輯 產生影響。

面向對象編程有自己的核心編碼理論,對於任何一種計算機語言而言,如果選擇支持此理論, 則稱此計算機語言支持面向對象編程 。如 C++、Java、Python……

因每一種計算機語言語法上的差異性,在提供 OOP 實現時的 語法規范 會有很大的區別。除此之外,對於每一種語言而言,也可以在 OOP 基礎理論上進行語法擴展或限制。如 Python 支持多繼承。而 Java 語言隻支持單根繼承……

1.1 OOP 特點

要瞭解 OOP 的特點,可從 2 個角度進行闡述。

廣義角度:讓程序像人類解決問題一樣去解決問題,讓程序具有人的思維模式。

人類解決問題時,先是要瞭解問題域中會涉及到 哪些對象 ,然後再深入瞭解 每一個對象的特性或功能, 最後再做出相應的決策。

比如:為班級選一名班長。

選班長就是現實世界的一個問題域,如何才能選擇一名符合要求的班長?

1.首先確定此問題中涉及的對象(此處便是班上的所有學生)。

2.然後瞭解每一個學生的興趣、愛好、性格……以及個人能力等等。

3.從瞭解的群體中匹配一個符合 班長標準 的學生便可。

面向對象編程中的 對象一詞 ,便是借鑒瞭現實世界中對象概念。

狹義角度:OOP 編碼為整個程序維護帶來的優勢

OOP 組織的代碼可讓程序整體上有高度的可閱讀性,除此之外,最主要的特點是可提高代碼的 復用性、安全性、可擴展性。

任何事情都會 2 面性,OOP 會增加代碼的理解難度。

1.2 OOP 基本概念

OOP 中有兩個很重要的概念, 類和對象 。

對象從何而來?

現實世界中我們很少思考這個問題,在 選班長時 ,不會思考學生是從哪裡來的,即使思考這個問題,也會認為那是哲學傢的事情。

我們不思考現實世界中的手機、電腦、電視機是怎麼來的……因為我們不關心這個,我們關心的是使用它們所提供的功能。

如果我們思考一下手機是怎麼出現的,則會發現:

1.首先需要工程師設計手機藍圖。

2.在工廠裡根據手機藍圖進行生產(可能生產很多)。

3.用戶購買手機,瞭解手機特性和功能,使用手機。

我們身邊的諸如 電視機、洗衣機、電腦……

無不例外的需要經過這幾個過程後方能來到我們的世界。

即使是人也是女媧按自己的樣子創建出來的……

同理,電腦世界裡不會突然冒出手機、電腦、學生……如何才能讓電腦出現此類對象。一樣,先設計一個藍圖,此藍圖在電腦世界我們就稱其為 “類” 。

有瞭 “類” 之後才可以 創建手機對象 ,有瞭對象後才能在程序代碼中使用設計時為手機賦予功能完成程序邏輯。

現實世界設計手機藍圖時,需要設計手機的外觀,如大小、形狀、體重……需要賦予手機功能、如打電話、播放音樂、播放視頻、上網……

在計算機的編碼世界裡,同樣在設計類時需要為 “手機類” 設計外觀和功能。 OPP 中稱外觀為屬性,稱功能為方法。

類是藍圖,具有抽象性特征

對象是根據藍圖創建出來的個體,具有具體性、實用性特征

2. Python 實現 OOP

如需使用 OOP 理念實現程序邏輯,則需遵循如下流程:

2.1 分析問題

首先需要明確問題: 如編寫一個程序摸擬小狗的行為。

此問題中的對象便是小狗,所以程序中需要一隻小狗。

按上所述,創建小狗之前需要設計“狗類”,因此需要為類的設計提供足夠的信息。

分析可得在設計類時需要有小狗屬性:姓名、年齡,小狗的行為:尊下、打滾。

2.2 類設計語法

class Dog():
    def __init__(self, name, age):
        """初始化屬性name和age"""
        self.name = name
        self.age = age
    def sit(self):
        """小狗蹲下行為"""
        print(self.name.title() + " 乖乖的尊下瞭!")
    def roll_over(self):
        """小狗打滾"""
        print(self.name.title() + " 開始打滾哈!")

如上為 python 中類設計的結構語法:

  • 類的函數稱為方法,方法的第一個參數須是 self 關鍵字。
  • __init__ 方法是必須的,其方法名不得修改。 此方法會在創建對象時被自動調用,用來初始化對象數據。
  • self.name 聲明一個對象變量,此變量會保存對象的數據。

2.3 創建對象語法

有瞭類後,方可創建對象,有瞭對象後方可激活屬性和方法。

my_dog = Dog('小雪', 6) print("小狗的名字:"+my_dog.name.title()+".") print("小狗今年"+str(my_dog.age)+" 歲瞭")
my_dog.sit() my_dog.roll_over()

創建小狗時,需調用和 類名相同的方法 ,如上述的 Dog( ) 方法,此方法也叫 構造方法 ,此方法實質是調用瞭類設計中的 __init__ 方法。所以需要傳遞小狗的具體姓名和年齡初始 name 和 age 變量。

調用類中的方法時,不需要為方法聲明時的 self 參數傳遞值。

有瞭對象後,如需要使用此對象的數據時,可使用 . 運算符。如上 my_dog.name 得到小狗的姓名。

當然,在創建小狗後,也可以根據需要 修改小狗的姓名和年齡。

my_dog.name='小花'
my_dog.age=4

同樣,也可以使用 . 運算符調用類設計時的方法。調用方法也不需要為第一個參數 self 傳值。

運行結果:

小狗的名字:小雪.
小狗今年6 歲瞭
小雪 乖乖的尊下瞭!
小雪 開始打滾哈!

有瞭類之後,可以根據此類的設計方案,創建出多個對象。每一個對象有自己的數據空間,彼此之間的數據是獨立且隔離的。

my_dog = Dog('小黑', 6)
your_dog = Dog('小白', 3)
print("我的小狗的名字: "+my_dog.name.title()+".")
print("我的小狗的年齡 "+str(my_dog.age)+"歲瞭.")
my_dog.sit()
print("\n你的小狗的名字: "+your_dog.name.title()+".")
print("你的小狗的年齡 "+str(your_dog.age)+" 歲瞭.")
your_dog.sit()

如同現實世界一樣。現在有瞭 2 隻小狗,它們是獨立的個體。修改其中一隻狗的名字,對另一隻小狗是沒影響的。

我的小狗的名字: 小黑.
我的小狗的年齡 6歲瞭.
小黑 乖乖的尊下瞭!
 
你的小狗的名字: 小白.
你的小狗的年齡 3 歲瞭.
小白 乖乖的尊下瞭!

3. OOP 的封裝性

封裝性可以從 2 個角度上展開討論:

3.1 廣義角度:無處不封裝

類就是一個封裝體:它把 數據 以及對 數據的相關操作 方法封裝在瞭一起。

方法也是一個封裝體: 封裝瞭代碼邏輯 。

封裝的優點!

當我們通過對象使用 數據和方法 時,不需要瞭解其中的內部細節,如此實現瞭 設計和使用 的分離,和現實世界中我們使用手機一樣,不需瞭解手機的內部結構和細節。

開發者在使用 python 提供的模塊時,不需要瞭解模塊中的相關實現細節,直接使用其功能便可。

設計和使用的分離能加速工業軟件的開發效率。 

3.2 狹義角度:保證內部數據的完整性

創建一隻小狗後,可以編寫如下代碼修改小狗的年齡。

my_dog = Dog('小雪', 6)
my_dog.age=-4

顯然這是不符合實際情況的,沒有一隻小狗的年齡可以是負 4 歲。但是,現在程序可以正常運行。

小狗今年-4 歲瞭

出現這樣不合常理的想象,應該追究誰的責任。 類的設計者還是對象使用者?

我們應該要追究類設計者的責任,就如同我剛買的手機不能充電一樣,是設計者的設計缺陷引起的。

我們應該在設計類的時候提供一種內部安全檢查機制,保護 變量 能被賦予一個正確的、可靠的值。

實施流程:

1. 在變量、方法的前面加上雙下劃線(__)讓變量成為私有概念

python 的語法有很大的彈性。添加下劃性隻是一種象征性或類似於道德層面的約定。並不能真正意義上讓外部不能訪問。

class Dog():
    def __init__(self, name, age):
        """初始化屬性name和age"""
        self.name = name
        #私有化
        self.__age = age
    def sit(self):
        """小狗蹲下行為"""
        print(self.name.title() + " 乖乖的尊下瞭!")
    def roll_over(self):
        """小狗打滾"""
        print(self.name.title() + " 開始打滾哈!")

2. 在類中提供對應的 set 和 get 方法實現對內部變量的保護。

def get_age(self):
        return self.__age
    # 對數據進行檢查
    def set_age(self, age):
        if age<0:
            print("小狗的年齡不可能為負數")
            return
        self.__age = age

3. 測試

my_dog = Dog('小雪', 6)
my_dog.set_age(-4)
print("小狗的名字:"+my_dog.name.title()+".")
print("小狗今年"+str(my_dog.get_age())+" 歲瞭")

輸出結果

小狗的年齡不可能為負數
小狗的名字:小雪.
小狗今年6 歲瞭

python 還有一種更優雅的解決方案。使用註解方式。

class Dog():
    def __init__(self, name, age):
        self.name = name
        # 私有屬性,屬性名(age)前面雙下劃線的名稱
        self.__age = age
    # 實例方法
    def run(self):
        print("{} 在跑……".format(self.name))
    # 使用 @property 定義age屬性的 get 方法
    @property
    def age(self):
        return self.__age
    # 使用 @age.setter 定義 age 屬性的 set 方法必須放在@property的後面
    @age.setter
    def age(self, age):
        if age < 0:
            print("小狗的年齡不能是負數")
            return
        self.__age = age
#實例化小狗
dog = Dog("小紅", 3)
print("{0} 狗狗的年齡是 {1}".format(dog.name, dog.age))
#修改年齡
dog.age = -4
print("{0} 狗狗的年齡是 {1}".format(dog.name, dog.age))

輸出結果

小紅 狗狗的年齡是 3
小狗的年齡不能是負數
小紅 狗狗的年齡是 3

4 . 總結

面向對象編程可以用《人類簡史》中的一句話總結,人類文明的進步不一定能澤福到每一個個體。

類可以設計的很完美,但每一個對象作為個體可以有自己的命運。

封裝是面向對象編程理念中最基本也是最重要的特性,沒有封裝便沒有後續的更多。

封裝可能讓我們把相關聯的數據與方法構建成一個邏輯上的整體,也可保護內部數據的安全性,畢竟 沒有數據安全性的程序是沒有意義的。

本篇文章就到這裡瞭,希望能夠給你帶來幫助,也希望您能夠多多關註WalkonNet的更多內容!       

推薦閱讀: