每一個不曾起舞的日子,都是對生命的辜負(fù)!
面向?qū)ο缶幊袒A(chǔ)
活在當(dāng)下的程序員應(yīng)該都聽過“面向?qū)ο缶幊獭币辉~,也經(jīng)常有人問能不能用一句話解釋下什么是“面向?qū)ο缶幊獭保覀兿葋砜纯幢容^正式的說法。
把一組數(shù)據(jù)結(jié)構(gòu)和處理它們的方法組成對象(object),把相同行為的對象歸納為類(class),通過類的封裝(encapsulation)隱藏內(nèi)部細(xì)節(jié),通過繼承(inheritance)實現(xiàn)類的特化(specialization)和泛化(generalization),通過多態(tài)(polymorphism)實現(xiàn)基于對象類型的動態(tài)分派。
這樣一說是不是更不明白了。所以我們還是看看更通俗易懂的說法,下面這段內(nèi)容來自于知乎。
之前我們說過“程序是指令的集合”,我們在程序中書寫的語句在執(zhí)行時會變成一條或多條指令然后由CPU去執(zhí)行。當(dāng)然為了簡化程序的設(shè)計,我們引入了函數(shù)的概念,把相對獨立且經(jīng)常重復(fù)使用的代碼放置到函數(shù)中,在需要使用這些功能的時候只要調(diào)用函數(shù)即可;如果一個函數(shù)的功能過于復(fù)雜和臃腫,我們又可以進(jìn)一步將函數(shù)繼續(xù)切分為子函數(shù)來降低系統(tǒng)的復(fù)雜性。但是說了這么多,不知道大家是否發(fā)現(xiàn),所謂編程就是程序員按照計算機的工作方式控制計算機完成各種任務(wù)。但是,計算機的工作方式與正常人類的思維模式是不同的,如果編程就必須得拋棄人類正常的思維方式去迎合計算機,編程的樂趣就少了很多,“每個人都應(yīng)該學(xué)習(xí)編程”這樣的豪言壯語就只能說說而已。當(dāng)然,這些還不是最重要的,最重要的是當(dāng)我們需要開發(fā)一個復(fù)雜的系統(tǒng)時,代碼的復(fù)雜性會讓開發(fā)和維護(hù)工作都變得舉步維艱,所以在上世紀(jì)60年代末期,“軟件工程”、“軟件危機”等一系列的概念開始在行業(yè)中出現(xiàn)。
當(dāng)然,程序員圈子內(nèi)的人都知道,現(xiàn)實中并沒有解決上面所說的這些問題的"銀彈",真正讓軟件開發(fā)者看到希望的是上世紀(jì)70年代誕生的Smalltalk編程語言中引入的面向?qū)ο蟮木幊趟枷耄嫦驅(qū)ο缶幊痰碾r形可以追溯到更早期的Simula語言)。按照這種編程理念,程序中的數(shù)據(jù)和操作數(shù)據(jù)的函數(shù)是一個邏輯上的整體,我們稱之為“對象”,而我們解決問題的方式就是創(chuàng)建出需要的對象并向?qū)ο蟀l(fā)出各種各樣的消息,多個對象的協(xié)同工作最終可以讓我們構(gòu)造出復(fù)雜的系統(tǒng)來解決現(xiàn)實中的問題。
說明
:當(dāng)然面向?qū)ο笠膊皇墙鉀Q軟件開發(fā)中所有問題的最后的“銀彈”,所以今天的高級程序設(shè)計語言幾乎都提供了對多種編程范式的支持,Python也不例外。
類和對象
簡單的說,類是對象的藍(lán)圖和模板,而對象是類的實例。這個解釋雖然有點像用概念在解釋概念,但是從這句話我們至少可以看出,類是抽象的概念,而對象是具體的東西。在面向?qū)ο缶幊痰氖澜缰?,一切皆為對象,對象都有屬性和行為,每個對象都是獨一無二的,而且對象一定屬于某個類(型)。當(dāng)我們把一大堆擁有共同特征的對象的靜態(tài)特征(屬性)和動態(tài)特征(行為)都抽取出來后,就可以定義出一個叫做“類”的東西。
定義類
在Python中可以使用class
關(guān)鍵字定義類,然后在類中通過之前學(xué)習(xí)過的函數(shù)來定義方法,這樣就可以將對象的動態(tài)特征描述出來,代碼如下所示。
class Student(object):
# __init__是一個特殊方法用于在創(chuàng)建對象時進(jìn)行初始化操作
# 通過這個方法我們可以為學(xué)生對象綁定name和age兩個屬性
def __init__(self, name, age):
self.name = name
self.age = age
def study(self, course_name):
print('%s正在學(xué)習(xí)%s.' % (self.name, course_name))
# PEP 8要求標(biāo)識符的名字用全小寫多個單詞用下劃線連接
# 但是很多程序員和公司更傾向于使用駝峰命名法(駝峰標(biāo)識)
def watch_av(self):
if self.age < 18:
print('%s只能觀看《熊出沒》.' % self.name)
else:
print('%s正在觀看島國愛情動作片.' % self.name)
說明
:寫在類中的函數(shù),我們通常稱之為(對象的)方法,這些方法就是對象可以接收的消息。
創(chuàng)建和使用對象
當(dāng)我們定義好一個類之后,可以通過下面的方式來創(chuàng)建對象并給對象發(fā)消息。
def main():
# 創(chuàng)建學(xué)生對象并指定姓名和年齡
stu1 = Student('駱昊', 38)
# 給對象發(fā)study消息
stu1.study('Python程序設(shè)計')
# 給對象發(fā)watch_av消息
stu1.watch_av()
stu2 = Student('王大錘', 15)
stu2.study('思想品德')
stu2.watch_av()
if __name__ == '__main__':
main()
訪問可見性問題
對于上面的代碼,有C++、Java、C#等編程經(jīng)驗的程序員可能會問,我們給Student
對象綁定的name和age屬性到底具有怎樣的訪問權(quán)限(也稱為可見性)。因為在很多面向?qū)ο缶幊陶Z言中,我們通常會將對象的屬性設(shè)置為私有的(private)
或受保護(hù)的(protected)
,簡單的說就是不允許外界訪問,而對象的方法通常都是公開的(public)
,因為公開的方法就是對象能夠接受的消息。在Python中,屬性和方法的訪問權(quán)限只有兩種,也就是公開的
和私有的
,如果希望屬性是私有的,在給屬性命名時可以用兩個下劃線作為開頭,下面的代碼可以驗證這一點。
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并沒有從語法上嚴(yán)格保證私有屬性或方法的私密性,它只是給私有的屬性和方法換了一個名字來“妨礙”對它們的訪問,事實上如果你知道更換名字的規(guī)則仍然可以訪問到它們,下面的代碼就可以驗證這一點。之所以這樣設(shè)定,可以用這樣一句名言加以解釋,就是“We are all consenting adults here”。因為絕大多數(shù)程序員都認(rèn)為開放比封閉要好,而且程序員要自己為自己的行為負(fù)責(zé)。
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()
在實際開發(fā)中,我們并不建議將屬性設(shè)置為私有的,因為這會導(dǎo)致子類無法訪問(后面會講到)。所以大多數(shù)Python程序員會遵循一種命名慣例就是讓屬性名以單下劃線開頭來表示屬性是受保護(hù)的,本類之外的代碼在訪問這樣的屬性時應(yīng)該要保持慎重。這種做法并不是語法上的規(guī)則,單下劃線開頭的屬性和方法外界仍然是可以訪問的,所以更多的時候它是一種暗示或隱喻,關(guān)于這一點可以看看我的《Python - 那些年我們踩過的那些坑》文章中的講解。
面向?qū)ο蟮闹е?/h2>
面向?qū)ο笥腥笾е?code>封裝、繼承
和多態(tài)
。后面兩個概念在下一個章節(jié)中進(jìn)行詳細(xì)的說明,這里我們先說一下什么是封裝。我自己對封裝的理解是“隱藏一切可以隱藏的實現(xiàn)細(xì)節(jié),只向外界暴露(提供)簡單的編程接口”。我們在類中定義的方法其實就是把數(shù)據(jù)和對數(shù)據(jù)的操作封裝起來了,在我們創(chuàng)建了對象之后,只需要給對象發(fā)送一個消息(調(diào)用方法)就可以執(zhí)行方法中的代碼,也就是說我們只需要知道方法的名字和傳入的參數(shù)(方法的外部視圖),而不需要知道方法內(nèi)部的實現(xiàn)細(xì)節(jié)(方法的內(nèi)部視圖)。
練習(xí)
練習(xí)1:定義一個描述數(shù)字時鐘
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
created on 2019/5/6
@user: Keekuun
功能描述:
定義一個類描述數(shù)字時鐘
"""
import time
class Clock:
"""數(shù)字時鐘"""
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 __str__(self):
"""顯示時間"""
return '%02d:%02d:%02d' % \
(self._hour, self._minute, self._second)
def main():
clock = Clock(21, 53, 00)
while True:
print(clock)
time.sleep(1)
clock.run()
if __name__ == '__main__':
main()
練習(xí)2:定義一個類描述平面上的點并提供移動點和計算到另一個點距離的方法。
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
created on 2019/5/6
@user: Keekuun
功能描述:
定義一個類描述平面上的點并提供移動點和計算到另一個點距離的方法。
"""
from math import sqrt
class Point(object):
def __init__(self, x=0, y=0):
"""初始化方法
:param x: 橫坐標(biāo)
:param y: 縱坐標(biāo)
"""
self.x = x
self.y = y
def move_to(self, x, y):
"""移動到指定位置
:param x: 新的橫坐標(biāo)
"param y: 新的縱坐標(biāo)
"""
self.x = x
self.y = y
def move_by(self, dx, dy):
"""移動指定的增量
:param dx: 橫坐標(biāo)的增量
"param dy: 縱坐標(biāo)的增量
"""
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)
# 打印對象時顯示的內(nèi)容,避免打印出地址
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()
exercise python 100 days from https://github.com/jackfrued/Python-100-Days
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。