day 20 面對對象(一)

面向對象編程介紹(1)

想一想

下面這個項目能不能正常完成

  • 你是萬達的老闆,現在要新建一個萬達商場,然後招了一百個工人,說明了你是要蓋一棟樓,把人往工地上一扔,讓工人開始幹活。

項目肯定是不能完成的,因爲沒有分工。工地上可以做的事情很多,可能一件簡單又安全的工作同時有多個人去做,而那些複雜又危險的工作,可能到最後都沒有人做。

必須要進行分工,哪些人是搭架子,哪些人是負責吊車,哪些人是混水泥。

請數一下下面兩個字符串分別有多少個字符

  • ehihhsqowoqdqiudhoqq
  • ehihh,sqowo,qdqiu,dhoqq

可以看到被分組後的字符串更容易計數

公司裏處理的都是大型項目,可能有上萬個函數,需要多個程序員合作開發。如果沒有分工的話,很可能一個簡單的功能,每個程序員都自己寫了一個的函數,而特別難的功能沒有人願意去做。

總結:大型的項目,必須要進行分工,將函數分爲幾個不同的類型,每個人負責一個或多個類型,比如一個人負責網站首頁,一個人負責訂單界面,一個人負責用戶設置頁面。

面向對象編程介紹(2)

  • 面向過程:根據業務邏輯從上到下寫代碼
  • 面向對象:將數據與函數綁定到一起,分類進行封裝,每個程序員只要負責分配給自己的分類,這樣能夠更快速的開發程序,減少了重複代碼

面向過程編程最易被初學者接受,其往往用一長段代碼來實現指定功能,開發過程的思路是將數據與函數按照執行的邏輯順序組織在一起,數據與函數分開考慮。

今天我們來學習一種新的編程方式:面向對象編程(Object Oriented Programming,OOP,面向對象程序設計)

解決菜鳥買電腦的問題

第一種方式:

1)在網上查找資料

2)根據自己預算和需求定電腦的型號 MacBook 15 頂配 1W8

3)去市場找到蘋果店各種店無法甄別真假 隨便找了一家

4)找到業務員,業務員推薦了另外一款 配置更高價格便宜,也是蘋果系統的,價格 1W

5)砍價30分鐘 付款9999

6)成交

回去之後發現各種問題 

第二種方式 :

1)找一個靠譜的電腦高手

2)給錢交易
  • 面向對象和麪向過程都是解決問題的一種思路而已

    • 買電腦的第一種方式:

      • 強調的是步驟、過程、每一步都是自己親自去實現的
      • 這種解決問題的思路我們就叫做面向過程
    • 買電腦的第二種方式:

      • 強調的是電腦高手, 電腦高手是處理這件事的主角,對我們而言,我們並不必親自實現整個步驟只需要調用電腦高手就可以解決問題
      • 這種解決問題的思路就 是面向對象
    • 用面向對象的思維解決問題的重點

      • 當遇到一個需求的時候不用自己去實現,如果自己一步步實現那就是面向過程
      • 應該找一個專門做這個事的人來做
      • 面向對象是基於面向過程的

解決喫啤酒鴨的問題

第一種方式(面向過程):

1)養鴨子

2)鴨子長成

3)殺

4)作料

5)烹飪

6)喫 

7)卒 

第二種方式(面向對象):

1)找個賣啤酒鴨的人 

2)給錢 交易

3)喫

需要了解的定義性文字:

面向對象(object-oriented ;簡稱: OO) 至今還沒有統一的概念 我這裏把它定義爲: 按人們 認識客觀世界的系統思維方式,採用基於對象(實體)的概念建立模型,模擬客觀世界分析、設 計、實現軟件的辦法。

面向對象編程(Object Oriented Programming-OOP) 是一種解決軟件複用的設計和編程方法。 這種方法把軟件系統中相近相似的操作邏輯和操作 應用數據、狀態,以類的型式描述出來,以對象實例的形式在軟件系統中複用,以達到提高軟件開發效率的作用。

類和對象

面向對象編程的2個非常重要的概念:類和對象

對象是面向對象編程的核心,在使用對象的過程中,爲了將具有共同特徵和行爲的一組對象抽象定義,提出了另外一個新的概念——類

類就相當於製造飛機時的圖紙,用它來進行創建的飛機就相當於對象

1. 類

人以類聚 物以羣分。
具有相似內部狀態和運動規律的實體的集合(或統稱爲抽象)。 
具有相同屬性和行爲事物的統稱

類是抽象的,在使用的時候通常會找到這個類的一個具體的存在,使用這個具體的存在。一個類可以找到多個對象

2. 對象

某一個具體事物的存在 ,在現實世界中可以是看得見摸得着的。

可以是直接使用的

3. 類和對象之間的關係


小總結:類就是創建對象的模板

4. 練習:區分類和對象

奔馳汽車 類
奔馳smart 類 
張三的那輛奔馳smart 對象
狗 類
大黃狗 類 
李四家那隻大黃狗 對象 
水果 類
蘋果 類 
紅蘋果 類 紅富士蘋果 類 
我嘴裏吃了一半的蘋果 對象

5. 類的構成

類(Class) 由3個部分構成

  • 類的名稱:類名
  • 類的屬性:一組數據
  • 類的方法:允許對進行操作的方法 (行爲)

<1> 舉例:

1)人類設計,只關心3樣東西:

  • 事物名稱(類名):人(Person)
  • 屬性:身高(height)、年齡(age)
  • 方法(行爲/功能):跑(run)、打架(fight)

2)狗類的設計

  • 類名:狗(Dog)
  • 屬性:品種 、毛色、性別、名字、 腿兒的數量
  • 方法(行爲/功能):叫 、跑、咬人、喫、搖尾巴

6. 類的抽象

如何把日常生活中的事物抽象成程序中的類?

擁有相同(或者類似)屬性和行爲的對象都可以抽像出一個類

方法:一般名詞都是類(名詞提煉法)

<1> 坦克發射3顆炮彈轟掉了2架飛機

  • 坦克--》可以抽象成 類
  • 炮彈--》可以抽象成類
  • 飛機-》可以抽象成類

<2> 小明在公車上牽着一條叼着熱狗的狗

  • 小明--》 人類
  • 公車--》 交通工具類
  • 熱狗--》 食物類
  • 狗--》 狗類

<3>【想一想】如下圖中,有哪些類呢?

說明:

  • 向日葵
    • 類名: xrk
    • 屬性:
    • 行爲: 放陽光
  • 豌豆
    • 類名: wd
    • 屬性: 顏色 、髮型,血量
    • 行爲:發炮, 搖頭
  • 堅果:
    • 類名:jg
    • 屬性:血量 類型
    • 行爲:阻擋;
  • 殭屍:
    • 類名:js
    • 屬性:顏色、血量、 類型、速度
    • 行爲:走 跑跳 喫 死

定義類

定義一個類,格式如下:

class 類名:
    方法列表

demo:定義一個Hero類

# class Hero:  # 經典類(舊式類)定義形式
# class Hero():

class Hero(object):  # 新式類定義形式
    def info(self):
        print("英雄各有見,何必問出處。")

說明:

  • 定義類時有2種形式:新式類和經典類,上面代碼中的Hero爲新式類,前兩行註釋部分則爲經典類;

  • object 是Python 裏所有類的最頂級父類;

  • 類名 的命名規則按照"大駝峯命名法";

  • info 是一個實例方法,第一個參數一般是self,表示實例對象本身,當然了可以將self換爲其它的名字,其作用是一個變量 這個變量指向了實例對象

創建對象

python中,可以根據已經定義的類去創建出一個或多個對象。

創建對象的格式爲:

對象名1 = 類名()
對象名2 = 類名()
對象名3 = 類名()

創建對象demo:

class Hero(object):  # 新式類定義形式
    """info 是一個實例方法,類對象可以調用實例方法,實例方法的第一個參數一定是self"""
    def info(self):
        """當對象調用實例方法時,Python會自動將對象本身的引用做爲參數,
            傳遞到實例方法的第一個參數self裏"""
        print(self) 
        print("self各不同,對象是出處。")


# Hero這個類 實例化了一個對象  taidamier(泰達米爾)
taidamier = Hero()

# 對象調用實例方法info(),執行info()裏的代碼
# . 表示選擇屬性或者方法
taidamier.info()

print(taidamier)  # 打印對象,則默認打印對象在內存的地址,結果等同於info裏的print(self)
print(id(taidamier))  # id(taidamier) 則是內存地址的十進制形式表示

說明:

  • 當創建一個對象時,就是用一個模子,來製造一個實物 

問題:

對象既然有實例方法,是否也可以有自己的屬性?

添加和獲取對象的屬性

class Hero(object):
    """定義了一個英雄類,可以移動和攻擊"""
    def move(self):
        """實例方法"""
        print("正在前往事發地點...")

    def attack(self):
        """實例方法"""
        print("發出了一招強力的普通攻擊...")

# 實例化了一個英雄對象 泰達米爾
taidamier = Hero()

# 給對象添加屬性,以及對應的屬性值
taidamier.name = "泰達米爾"  # 姓名
taidamier.hp = 2600  # 生命值
taidamier.atk = 450  # 攻擊力
taidamier.armor = 200  # 護甲值

# 通過.成員選擇運算符,獲取對象的屬性值
print("英雄 %s 的生命值 :%d" % (taidamier.name, taidamier.hp))
print("英雄 %s 的攻擊力 :%d" % (taidamier.name, taidamier.atk))
print("英雄 %s 的護甲值 :%d" % (taidamier.name, taidamier.armor))

# 通過.成員選擇運算符,獲取對象的實例方法
taidamier.move()
taidamier.attack()

問題:

對象創建並添加屬性後,能否在類的實例方法裏獲取這些屬性呢?如果可以的話,應該通過什麼方式?

在方法內通過self獲取對象屬性

class Hero(object):
    """定義了一個英雄類,可以移動和攻擊"""
    def move(self):
        """實例方法"""
        print("正在前往事發地點...")

    def attack(self):
        """實例方法"""
        print("發出了一招強力的普通攻擊...")

    def info(self):
        """在類的實例方法中,通過self獲取該對象的屬性"""
        print("英雄 %s 的生命值 :%d" % (self.name, self.hp))
        print("英雄 %s 的攻擊力 :%d" % (self.name, self.atk))
        print("英雄 %s 的護甲值 :%d" % (self.name, self.armor))


# 實例化了一個英雄對象 泰達米爾
taidamier = Hero()

# 給對象添加屬性,以及對應的屬性值
taidamier.name = "泰達米爾"  # 姓名
taidamier.hp = 2600  # 生命值
taidamier.atk = 450  # 攻擊力
taidamier.armor = 200  # 護甲值

# 通過.成員選擇運算符,獲取對象的實例方法
taidamier.info()  # 只需要調用實例方法info(),即可獲取英雄的屬性
taidamier.move()
taidamier.attack()

問題:

創建對象後再去添加屬性有點不合適,有沒有簡單的辦法,可以在創建對象的時候,就已經擁有這些屬性?

__init__()方法


class Hero(object):
    """定義了一個英雄類,可以移動和攻擊"""
    # Python 的類裏提供的,兩個下劃線開始,兩個下劃線結束的方法,就是魔法方法,__init__()就是一個魔法方法,通常用來做屬性初始化 或 賦值 操作。
    # 如果類面沒有寫__init__方法,Python會自動創建,但是不執行任何操作,
    # 如果爲了能夠在完成自己想要的功能,可以自己定義__init__方法,
    # 所以一個類裏無論自己是否編寫__init__方法 一定有__init__方法。

    def __init__(self):
        """ 方法,用來做變量初始化 或 賦值 操作,在類實例化對象的時候,會被自動調用"""
        self.name = "泰達米爾" # 姓名
        self.hp = 2600 # 生命值
        self.atk = 450  # 攻擊力
        self.armor = 200  # 護甲值

    def move(self):
        """實例方法"""
        print("正在前往事發地點...")

    def attack(self):
        """實例方法"""
        print("發出了一招強力的普通攻擊...")


# 實例化了一個英雄對象,並自動調用__init__()方法
taidamier = Hero()

# 通過.成員選擇運算符,獲取對象的實例方法
taidamier.info() # 只需要調用實例方法info(),即可獲取英雄的屬性
taidamier.move()
taidamier.attack()

說明:

  • __init__()方法,在創建一個對象時默認被調用,不需要手動調用
  • __init__(self)中的self參數,不需要開發者傳遞,python解釋器會自動把當前的對象引用傳遞過去。

問題:

在類的方法裏定義屬性的固定值,則每個對象實例變量的屬性值都是相同的。

一個遊戲裏往往有很多不同的英雄,能否讓實例化的每個對象,都有不同的屬性值呢?

有參數的__init__()方法

class Hero(object):
    """定義了一個英雄類,可以移動和攻擊"""

    def __init__(self, name, skill, hp, atk, armor):
        """ __init__() 方法,用來做變量初始化 或 賦值 操作"""
        # 英雄名
        self.name = name
        # 技能
        self.skill = skill
        # 生命值:
        self.hp = hp
        # 攻擊力
        self.atk = atk
        # 護甲值
        self.armor = armor

    def move(self):
        """實例方法"""
        print("%s 正在前往事發地點..." % self.name)

    def attack(self):
        """實例方法"""
        print("發出了一招強力的%s..." % self.skill)

    def info(self):
        print("英雄 %s 的生命值 :%d" % (self.name, self.hp))
        print("英雄 %s 的攻擊力 :%d" % (self.name, self.atk))
        print("英雄 %s 的護甲值 :%d" % (self.name, self.armor))


# 實例化英雄對象時,參數會傳遞到對象的__init__()方法裏
taidamier = Hero("泰達米爾", "旋風斬", 2600, 450, 200)
gailun = Hero("蓋倫", "大寶劍", 4200, 260, 400)


# print(gailun)
# print(taidamier)

# 不同對象的屬性值的單獨保存
print(id(taidamier.name)) 
print(id(gailun.name))

# 同一個類的不同對象,實例方法共享
print(id(taidamier.move())) 
print(id(gailun.move()))

說明:

  • 通過一個類,可以創建多個對象,就好比 通過一個模具創建多個實體一樣

  • __init__(self)中,默認有1個參數名字爲self,如果在創建對象時傳遞了2個實參,那麼__init__(self)中出了self作爲第一個形參外還需要2個形參,例如__init__(self,x,y)

注意:

  1. 在類內部獲取 屬性 和 實例方法,通過self獲取;
  2. 在類外部獲取 屬性 和 實例方法,通過對象名獲取。

  3. 如果一個類有多個對象,每個對象的屬性是各自保存的,都有各自獨立的地址;

  4. 但是實例方法是所有對象共享的,只佔用一份內存空間。類會通過self來判斷是哪個對象調用了實例方法。

__str__()方法

class Hero(object):
    """定義了一個英雄類,可以移動和攻擊"""

    def __init__(self, name, skill, hp, atk, armor):
        """ __init__() 方法,用來做變量初始化 或 賦值 操作"""
        # 英雄名
        self.name = name  # 實例變量
        # 技能
        self.skill = skill
        # 生命值:
        self.hp = hp   # 實例變量
        # 攻擊力
        self.atk = atk
        # 護甲值
        self.armor = armor

    def move(self):
        """實例方法"""
        print("%s 正在前往事發地點..." % self.name)

    def attack(self):
        """實例方法"""
        print("發出了一招強力的%s..." % self.skill)

    # def info(self):
    #     print("英雄 %s 的生命值 :%d" % (self.name, self.hp))
    #     print("英雄 %s 的攻擊力 :%d" % (self.name, self.atk))
    #     print("英雄 %s 的護甲值 :%d" % (self.name, self.armor))


    def __str__(self):
        """
            這個方法是一個魔法方法 (Magic Method) ,用來顯示信息
            該方法需要 return 一個數據,並且只有self一個參數,當在類的外部 print(對象) 則打印這個數據
        """
        return "英雄 <%s> 數據: 生命值 %d, 攻擊力 %d, 護甲值 %d" % (self.name, self.hp, self.atk, self.armor)


taidamier = Hero("泰達米爾", "旋風斬", 2600, 450, 200)
gailun = Hero("蓋倫", "大寶劍", 4200, 260, 400)

# 如果沒有__str__ 則默認打印 對象在內存的地址。
# 當類的實例化對象 擁有 __str__ 方法後,那麼打印對象則打印 __str__ 的返回值。
print(taidamier)
print(gailun)

# 查看類的文檔說明,也就是類的註釋
print(Hero.__doc__)

說明:

  • 在python中方法名如果是__xxxx__()的,那麼就有特殊的功能,因此叫做“魔法”方法
  • 當使用print輸出對象的時候,默認打印對象的內存地址。如果類定義了__str__(self)方法,那麼就會打印從在這個方法中 return 的數據
  • __str__方法通常返回一個字符串,作爲這個對象的描述信息

__del__()方法

創建對象後,python解釋器默認調用__init__()方法;

當刪除對象時,python解釋器也會默認調用一個方法,這個方法爲__del__()方法

class Hero(object):

    # 初始化方法
    # 創建完對象後會自動被調用
    def __init__(self, name):
        print('__init__方法被調用')
        self.name = name

    # 當對象被刪除時,會自動被調用
    def __del__(self):
        print("__del__方法被調用")
        print("%s 被 GM 幹掉了..." % self.name)


# 創建對象
taidamier = Hero("泰達米爾")

# 刪除對象
print("%d 被刪除1次" % id(taidamier))
del(taidamier)


print("--" * 10)


gailun = Hero("蓋倫")
gailun1 = gailun
gailun2 = gailun

print("%d 被刪除1次" % id(gailun))
del(gailun)

print("%d 被刪除1次" % id(gailun1))
del(gailun1)

print("%d 被刪除1次" % id(gailun2))
del(gailun2)

總結

  • 當有變量保存了一個對象的引用時,此對象的引用計數就會加1;

  • 當使用del() 刪除變量指向的對象時,則會減少對象的引用計數。如果對象的引用計數不爲1,那麼會讓這個對象的引用計數減1,當對象的引用計數爲0的時候,則對象纔會被真正刪除(內存被回收)。

應用:烤地瓜

爲了更好的理解面向對象編程,下面以“烤地瓜”爲案例,進行分析

1. 分析“烤地瓜”的屬性和方法

示例屬性如下:

  • cookedLevel : 這是數字;0~3表示還是生的,超過3表示半生不熟,超過5表示已經烤好了,超過8表示已經烤成木炭了!我們的地瓜開始時時生的
  • cookedString : 這是字符串;描述地瓜的生熟程度
  • condiments : 這是地瓜的配料列表,比如番茄醬、芥末醬等

示例方法如下:

  • cook() : 把地瓜烤一段時間
  • addCondiments() : 給地瓜添加配料
  • __init__() : 設置默認的屬性
  • __str__() : 讓print的結果看起來更好一些

2. 定義類,並且定義__init__()方法

#定義`地瓜`類
class SweetPotato:
    """這是烤地瓜的類"""

    #定義初始化方法
    def __init__(self):
        self.cookedLevel = 0
        self.cookedString = "生的"
        self.condiments = []

3. 添加"烤地瓜"方法

    #烤地瓜方法
    def cook(self, time):
        self.cookedLevel += time
        if self.cookedLevel > 8:
            self.cookedString = "烤成灰了"
        elif self.cookedLevel > 5:
            self.cookedString = "烤好了"    
        elif self.cookedLevel > 3:
            self.cookedString = "半生不熟"
        else:
            self.cookedString = "生的"

4. 基本的功能已經有了一部分,趕緊測試一下

把上面2塊代碼合併爲一個程序後,在代碼的下面添加以下代碼進行測試

mySweetPotato = SweetPotato()
print(mySweetPotato.cookedLevel)
print(mySweetPotato.cookedString)
print(mySweetPotato.condiments)

完整的代碼爲:

class SweetPotato:
    """這是烤地瓜的類"""

    # 定義初始化方法
    def __init__(self):
        self.cookedLevel = 0
        self.cookedString = "生的"
        self.condiments = []

    # 烤地瓜方法
    def cook(self, time):
        self.cookedLevel += time
        if self.cookedLevel > 8:
            self.cookedString = "烤成灰了"
        elif self.cookedLevel > 5:
            self.cookedString = "烤好了"    
        elif self.cookedLevel > 3:
            self.cookedString = "半生不熟"
        else:
            self.cookedString = "生的"

# 用來進行測試
mySweetPotato = SweetPotato()
print(mySweetPotato.cookedLevel)
print(mySweetPotato.cookedString)
print(mySweetPotato.condiments)

5. 測試cook方法是否好用

在上面的代碼最後面添加如下代碼:

print("------接下來要進行烤地瓜了-----")
mySweetPotato.cook(4) #烤4分鐘
print(mySweetPotato.cookedLevel)
print(mySweetPotato.cookedString)

6. 定義addCondiments()方法和__str__()方法


    def __str__(self):
        msg = self.cookedString + " 地瓜"
        if len(self.condiments) > 0:
            msg = msg + "("
            for temp in self.condiments:
                msg = msg + temp + ", "
            msg = msg.strip(", ")

            msg = msg + ")"
        return msg

    def addCondiments(self, condiments):
        self.condiments.append(condiments)

7. 再次測試

完整的代碼如下:


class SweetPotato:
    """這是烤地瓜的類"""

    # 定義初始化方法
    def __init__(self):
        self.cookedLevel = 0
        self.cookedString = "生的"
        self.condiments = []

    # 定製print時的顯示內容
    def __str__(self):
        msg = self.cookedString + " 地瓜"
        if len(self.condiments) > 0:
            msg = msg + "("

            for temp in self.condiments:
                msg = msg + temp + ", "
            msg = msg.strip(", ")

            msg = msg + ")"
        return msg

    # 烤地瓜方法
    def cook(self, time):
        self.cookedLevel += time
        if self.cookedLevel > 8:
            self.cookedString = "烤成灰了"
        elif self.cookedLevel > 5:
            self.cookedString = "烤好了"    
        elif self.cookedLevel > 3:
            self.cookedString = "半生不熟"
        else:
            self.cookedString = "生的"

    # 添加配料
    def addCondiments(self, condiments):
        self.condiments.append(condiments)

# 用來進行測試
mySweetPotato = SweetPotato()
print("------有了一個地瓜,還沒有烤-----")
print(mySweetPotato.cookedLevel)
print(mySweetPotato.cookedString)
print(mySweetPotato.condiments)
print("------接下來要進行烤地瓜了-----")
print("------地瓜經烤了4分鐘-----")
mySweetPotato.cook(4) #烤4分鐘
print(mySweetPotato)
print("------地瓜又經烤了3分鐘-----")
mySweetPotato.cook(3) #又烤了3分鐘
print(mySweetPotato)
print("------接下來要添加配料-番茄醬------")
mySweetPotato.addCondiments("番茄醬")
print(mySweetPotato)
print("------地瓜又經烤了5分鐘-----")
mySweetPotato.cook(5) #又烤了5分鐘
print(mySweetPotato)
print("------接下來要添加配料-芥末醬------")
mySweetPotato.addCondiments("芥末醬")
print(mySweetPotato)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章