分享篇助小白深入理解面向對象的學習文檔....

學習了一週的面向對象思想,感覺的確有所進步,雖然對一些知識瞭解還不是很全面,但收穫到一點點面向對象的感覺,繼續學習ing````````感謝(python100)計劃。

面向對象編程基礎

1.定義:
"把一組數據結構和處理它們的方法組成對象(object),把相同行爲的對象歸納爲類(class),通過類的封裝(encapsulation)隱藏內部細節,通過繼承(inheritance)實現類的特化(specialization)和泛化(generalization),通過多態(polymorphism)實現基於對象類型的動態分派。"

看看更通俗易懂的說法,下面這段內容來自於知乎
在這裏插入圖片描述

類和對象

簡單的說,類是對象的藍圖和模板,而對象是類的實例。這個解釋雖然有點像用概念在解釋概念,但是從這句話我們至少可以看出,類是抽象的概念,而對象是具體的東西。在面向對象編程的世界中,一切皆爲對象,對象都有屬性和行爲,每個對象都是獨一無二的,而且對象一定屬於某個類(型)。當我們把一大堆擁有共同特徵的對象的靜態特徵(屬性)和動態特徵(行爲)都抽取出來後,就可以定義出一個叫做“類”的東西。

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-KTA4akrp-1587220569227)(./res/object-feature.png)]

定義類

在Python中可以使用class關鍵字定義類,然後在類中通過之前學習過的函數來定義方法,這樣就可以將對象的動態特徵描述出來,代碼如下所示。

class Student(object):

    # __init__是一個特殊方法用於在創建對象時進行初始化操作
    # 通過這個方法我們可以爲學生對象綁定name和age兩個屬性
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def study(self, course_name):
        print('%s正在學習%s.' % (self.name, course_name))

    # PEP 8要求標識符的名字用全小寫多個單詞用下劃線連接
    # 但是部分程序員和公司更傾向於使用駝峯命名法(駝峯標識)
    def watch_movie(self):
        if self.age < 18:
            print('%s只能觀看《熊出沒》.' % self.name)
        else:
            print('%s正在觀看島國愛情大電影.' % self.name)

說明: 寫在類中的函數,我們通常稱之爲(對象的)方法,這些方法就是對象可以接收的消息。

創建和使用對象

當我們定義好一個類之後,可以通過下面的方式來創建對象並給對象發消息。

def main():
    # 創建學生對象並指定姓名和年齡
    stu1 = Student('駱昊', 38)
    # 給對象發study消息
    stu1.study('Python程序設計')
    # 給對象發watch_av消息
    stu1.watch_movie()
    stu2 = Student('王大錘', 15)
    stu2.study('思想品德')
    stu2.watch_movie()


if __name__ == '__main__':
    main()

訪問可見性問題

對於上面的代碼,有C++、Java、C#等編程經驗的程序員可能會問,我們給Student對象綁定的nameage屬性到底具有怎樣的訪問權限(也稱爲可見性)。因爲在很多面向對象編程語言中,我們通常會將對象的屬性設置爲私有的(private)或受保護的(protected),簡單的說就是不允許外界訪問,而對象的方法通常都是公開的(public),因爲公開的方法就是對象能夠接受的消息。在Python中,屬性和方法的訪問權限只有兩種,也就是公開的和私有的,如果希望屬性是私有的,在給屬性命名時可以用兩個下劃線作爲開頭,下面的代碼可以驗證這一點。

class Test:

    def __init__(self, foo):
        self.__foo = foo

    def __bar(self):
        print(self.__foo)
        print('__bar')


def main():
    test = Test('hello')
    # AttributeError: 'Test' object has no attribute '__bar'
    test.__bar()
    # AttributeError: 'Test' object has no attribute '__foo'
    print(test.__foo)


if __name__ == "__main__":
    main()

但是,Python並沒有從語法上嚴格保證私有屬性或方法的私密性,它只是給私有的屬性和方法換了一個名字來妨礙對它們的訪問,事實上如果你知道更換名字的規則仍然可以訪問到它們,下面的代碼就可以驗證這一點。之所以這樣設定,可以用這樣一句名言加以解釋,就是"We are all consenting adults here"。因爲絕大多數程序員都認爲開放比封閉要好,而且程序員要自己爲自己的行爲負責。

class Test:

    def __init__(self, foo):
        self.__foo = foo

    def __bar(self):
        print(self.__foo)
        print('__bar')


def main():
    test = Test('hello')
    test._Test__bar()
    print(test._Test__foo)


if __name__ == "__main__":
    main()

在實際開發中,我們並不建議將屬性設置爲私有的,因爲這會導致子類無法訪問(後面會講到)。所以大多數Python程序員會遵循一種命名慣例就是讓屬性名以單下劃線開頭來表示屬性是受保護的,本類之外的代碼在訪問這樣的屬性時應該要保持慎重。這種做法並不是語法上的規則,單下劃線開頭的屬性和方法外界仍然是可以訪問的,所以更多的時候它是一種暗示或隱喻,關於這一點可以看看我的《Python - 那些年我們踩過的那些坑》文章中的講解。

面向對象的支柱

面向對象有三大支柱:封裝、繼承和多態。後面兩個概念在下一個章節中進行詳細的說明,這裏我們先說一下什麼是封裝。我自己對封裝的理解是"隱藏一切可以隱藏的實現細節,只向外界暴露(提供)簡單的編程接口"。我們在類中定義的方法其實就是把數據和對數據的操作封裝起來了,在我們創建了對象之後,只需要給對象發送一個消息(調用方法)就可以執行方法中的代碼,也就是說我們只需要知道方法的名字和傳入的參數(方法的外部視圖),而不需要知道方法內部的實現細節(方法的內部視圖)。

練習

練習1:定義一個類描述數字時鐘。

參考答案:

from time import sleep


class Clock(object):
    """數字時鐘"""

    def __init__(self, hour=0, minute=0, second=0):
        """初始化方法

        :param hour: 時
        :param minute: 分
        :param second: 秒
        """
        self._hour = hour
        self._minute = minute
        self._second = second

    def run(self):
        """走字"""
        self._second += 1
        if self._second == 60:
            self._second = 0
            self._minute += 1
            if self._minute == 60:
                self._minute = 0
                self._hour += 1
                if self._hour == 24:
                    self._hour = 0

    def show(self):
        """顯示時間"""
        return '%02d:%02d:%02d' % \
               (self._hour, self._minute, self._second)


def main():
    clock = Clock(23, 59, 58)
    while True:
        print(clock.show())
        sleep(1)
        clock.run()


if __name__ == '__main__':
    main()

練習2:定義一個類描述平面上的點並提供移動點和計算到另一個點距離的方法。

參考答案:

from math import sqrt


class Point(object):

    def __init__(self, x=0, y=0):
        """初始化方法
        
        :param x: 橫座標
        :param y: 縱座標
        """
        self.x = x
        self.y = y

    def move_to(self, x, y):
        """移動到指定位置
        
        :param x: 新的橫座標
        "param y: 新的縱座標
        """
        self.x = x
        self.y = y

    def move_by(self, dx, dy):
        """移動指定的增量
        
        :param dx: 橫座標的增量
        "param dy: 縱座標的增量
        """
        self.x += dx
        self.y += dy

    def distance_to(self, other):
        """計算與另一個點的距離
        
        :param other: 另一個點
        """
        dx = self.x - other.x
        dy = self.y - other.y
        return sqrt(dx ** 2 + dy ** 2)

    def __str__(self):
        return '(%s, %s)' % (str(self.x), str(self.y))


def main():
    p1 = Point(3, 5)
    p2 = Point()
    print(p1)
    print(p2)
    p2.move_by(-1, 2)
    print(p2)
    print(p1.distance_to(p2))


if __name__ == '__main__':
    main()

說明: 本章中的插圖來自於Grady Booch等著作的《面向對象分析與設計》一書,該書是講解面向對象編程的經典著作,有興趣的讀者可以購買和閱讀這本書來了解更多的面向對象的相關知識。

面向對象進階

在前面的章節我們已經瞭解了面向對象的入門知識,知道了如何定義類,如何創建對象以及如何給對象發消息。爲了能夠更好的使用面向對象編程思想進行程序開發,我們還需要對Python中的面向對象編程進行更爲深入的瞭解。

@property裝飾器

之前我們討論過Python中屬性和方法訪問權限的問題,雖然我們不建議將屬性設置爲私有的,但是如果直接將屬性暴露給外界也是有問題的,比如我們沒有辦法檢查賦給屬性的值是否有效。我們之前的建議是將屬性命名以單下劃線開頭,通過這種方式來暗示屬性是受保護的,不建議外界直接訪問,那麼如果想訪問屬性可以通過屬性的getter(訪問器)和setter(修改器)方法進行對應的操作。如果要做到這點,就可以考慮使用@property包裝器來包裝getter和setter方法,使得對屬性的訪問既安全又方便,代碼如下所示。

class Person(object):

    def __init__(self, name, age):
        self._name = name
        self._age = age

    # 訪問器 - getter方法
    @property
    def name(self):
        return self._name

    # 訪問器 - getter方法
    @property
    def age(self):
        return self._age

    # 修改器 - setter方法
    @age.setter
    def age(self, age):
        self._age = age

    def play(self):
        if self._age <= 16:
            print('%s正在玩飛行棋.' % self._name)
        else:
            print('%s正在玩鬥地主.' % self._name)


def main():
    person = Person('王大錘', 12)
    person.play()
    person.age = 22
    person.play()
    # person.name = '白元芳'  # AttributeError: can't set attribute


if __name__ == '__main__':
    main()

__slots__魔法

我們講到這裏,不知道大家是否已經意識到,Python是一門動態語言。通常,動態語言允許我們在程序運行時給對象綁定新的屬性或方法,當然也可以對已經綁定的屬性和方法進行解綁定。但是如果我們需要限定自定義類型的對象只能綁定某些屬性,可以通過在類中定義__slots__變量來進行限定。需要注意的是__slots__的限定只對當前類的對象生效,對子類並不起任何作用。

class Person(object):

    # 限定Person對象只能綁定_name, _age和_gender屬性
    __slots__ = ('_name', '_age', '_gender')

    def __init__(self, name, age):
        self._name = name
        self._age = age

    @property
    def name(self):
        return self._name

    @property
    def age(self):
        return self._age

    @age.setter
    def age(self, age):
        self._age = age

    def play(self):
        if self._age <= 16:
            print('%s正在玩飛行棋.' % self._name)
        else:
            print('%s正在玩鬥地主.' % self._name)


def main():
    person = Person('王大錘', 22)
    person.play()
    person._gender = '男'
    # AttributeError: 'Person' object has no attribute '_is_gay'
    # person._is_gay = True

靜態方法和類方法

之前,我們在類中定義的方法都是對象方法,也就是說這些方法都是發送給對象的消息。實際上,我們寫在類中的方法並不需要都是對象方法,例如我們定義一個“三角形”類,通過傳入三條邊長來構造三角形,並提供計算周長和麪積的方法,但是傳入的三條邊長未必能構造出三角形對象,因此我們可以先寫一個方法來驗證三條邊長是否可以構成三角形,這個方法很顯然就不是對象方法,因爲在調用這個方法時三角形對象尚未創建出來(因爲都不知道三條邊能不能構成三角形),所以這個方法是屬於三角形類而並不屬於三角形對象的。我們可以使用靜態方法來解決這類問題,代碼如下所示。

from math import sqrt


class Triangle(object):

    def __init__(self, a, b, c):
        self._a = a
        self._b = b
        self._c = c

    @staticmethod
    def is_valid(a, b, c):
        return a + b > c and b + c > a and a + c > b

    def perimeter(self):
        return self._a + self._b + self._c

    def area(self):
        half = self.perimeter() / 2
        return sqrt(half * (half - self._a) *
                    (half - self._b) * (half - self._c))


def main():
    a, b, c = 3, 4, 5
    # 靜態方法和類方法都是通過給類發消息來調用的
    if Triangle.is_valid(a, b, c):
        t = Triangle(a, b, c)
        print(t.perimeter())
        # 也可以通過給類發消息來調用對象方法但是要傳入接收消息的對象作爲參數
        # print(Triangle.perimeter(t))
        print(t.area())
        # print(Triangle.area(t))
    else:
        print('無法構成三角形.')


if __name__ == '__main__':
    main()

和靜態方法比較類似,Python還可以在類中定義類方法,類方法的第一個參數約定名爲cls,它代表的是當前類相關的信息的對象(類本身也是一個對象,有的地方也稱之爲類的元數據對象),通過這個參數我們可以獲取和類相關的信息並且可以創建出類的對象,代碼如下所示。

from time import time, localtime, sleep


class Clock(object):
    """數字時鐘"""

    def __init__(self, hour=0, minute=0, second=0):
        self._hour = hour
        self._minute = minute
        self._second = second

    @classmethod
    def now(cls):
        ctime = localtime(time())
        return cls(ctime.tm_hour, ctime.tm_min, ctime.tm_sec)

    def run(self):
        """走字"""
        self._second += 1
        if self._second == 60:
            self._second = 0
            self._minute += 1
            if self._minute == 60:
                self._minute = 0
                self._hour += 1
                if self._hour == 24:
                    self._hour = 0

    def show(self):
        """顯示時間"""
        return '%02d:%02d:%02d' % \
               (self._hour, self._minute, self._second)


def main():
    # 通過類方法創建對象並獲取系統時間
    clock = Clock.now()
    while True:
        print(clock.show())
        sleep(1)
        clock.run()


if __name__ == '__main__':
    main()

繼承和多態

剛纔我們提到了,可以在已有類的基礎上創建新類,這其中的一種做法就是讓一個類從另一個類那裏將屬性和方法直接繼承下來,從而減少重複代碼的編寫。提供繼承信息的我們稱之爲父類,也叫超類或基類;得到繼承信息的我們稱之爲子類,也叫派生類或衍生類。子類除了繼承父類提供的屬性和方法,還可以定義自己特有的屬性和方法,所以子類比父類擁有的更多的能力,在實際開發中,我們經常會用子類對象去替換掉一個父類對象,這是面向對象編程中一個常見的行爲,對應的原則稱之爲里氏替換原則。下面我們先看一個繼承的例子。

class Person(object):
    """人"""

    def __init__(self, name, age):
        self._name = name
        self._age = age

    @property
    def name(self):
        return self._name

    @property
    def age(self):
        return self._age

    @age.setter
    def age(self, age):
        self._age = age

    def play(self):
        print('%s正在愉快的玩耍.' % self._name)

    def watch_av(self):
        if self._age >= 18:
            print('%s正在觀看愛情動作片.' % self._name)
        else:
            print('%s只能觀看《熊出沒》.' % self._name)


class Student(Person):
    """學生"""

    def __init__(self, name, age, grade):
        super().__init__(name, age)
        self._grade = grade

    @property
    def grade(self):
        return self._grade

    @grade.setter
    def grade(self, grade):
        self._grade = grade

    def study(self, course):
        print('%s的%s正在學習%s.' % (self._grade, self._name, course))


class Teacher(Person):
    """老師"""

    def __init__(self, name, age, title):
        super().__init__(name, age)
        self._title = title

    @property
    def title(self):
        return self._title

    @title.setter
    def title(self, title):
        self._title = title

    def teach(self, course):
        print('%s%s正在講%s.' % (self._name, self._title, course))


def main():
    stu = Student('王大錘', 15, '初三')
    stu.study('數學')
    stu.watch_av()
    t = Teacher('駱昊', 38, '磚家')
    t.teach('Python程序設計')
    t.watch_av()


if __name__ == '__main__':
    main()

子類在繼承了父類的方法後,可以對父類已有的方法給出新的實現版本,這個動作稱之爲方法重寫(override)。通過方法重寫我們可以讓父類的同一個行爲在子類中擁有不同的實現版本,當我們調用這個經過子類重寫的方法時,不同的子類對象會表現出不同的行爲,這個就是多態(poly-morphism)。

from abc import ABCMeta, abstractmethod


class Pet(object, metaclass=ABCMeta):
    """寵物"""

    def __init__(self, nickname):
        self._nickname = nickname

    @abstractmethod
    def make_voice(self):
        """發出聲音"""
        pass


class Dog(Pet):
    """狗"""

    def make_voice(self):
        print('%s: 汪汪汪...' % self._nickname)


class Cat(Pet):
    """貓"""

    def make_voice(self):
        print('%s: 喵...喵...' % self._nickname)


def main():
    pets = [Dog('旺財'), Cat('凱蒂'), Dog('大黃')]
    for pet in pets:
        pet.make_voice()


if __name__ == '__main__':
    main()

在上面的代碼中,我們將Pet類處理成了一個抽象類,所謂抽象類就是不能夠創建對象的類,這種類的存在就是專門爲了讓其他類去繼承它。Python從語法層面並沒有像Java或C#那樣提供對抽象類的支持,但是我們可以通過abc模塊的ABCMeta元類和abstractmethod包裝器來達到抽象類的效果,如果一個類中存在抽象方法那麼這個類就不能夠實例化(創建對象)。上面的代碼中,DogCat兩個子類分別對Pet類中的make_voice抽象方法進行了重寫並給出了不同的實現版本,當我們在main函數中調用該方法時,這個方法就表現出了多態行爲(同樣的方法做了不同的事情)。

綜合案例

案例1:奧特曼打小怪獸。

from abc import ABCMeta, abstractmethod
from random import randint, randrange


class Fighter(object, metaclass=ABCMeta):
    """戰鬥者"""

    # 通過__slots__魔法限定對象可以綁定的成員變量
    __slots__ = ('_name', '_hp')

    def __init__(self, name, hp):
        """初始化方法

        :param name: 名字
        :param hp: 生命值
        """
        self._name = name
        self._hp = hp

    @property
    def name(self):
        return self._name

    @property
    def hp(self):
        return self._hp

    @hp.setter
    def hp(self, hp):
        self._hp = hp if hp >= 0 else 0

    @property
    def alive(self):
        return self._hp > 0

    @abstractmethod
    def attack(self, other):
        """攻擊

        :param other: 被攻擊的對象
        """
        pass


class Ultraman(Fighter):
    """奧特曼"""

    __slots__ = ('_name', '_hp', '_mp')

    def __init__(self, name, hp, mp):
        """初始化方法

        :param name: 名字
        :param hp: 生命值
        :param mp: 魔法值
        """
        super().__init__(name, hp)
        self._mp = mp

    def attack(self, other):
        other.hp -= randint(15, 25)

    def huge_attack(self, other):
        """究極必殺技(打掉對方至少50點或四分之三的血)

        :param other: 被攻擊的對象

        :return: 使用成功返回True否則返回False
        """
        if self._mp >= 50:
            self._mp -= 50
            injury = other.hp * 3 // 4
            injury = injury if injury >= 50 else 50
            other.hp -= injury
            return True
        else:
            self.attack(other)
            return False

    def magic_attack(self, others):
        """魔法攻擊

        :param others: 被攻擊的羣體

        :return: 使用魔法成功返回True否則返回False
        """
        if self._mp >= 20:
            self._mp -= 20
            for temp in others:
                if temp.alive:
                    temp.hp -= randint(10, 15)
            return True
        else:
            return False

    def resume(self):
        """恢復魔法值"""
        incr_point = randint(1, 10)
        self._mp += incr_point
        return incr_point

    def __str__(self):
        return '~~~%s奧特曼~~~\n' % self._name + \
            '生命值: %d\n' % self._hp + \
            '魔法值: %d\n' % self._mp


class Monster(Fighter):
    """小怪獸"""

    __slots__ = ('_name', '_hp')

    def attack(self, other):
        other.hp -= randint(10, 20)

    def __str__(self):
        return '~~~%s小怪獸~~~\n' % self._name + \
            '生命值: %d\n' % self._hp


def is_any_alive(monsters):
    """判斷有沒有小怪獸是活着的"""
    for monster in monsters:
        if monster.alive > 0:
            return True
    return False


def select_alive_one(monsters):
    """選中一隻活着的小怪獸"""
    monsters_len = len(monsters)
    while True:
        index = randrange(monsters_len)
        monster = monsters[index]
        if monster.alive > 0:
            return monster


def display_info(ultraman, monsters):
    """顯示奧特曼和小怪獸的信息"""
    print(ultraman)
    for monster in monsters:
        print(monster, end='')


def main():
    u = Ultraman('駱昊', 1000, 120)
    m1 = Monster('狄仁傑', 250)
    m2 = Monster('白元芳', 500)
    m3 = Monster('王大錘', 750)
    ms = [m1, m2, m3]
    fight_round = 1
    while u.alive and is_any_alive(ms):
        print('========第%02d回合========' % fight_round)
        m = select_alive_one(ms)  # 選中一隻小怪獸
        skill = randint(1, 10)   # 通過隨機數選擇使用哪種技能
        if skill <= 6:  # 60%的概率使用普通攻擊
            print('%s使用普通攻擊打了%s.' % (u.name, m.name))
            u.attack(m)
            print('%s的魔法值恢復了%d點.' % (u.name, u.resume()))
        elif skill <= 9:  # 30%的概率使用魔法攻擊(可能因魔法值不足而失敗)
            if u.magic_attack(ms):
                print('%s使用了魔法攻擊.' % u.name)
            else:
                print('%s使用魔法失敗.' % u.name)
        else:  # 10%的概率使用究極必殺技(如果魔法值不足則使用普通攻擊)
            if u.huge_attack(m):
                print('%s使用究極必殺技虐了%s.' % (u.name, m.name))
            else:
                print('%s使用普通攻擊打了%s.' % (u.name, m.name))
                print('%s的魔法值恢復了%d點.' % (u.name, u.resume()))
        if m.alive > 0:  # 如果選中的小怪獸沒有死就回擊奧特曼
            print('%s回擊了%s.' % (m.name, u.name))
            m.attack(u)
        display_info(u, ms)  # 每個回合結束後顯示奧特曼和小怪獸的信息
        fight_round += 1
    print('\n========戰鬥結束!========\n')
    if u.alive > 0:
        print('%s奧特曼勝利!' % u.name)
    else:
        print('小怪獸勝利!')


if __name__ == '__main__':
    main()

案例2:撲克遊戲。

import random


class Card(object):
    """一張牌"""

    def __init__(self, suite, face):
        self._suite = suite
        self._face = face

    @property
    def face(self):
        return self._face

    @property
    def suite(self):
        return self._suite

    def __str__(self):
        if self._face == 1:
            face_str = 'A'
        elif self._face == 11:
            face_str = 'J'
        elif self._face == 12:
            face_str = 'Q'
        elif self._face == 13:
            face_str = 'K'
        else:
            face_str = str(self._face)
        return '%s%s' % (self._suite, face_str)
    
    def __repr__(self):
        return self.__str__()


class Poker(object):
    """一副牌"""

    def __init__(self):
        self._cards = [Card(suite, face) 
                       for suite in '♠♥♣♦'
                       for face in range(1, 14)]
        self._current = 0

    @property
    def cards(self):
        return self._cards

    def shuffle(self):
        """洗牌(隨機亂序)"""
        self._current = 0
        random.shuffle(self._cards)

    @property
    def next(self):
        """發牌"""
        card = self._cards[self._current]
        self._current += 1
        return card

    @property
    def has_next(self):
        """還有沒有牌"""
        return self._current < len(self._cards)


class Player(object):
    """玩家"""

    def __init__(self, name):
        self._name = name
        self._cards_on_hand = []

    @property
    def name(self):
        return self._name

    @property
    def cards_on_hand(self):
        return self._cards_on_hand

    def get(self, card):
        """摸牌"""
        self._cards_on_hand.append(card)

    def arrange(self, card_key):
        """玩家整理手上的牌"""
        self._cards_on_hand.sort(key=card_key)


# 排序規則-先根據花色再根據點數排序
def get_key(card):
    return (card.suite, card.face)


def main():
    p = Poker()
    p.shuffle()
    players = [Player('東邪'), Player('西毒'), Player('南帝'), Player('北丐')]
    for _ in range(13):
        for player in players:
            player.get(p.next)
    for player in players:
        print(player.name + ':', end=' ')
        player.arrange(get_key)
        print(player.cards_on_hand)


if __name__ == '__main__':
    main()

說明: 大家可以自己嘗試在上面代碼的基礎上寫一個簡單的撲克遊戲,例如21點(Black Jack),遊戲的規則可以自己在網上找一找。

案例3:工資結算系統。

"""
某公司有三種類型的員工 分別是部門經理、程序員和銷售員
需要設計一個工資結算系統 根據提供的員工信息來計算月薪
部門經理的月薪是每月固定15000元
程序員的月薪按本月工作時間計算 每小時150元
銷售員的月薪是1200元的底薪加上銷售額5%的提成
"""
from abc import ABCMeta, abstractmethod


class Employee(object, metaclass=ABCMeta):
    """員工"""

    def __init__(self, name):
        """
        初始化方法

        :param name: 姓名
        """
        self._name = name

    @property
    def name(self):
        return self._name

    @abstractmethod
    def get_salary(self):
        """
        獲得月薪

        :return: 月薪
        """
        pass


class Manager(Employee):
    """部門經理"""

    def get_salary(self):
        return 15000.0


class Programmer(Employee):
    """程序員"""

    def __init__(self, name, working_hour=0):
        super().__init__(name)
        self._working_hour = working_hour

    @property
    def working_hour(self):
        return self._working_hour

    @working_hour.setter
    def working_hour(self, working_hour):
        self._working_hour = working_hour if working_hour > 0 else 0

    def get_salary(self):
        return 150.0 * self._working_hour


class Salesman(Employee):
    """銷售員"""

    def __init__(self, name, sales=0):
        super().__init__(name)
        self._sales = sales

    @property
    def sales(self):
        return self._sales

    @sales.setter
    def sales(self, sales):
        self._sales = sales if sales > 0 else 0

    def get_salary(self):
        return 1200.0 + self._sales * 0.05


def main():
    emps = [
        Manager('劉備'), Programmer('諸葛亮'),
        Manager('曹操'), Salesman('荀彧'),
        Salesman('呂布'), Programmer('張遼'),
        Programmer('趙雲')
    ]
    for emp in emps:
        if isinstance(emp, Programmer):
            emp.working_hour = int(input('請輸入%s本月工作時間: ' % emp.name))
        elif isinstance(emp, Salesman):
            emp.sales = float(input('請輸入%s本月銷售額: ' % emp.name))
        # 同樣是接收get_salary這個消息但是不同的員工表現出了不同的行爲(多態)
        print('%s本月工資爲: ¥%s元' %
              (emp.name, emp.get_salary()))


if __name__ == '__main__':
    main()

再次感謝作者的學習資料!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章