面向物件編程
文章目錄
- 面向物件編程
- 兩種編程方式
- 類和物件的定義
- 面向物件編程三步驟
- 一、定義類
- 二、造物件
- 三、發訊息
- 案例
- 靜態方法、類方法
- 面向物件解決實際問題
- 魔術方法
- 面向物件四大支柱
- 繼承
- 多型
- 物件之間的關系
- 最終總結
兩種編程方式
指令式編程 —> 面向程序(函式)編程 —> 程式比較簡單的時候沒有任何毛病
編程范式(程式設計的方法論):面向物件編程/函式式編程
類和物件的定義
-
物件:物件是可以接收訊息的物體,面向物件編程就是通過給物件發訊息達到解決問題的目標,
-
物件 = 資料 + 函式(方法) —> 物件將資料和操作資料的函式從邏輯上變成了一個整體,
- 一切皆為物件
- 物件都有屬性和行為
- 每個物件都是獨一無二的
- 物件一定屬于某個類
-
類(型別): 將一大類物件共同的特征(靜態特征和動態特征)抽取出來之后得到的一個抽象概念,
簡單的說,類是物件的藍圖(模板),有了類才能夠創建出這種型別的物件,
面向物件編程三步驟
面向物件編程
-
-
1.定義類 —>類的命名使用駝峰命名法(每個單詞首字母大寫)
- 資料抽象:找到和物件相關的靜態特征(屬性)—> 找名詞
- 行為抽象:找到和物件相關的動態特征(方法)—> 找動詞
- 2.造物件 具體
- 3.發訊息
一、定義類
適應性代碼 在實際開發中不會加 print 不然會高耦合
在Python中,可以使用class關鍵字加上類名來定義類(類名的命名使用駝峰命名法,即每個單詞首字母大寫),通過縮進我們可以確定類的代碼塊,就如同定義函式那樣,
self 是代表接收訊息的物件
'''
example01
'''
class Student:
"""學生"""
# 資料抽象 (屬性)
def __init__(self, name, age): #self 學生物件 后面的是引數
self.name = name #給物件系結name 資料
self.age = age
# 行為抽象 (方法)
def eat(self): #self 代表接收訊息的學生物件
print(f'{self.name}正在吃飯')
def study(self,course_name):
print(f'{self.name}正在學習{course_name}')
def play(self,game_name):
print(f'{self.name}正在玩{game_name}')
def watch_av(self):
if self.age < 18:
print(f'{self.name}未滿18歲,只能看《天線寶寶》')
else:
print(f'{self.name}正在觀看島國片')

二、造物件
在前面學的函式與模塊知識
此時我們在相同路徑下創建新的檔案example02
匯入example01
'''
example02
'''
from example01 import Student
#第二步 創建物件 ---> 構造器語法 ---> 類名(...,...)
stu1 = Student('王大錘',15)
stu2 = Student('吳某凡',25)
三、發訊息
還是在example02中
#第三步:給物件發訊息(呼叫物件的方法)
#Student.study(stu1,'python程式設計')
stu1.study('python程式設計')
stu1.eat()
stu1.watch_av()
stu1.play('斗地主')
stu2.play('選妃')
stu2.watch_av()
案例
import time
# 定義數字時鐘類
class Clock(object):
"""數字時鐘"""
def __init__(self, hour=0, minute=0, second=0):
"""初始化方法
:param hour: 時
:param minute: 分
:param second: 秒
"""
self.hour = hour
self.min = minute
self.sec = second
def run(self):
"""走字"""
self.sec += 1
if self.sec == 60:
self.sec = 0
self.min += 1
if self.min == 60:
self.min = 0
self.hour += 1
if self.hour == 24:
self.hour = 0
def show(self):
"""顯示時間"""
return f'{self.hour:0>2d}:{self.min:0>2d}:{self.sec:0>2d}'
# 創建時鐘物件
clock = Clock(23, 59, 58)
while True:
# 給時鐘物件發訊息讀取時間
print(clock.show())
# 休眠1秒鐘
time.sleep(1)
# 給時鐘物件發訊息使其走字
clock.run()
靜態方法、類方法
我們在類里面寫的函式,通常稱之為方法,它們基本上都是發給物件的訊息
但是有的時候,我們的訊息并不想發給物件,而是希望發給這個類(類本身也是一個物件)
在創建物件前給類發訊息讓它決定是否執行,能執行就創建物件,
- 靜態方法 - 發給類的訊息 —> @staticmethod —> 裝飾器
- 類方法 - 發給類的訊息 —> @classmethod —> 裝飾器 —> 第一個引數(cls)是接收訊息的類
"""
example05 - 定義三角形的類,提供計算周長和面積的方法
Author: 龔凡
Date: 2021/8/5 0005
"""
import math
# 直接在初始化方法下面寫判斷條件是可以的,但會造成代碼冗余執行效率不加
# 如果條件不滿足會導致報錯難以繼續執行,此時我們就需要使用靜態方法讓類先判斷后才決定是否執行
# class Triangle:
# def __init__(self, a, b, c):
# if a + b > c and b + c > a and c + a > b:
# self.a = a
# self.b = b
# self.c = c
# else:
# raise ValueError('無效的邊長') # 引發例外
#
# def perimeter(self):
# return self.a + self.b + self.c
#
# def area(self):
# half = self.perimeter() / 2
# return math.sqrt(half * (half - self.a) * (half - self.b) * (half - self.c))
#
class Triangle:
"""三角形"""
def __init__(self, a, b, c):
self.a = a
self.b = b
self.c = c
# @classmethod
# def is_valid(cls, a, b, c):
# return a + b > c and b + c > a and a + c > b
@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 (half * (half - self.a) * (half - self.b) * (half - self.c)) ** 0.5
if __name__ == '__main__':
# 呼叫靜態方法,判斷三條邊能否構成三角形
if Triangle.is_valid(3, 4, 5):
t = Triangle(3, 4, 5) #創建物件
print(t.perimeter()) #發訊息
print(t.area())
else:
print('無效的邊長,無法構造三角形物件')
面向物件解決實際問題
魔術方法
魔術方法(魔法方法)—> 有特殊用途和意義的方法
-
魔術方法(魔法方法)—> 有特殊用途和意義的方法
- init —> 初始化方法,在呼叫構造器語法創建物件的時候會被自動呼叫
- str —> 獲得物件的字串表示,在呼叫print函式輸出物件時會被自動呼叫
-
repr —> 獲得物件的字串表示,把物件放到容器中呼叫print輸出時會自動呼叫
—> representation - lt —> 在使用 < 運算子比較兩個物件大小時會自動呼叫
如果要限制一個類的物件只能擁有某些屬性,可以在類中使用__slots__魔法屬性
里面的屬性是不可修改的,
class Student:
__slots__ = ('name', 'gender')
"""
example06 - 撲克游戲,四個玩家參與,先洗牌,再把牌發到四個玩家的手上,
~ 牌(Card)
- 屬性:花色(suite)、點數(face)
- 行為:顯示
~ 撲克(Poker)
- 屬性:保存牌的串列
- 行為:洗牌(shuffle)、發牌(deal)
~ 玩家
- 屬性:名字(昵稱)、保存玩家手牌的串列
- 行為:摸牌(get)、整理(arrange)
魔術方法(魔法方法)---> 有特殊用途和意義的方法
~ __init__ ---> 初始化方法,在呼叫構造器語法創建物件的時候會被自動呼叫
~ __str__ ---> 獲得物件的字串表示,在呼叫print函式輸出物件時會被自動呼叫
~ __repr__ ---> 獲得物件的字串表示,把物件放到容器中呼叫print輸出時會自動呼叫
---> representation
~ __lt__ ---> 在使用 < 運算子比較兩個物件大小時會自動呼叫
如果一個變數的取值只有有限個選項,可以考慮使用列舉型別,
Python中沒有定義列舉型別的語法,但是可以通過繼承Enum類來實作列舉型別,
結論1:列舉型別是定義符號常量的最佳選擇!!!
結論2:符號常量(有意義的名字)總是優于字面常量!!!
Author:
Date: 2021/8/5
"""
from enum import Enum
# 列舉型別
class Suite(Enum):
SPADE, HEART, CLUB, DIAMOND = range(4)
class Card:
"""牌"""
def __init__(self, suite, face):
self.suite = suite
self.face = face
def __str__(self):
return self.show()
def __repr__(self):
return self.show()
def __lt__(self, other): #兩個牌做比較 利于下面整理牌
if self.suite == other.suite:
return self.face < other.face
return self.suite.value < other.suite.value
def show(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 main():
"""程式入口"""
card1 = Card(Suite.HEART, 1)
card2 = Card(Suite.SPADE, 13)
print(card1, card2)
print(card1 is card2)
card3 = Card(Suite.DIAMOND, 9)
card4 = Card(Suite.CLUB, 11)
print(card3.show(), card4.show())
card1 = card2
print(card1, card2, card3)
# 身份運算子
print(card1 is card2)
print(card1 is card3)
cards = [card1, card2, card3, card4]
print(cards)
if __name__ == '__main__':
main()
"""
example07 - 撲克
Author:
Date: 2021/8/6
"""
import random
from example06 import Card
from example06 import Suite
class Poker:
"""撲克"""
def __init__(self):
self.cards = [Card(suite, face)
for suite in Suite
for face in range(1, 14)]
self.counter = 0
def shuffle(self):
"""洗牌"""
self.counter = 0
random.shuffle(self.cards)
def deal(self) -> Card:
"""發牌"""
card = self.cards[self.counter]
self.counter += 1
return card
def has_more(self) -> bool:
"""是否還有牌"""
return self.counter < len(self.cards)
def main():
poker = Poker()
poker.shuffle()
while poker.has_more():
print(poker.deal(), end=' ')
if __name__ == '__main__':
main()
from example07 import Poker
class Player:
"""玩家"""
def __init__(self, nickname):
self.nickname = nickname
self.cards = []
def get_one_card(self, card):
"""摸一張牌"""
self.cards.append(card)
def arrange(self):
"""整理手上的牌"""
self.cards.sort()
def show(self):
"""顯示玩家手上的牌"""
print(self.nickname, end=': ')
for card in self.cards:
print(card, end=' ')
print()
def main():
nicknames = ('東邪', '西毒', '南帝', '北丐')
players = [Player(nickname) for nickname in nicknames]
poker = Poker()
poker.shuffle()
# 將牌發到四個玩家的手上
for _ in range(13):
for player in players:
card = poker.deal()
player.get_one_card(card)
# 顯示四個玩家手上的牌
for player in players:
player.arrange()
player.show()
if __name__ == '__main__':
main()
面向物件四大支柱
面向物件編程的四大支柱:
-
~ 抽象(abstraction):提取共性(定義類就是一個抽象程序,需要做資料抽象和行為抽象),
-
~ 封裝(encapsulation):把資料和操作資料的函式從邏輯上組裝成一個整體(物件),
—> 隱藏實作細節,暴露簡單的呼叫介面, -
~ 繼承(inheritance):擴展已有的類創建新類,實作對已有類的代碼復用,
-
~ 多型(polymorphism):給不同的物件發出同樣的訊息,不同的物件執行了不同的行為,
—> 方法重寫:子類對父類已有的方法,重新給出自己的實作版本
抽象和封裝在前面我們已經學習了,現在來學習繼承和多型,
繼承
繼承:對已有的類進行擴展創建出新的類,這個程序就叫繼承,
提供繼承資訊的類叫做父類(超類、基類),得到繼承資訊的類稱為子類(派生類),
注意 !!
繼承是實作代碼復用的一種手段,但是千萬不要濫用繼承,
子類直接從父類繼承公共的屬性和行為,再添加自己特有的屬性和行為,
所以子類一定是比父類更強大的,任何時候都可以用子類物件去替代父類物件,
Python中的繼承允許多重繼承,一個類可以有一個或多個父類,
如果不是必須使用多重繼承的場景下,請盡量使用單一繼承,
在下列例子中 我們定義Person為父類類,子類為三個,分別是Student、Teacher、Programmer、它們都有三個共同的行為是 eat 、play、introduce,所以創建父類來裝這些行為,這樣有利于減少代碼,
class Person:
"""人"""
def __init__(self, name, gender):
self.name = name
self.gender = gender
def eat(self):
"""吃飯"""
print(f'{self.name}正在吃飯.')
def play(self, game_name):
"""玩"""
print(f'{self.name}正在玩{game_name}.')
def introduce(self):
"""自我介紹"""
sex = "男" if self.gender else "女"
print(f'我叫{self.name}, 是一個{sex}人.')
class Student(Person):
"""學生"""
def __init__(self, name, gender, grade):
super().__init__(name, gender)
self.grade = grade
def study(self, course_name):
"""學習"""
print(f'{self.name}正在學習{course_name}.')
class Teacher(Person):
"""老師"""
def __init__(self, name, gender, title):
super().__init__(name, gender)
self.title = title
def teach(self, course_name):
"""教課"""
print(f'{self.name}{self.title}正在講授{course_name}.')
class Programmer(Person):
"""程式員"""
def write_code(self, programming_language):
"""寫代碼"""
print(f'{self.name}正在用{programming_language}寫代碼.')
stu = Student('王大錘', True, '五年級')
stu.study('語文')
stu.play('王者榮耀')
stu.eat()
stu.introduce()
teacher = Teacher('王大錘', True, '叫獸')
teacher.eat()
teacher.play('斗地主')
teacher.teach('Python程式設計')
teacher.introduce()
programmer = Programmer('白元芳', True)
programmer.eat()
programmer.play('吃雞')
programmer.write_code('Python')
多型
~ 多型(polymorphism):給不同的物件發出同樣的訊息,不同的物件執行了不同的行為,
—> 方法重寫:子類對父類已有的方法,重新給出自己的實作版本
在父類Employee中擁有get_salary行為,且三個子類中也有相同的行為但實作程序不同,運行下代碼會看出,
子類對父類已有的方法,重新給出自己的實作版本,這個程序叫做方法重寫(override),
在重寫方法的程序中,不同的子類可以對父類的同一個方法給出不同的實作版本,那么該方法在運行時就會表現出多型行為,
'''
三類員工:
~ 部門經理:固定月薪,15000元
~ 程式員:計時結算月薪,每小時200元
~ 銷售員:底薪+提成,底薪1800元,銷售額5%提成
錄入員工資訊,自動結算月薪
'''
from abc import abstractmethod
class Employee:
def __init__(self, no, name):
self.no = no
self.name = name
@abstractmethod
def get_salary(self):
pass
class Manager(Employee):
def get_salary(self):
return 15000
class Programmer(Employee):
def __init__(self, no, name):
super().__init__(no, name)
self.working_hour = 0
def get_salary(self):
return 200 * self.working_hour
class Salesman(Employee):
def __init__(self, no, name):
super().__init__(no, name)
self.sales = 0
def get_salary(self):
return 1800 + 0.05 * self.sales
def main():
emps = [
Manager(1122, '劉備'), Programmer(2233, '諸葛亮'),
Salesman(3344, '關羽'), Salesman(4455, '張飛'),
Programmer(5566, '龐統'), Salesman(6677, '馬超')
]
for emp in emps:
if type(emp) == Programmer:
emp.working_hour = int(input(f'請輸入{emp.name}本月作業時長: '))
elif type(emp) == Salesman:
emp.sales = float(input(f'請輸入{emp.name}本月銷售額: '))
print(f'{emp.name}本月工資: {emp.get_salary()}元')
if __name__ == '__main__':
main()
物件之間的關系
兩個類之間有三種關系
-
~ is-a關系:繼承 —> 從一個類派生出另一個類
如 a student is a person.
a teacher is a person. -
~ has-a關系:關聯 —> 把一個類的物件作為另外一個類的物件的屬性
a person has an identity card.
a car has an engine.(普通)關聯
強關聯:整體和部分的關聯,聚合和合成 -
~ use-a關系:依賴 —> 一個類的物件作為另外一個類的方法的引數或回傳值
a person use a vehicle.
如下代碼 馬、摩托車與交通工具屬于繼承關系
徒弟 與 唐僧 屬于 關聯 關系
引擎 與 摩托車 屬于 依賴關系
class Vehicle:
"""交通工具"""
pass
class Horse(Vehicle):
"""馬"""
pass
class Motobike(Vehicle):
"""摩托車"""
def __init__(self):
self.engine = Engine()
class Engine:
"""引擎"""
pass
class Follower:
"""徒弟"""
def __init__(self, name):
self.name = name
def do_work(self):
pass
class MrTang:
"""唐僧"""
def __init__(self):
self.followers = [Follower('孫悟空'), Follower('豬悟能'), Follower('沙悟凈')]
def drive(self, vehicle):
"""駕駛"""
pass
最終總結
Python是動態語言,Python中的物件可以動態的添加屬性,在面向物件的世界中,一切皆為物件,我們定義的類也是物件,所以類也可以接收訊息,對應的方法是類方法或靜態方法,通過繼承,我們可以從已有的類創建新類,實作對已有類代碼的復用,但是要想靈活運用面向物件編程中的抽象、封裝、繼承、多型需要長時間的積累和沉淀,這件事情并非一夕之功,也無法一蹴而就,
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/292539.html
標籤:其他
