從零學Python:18-物件導向程式設計應用
物件導向程式設計對初學者來說不難理解但很難應用,雖然我們為大家總結過物件導向的三步走方法(定義類、建立物件、給物件發訊息),但是說起來容易做起來難。 大量的程式設計練習和 閱讀優質的程式碼可能是這個階段最能夠幫助到大家的兩件事情。接下來我們還是透過經典的案例來剖析物件導向程式設計的知識,同時也透過這些案例為大家講解如何運用之前學過的Python知識。
經典案例
案例1: 普ke 遊戲。
說明:簡單起見,我們的 普ke只有52張牌(沒有大小王),遊戲需要將52張牌發到4個玩家的手上,每個玩家手上有13張牌,按照黑桃、紅心、草花、方塊的順序和點數從小到大排列,暫時不實現其他的功能。
使用物件導向程式設計方法,首先需要從問題的需求中找到物件並抽象出對應的類,此外還要找到物件的屬性和行為。當然,這件事情並不是特別困難,我們可以從需求的描述中找出名詞和動詞,名詞通常就是物件或者是物件的屬性,而動詞通常是物件的行為。puke遊戲中至少應該有三類物件,分別是牌、 普ke和玩家,牌、 普ke、玩家三個類也並不是孤立的。類和類之間的關係可以粗略的分為 is-a關係(繼承)、 has-a關係(關聯)和 use-a關係(依賴)。很顯然 普ke和牌是has-a關係,因為一副 普ke有(has-a)52張牌;玩家和牌之間不僅有關聯關係還有依賴關係,因為玩家手上有(has-a)牌而且玩家使用了(use-a)牌。
牌的屬性顯而易見,有花色和點數。我們可以用0到3的四個數字來代表四種不同的花色,但是這樣的程式碼可讀性會非常糟糕,因為我們並不知道黑桃、紅心、草花、方塊跟0到3的數字的對應關係。如果一個變數的取值只有有限多個選項,我們可以使用列舉。與C、Java等語言不同的是,Python中沒有宣告列舉型別的關鍵字,但是可以透過繼承enum模組的Enum類來建立列舉型別,程式碼如下所示。
from enum
import Enum
class Suite(Enum):
"""花色(列舉)"""
SPADE, HEART, CLUB, DIAMOND = range(
4)
透過上面的程式碼可以看出,定義列舉型別其實就是定義符號常量,如SPADE、HEART等。每個符號常量都有與之對應的值,這樣表示黑桃就可以不用數字0,而是用Suite.SPADE;同理,表示方塊可以不用數字3, 而是用Suite.DIAMOND。注意,使用符號常量肯定是優於使用字面常量的,因為能夠讀懂英文就能理解符號常量的含義,程式碼的可讀性會提升很多。Python中的列舉型別是可迭代型別,簡單的說就是可以將列舉型別放到for-in迴圈中,依次取出每一個符號常量及其對應的值,如下所示。
for suite
in Suite:
print(
f'
{suite}:
{suite.value}')
接下來我們可以定義牌類。
class
Card:
"""牌"""
def
__init__
(self, suite, face):
self.suite = suite
self.face = face
def
__repr__
(self):
suites =
'♠♥♣♦'
faces = [
'',
'A',
'2',
'3',
'4',
'5',
'6',
'7',
'8',
'9',
'10',
'J',
'Q',
'K']
# 根據牌的花色和點數取到對應的字元
return f'{suites[self.suite.value]}{faces[self.face]}'
可以透過下面的程式碼來測試下Card類。
card1 = Card(Suite.SPADE,
5)
card2 = Card(Suite.HEART,
13)
print(card1, card2)
# ♠5 ♥K
接下來我們定義puke類。
import random
class Poker(object):
""
"puke"
""
def __init__(
self):
# 透過列表的生成式語法建立一個裝
52張牌的列表
self.cards = [Card(suite, face)
for suite
in Suite
for face
in range(
1,
14)]
# current屬性表示發牌的位置
self.current =
0
def shuffle(
self):
""
"洗牌"
""
self.current =
0
# 透過random模組的shuffle函式實現列表的隨機亂序
random.shuffle(
self.cards)
def deal(
self):
""
"發牌"
""
card =
self.cards[
self.current]
self.current +=
1
return card
@property
def has_next(
self):
""
"還有沒有牌可以發"
""
return
self.current < len(
self.cards)
可以透過下面的程式碼來測試下Poker類。
poker = Poker()
poker.shuffle()
print(poker.cards)
定義玩家類。
class
Player
(object):
"""玩家"""
def
__init__
(self, name):
self.name = name
self.cards = []
def
get_one
(self, card):
"""摸牌"""
self.cards.append(card)
def
arrange
(self):
self.cards.sort()
建立四個玩家並將牌發到玩家的手上。
poker = Poker()
poker.shuffle()
players = [Player(
'東邪'), Player(
'西毒'), Player(
'南帝'), Player(
'北丐')]
for _ in
range(
13):
for player in players:
player.get_one(poker.deal())
for player in players:
player.arrange()
print(f
'{player.name}: ', end=
'')
print(player.cards)
執行上面的程式碼會在player.arrange()那裡出現異常,因為Player的arrange方法使用了列表的sort對玩家手上的牌進行排序,排序需要比較兩個Card物件的大小,而<運算子又不能直接作用於Card型別,所以就出現了TypeError異常,異常訊息為:'<' not supported between instances of 'Card' and 'Card'。
為了解決這個問題,我們可以對Card類的程式碼稍作修改,使得兩個Card物件可以直接用<進行大小的比較。這裡用到技術叫 運算子過載,Python中要實現對<運算子的過載,需要在類中新增一個名為__lt__的魔術方法。很顯然,魔術方法__lt__中的lt是英文單詞“less than”的縮寫,以此類推,魔術方法__gt__對應>運算子,魔術方法__le__對應<=運算子,__ge__對應>=運算子,__eq__對應==運算子,__ne__對應!=運算子。
修改後的Card類程式碼如下所示。
class
Card:
"""牌"""
def
__init__
(self, suite, face):
self.suite = suite
self.face = face
def
__repr__
(self):
suites =
'♠♥♣♦'
faces = [
'',
'A',
'2',
'3',
'4',
'5',
'6',
'7',
'8',
'9',
'10',
'J',
'Q',
'K']
# 根據牌的花色和點數取到對應的字元
return f'{suites[self.suite.value]}{faces[self.face]}'
def __lt__(self, other):
# 花色相同比較點數的大小
if self.suite == other.suite:
return self.face < other.face
# 花色不同比較花色對應的值
return self.suite.value < other.suite.value
說明: 大家可以嘗試在上面程式碼的基礎上寫一個簡單的puke遊戲,如21點遊戲(Black Jack),遊戲的規則可以自己在網上找一找。
案例2:工資結算系統。
要求:某公司有三種型別的員工,分別是部門經理、程式設計師和銷售員。需要設計一個工資結算系統,根據提供的員工資訊來計算員工的月薪。其中,部門經理的月薪是固定15000元;程式設計師按工作時間(以小時為單位)支付月薪,每小時200元;銷售員的月薪由1800元底薪加上銷售額5%的提成兩部分構成。
透過對上述需求的分析,可以看出部門經理、程式設計師、銷售員都是員工,有相同的屬性和行為,那麼我們可以先設計一個名為Employee的父類,再透過繼承的方式從這個父類派生出部門經理、程式設計師和銷售員三個子類。很顯然,後續的程式碼不會建立Employee 類的物件,因為我們需要的是具體的員工物件,所以這個類可以設計成專門用於繼承的抽象類。Python中沒有定義抽象類的關鍵字,但是可以透過abc模組中名為ABCMeta 的元類來定義抽象類。關於元類的知識,後面的課程中會有專門的講解,這裡不用太糾結這個概念,記住用法即可。
from abc
import ABCMeta, abstractmethod
class Employee(metaclass=ABCMeta):
"""員工"""
def
__init__
(self, name):
self.name = name
@abstractmethod
def
get_salary
(self):
"""結算月薪"""
pass
在上面的員工類中,有一個名為get_salary的方法用於結算月薪,但是由於還沒有確定是哪一類員工,所以結算月薪雖然是員工的公共行為但這裡卻沒有辦法實現。對於暫時無法實現的方法,我們可以使用abstractmethod裝飾器將其宣告為抽象方法,所謂 抽象方法就是隻有宣告沒有實現的方法, 宣告這個方法是為了讓 子類 去重寫這個方法。接下來的程式碼展示瞭如何從員工類派生出部門經理、程式設計師、銷售員這三個子類以及子類如何重寫父類的抽象方法。
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
def
get_salary
(self):
return
200 * self.working_hour
class Salesman(Employee):
"""銷售員"""
def
__init__
(self, name, sales=
0):
super().__init__(name)
self.sales = sales
def
get_salary
(self):
return
1800 + self.sales *
0.05
上面的Manager、Programmer、Salesman三個類都繼承自Employee,三個類都分別重寫了get_salary方法。 重寫就是子類對父類已有的方法重新做出實現。相信大家已經注意到了,三個子類中的get_salary各不相同,所以這個方法在程式執行時會產生 多型行為,多型簡單的說就是 呼叫相同的方法, 不同的子類物件做不同的事情。
我們透過下面的程式碼來完成這個工資結算系統,由於程式設計師和銷售員需要分別錄入本月的工作時間和銷售額,所以在下面的程式碼中我們使用了Python內建的isinstance函式來判斷員工物件的型別。我們之前講過的type函式也能識別物件的型別,但是isinstance函式更加強大,因為它可以判斷出一個物件是不是某個繼承結構下的子型別,你可以簡答的理解為type函式是對物件型別的精準匹配,而isinstance函式是對物件型別的模糊匹配。
emps = [
Manager(
'劉備'), Programmer(
'諸葛亮'), Manager(
'曹操'),
Programmer(
'荀彧'), Salesman(
'呂布'), Programmer(
'張遼'),
]
for emp
in emps:
if isinstance(emp, Programmer):
emp.working_hour = int(input(
f'請輸入
{emp.name}本月工作時間: '))
elif isinstance(emp, Salesman):
emp.sales = float(input(
f'請輸入
{emp.name}本月銷售額: '))
print(
f'
{emp.name}本月工資為: ¥
{emp.get_salary():
.2f}元')
簡單的總結
物件導向的程式設計思想非常的好,也符合人類的正常思維習慣,但是要想靈活運用物件導向程式設計中的抽象、封裝、繼承、多型需要長時間的積累和沉澱,這件事情並非一夕之功,也無法一蹴而就。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69923331/viewspace-2707536/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 從零學Python:17課-物件導向程式設計(進階)Python物件程式設計
- 從零學Python:第十六課-物件導向程式設計入門Python物件程式設計
- Python物件導向程式設計Python物件程式設計
- Python 物件導向程式設計Python物件程式設計
- Python學習之物件導向程式設計Python物件程式設計
- Python OOP 物件導向程式設計PythonOOP物件程式設計
- python技能--物件導向程式設計Python物件程式設計
- Python物件導向程式設計(1)Python物件程式設計
- Python - 物件導向程式設計 - super()Python物件程式設計
- Python - 物件導向程式設計 - @propertyPython物件程式設計
- Python學習之路——類-物件導向程式設計Python物件程式設計
- Python學習之物件導向高階程式設計Python物件程式設計
- 史上最全 Python 物件導向程式設計Python物件程式設計
- python基礎(物件導向程式設計)Python物件程式設計
- python物件導向程式設計基礎Python物件程式設計
- python之物件導向程式設計(一)Python物件程式設計
- 14 Python物件導向程式設計:反射Python物件程式設計反射
- 圖解python | 物件導向程式設計圖解Python物件程式設計
- Python_從零開始學習_(49) 飛機大戰_物件導向設計類Python物件
- 物件導向程式設計物件程式設計
- 從零開始的Python學習Episode 18——物件導向(1)Python物件
- 從零開始的Python學習Episode 19——物件導向(2)Python物件
- Python3:物件導向程式設計學習筆記(2)Python物件程式設計筆記
- 程式設計思想 物件導向程式設計物件
- js物件導向程式設計JS物件程式設計
- 十三、物件導向程式設計物件程式設計
- 十六、物件導向程式設計物件程式設計
- 13 Python物件導向程式設計:裝飾器Python物件程式設計
- JS物件導向程式設計(一):物件JS物件程式設計
- Python - 物件導向程式設計 - 什麼是 Python 類、類物件、例項物件Python物件程式設計
- 深入解析 Java 物件導向程式設計與類屬性應用Java物件程式設計
- python學習之物件導向程式設計的一些思考Python物件程式設計
- 從物件導向解讀設計思想物件
- 12 Python物件導向程式設計:運算子過載Python物件程式設計
- Python基礎入門(6)- 物件導向程式設計Python物件程式設計
- Python - 物件導向程式設計 - __new()__ 和單例模式Python物件程式設計單例模式
- 物件導向程式設計C++物件程式設計C++
- javascript:物件導向的程式設計JavaScript物件程式設計