物件導向武功秘笈(2):招式篇— Python與Java的 ... - YC Note

文章推薦指數: 80 %
投票人數:10人

抽象化:抽象類別(Abstract Class)、抽象方法(Abstract Method)和接口(Interface). 事實上,剛剛使用 Animal 的方法並不是很正確,我們將 Animal 當作一個 ... 物件導向武功秘笈(2):招式篇—Python與Java的物件導向編程介紹 YCChen 2018-04-10 Coding 軟體設計 物件導向編程/類別(Class)與物件(Object)/方法多載(MethodOverloading)/物件導向三大特性—封裝(Encapsulation)/物件導向三大特性—繼承(Inheritance)/抽象化:抽象類別(AbstractClass)、抽象方法(AbstractMethod)和接口(Interface)/物件導向三大特性—多型(Polymorphism)/ 物件導向編程 在上一章當中,我們藉由好的程式碼的特性:「正常執行」、「穩健」、「不重複撰寫」、「可讀性」、「可擴展」,自然而然引出物件導向的概念。

在這一章當中YC會接續介紹完整的物件導向要如何實現,包括物件導向三大特性:封裝、繼承和多型。

在本章我會採用兩種語言交叉作說明,一種是靜態型別的語言Java,另一種是動態型別的語言Python,這兩種語言都是可以實現物件導向的語言,而所謂型別的動態與靜態可以用一個簡單的方法來區分:型別檢查(TypeChecking)發生在什麼時候?像Java這類的靜態型別語言,它的型別檢查是在編譯時期(CompileTime)完成的,而像是Python這類的動態型別語言,它的型別檢查則是在執行時期(Runtime)才去做,所以Python可以不事先宣告變數型別,這點使得Python在開發上方便許多。

雖然Python和Java都是支援物件導向的語言,但在使用上有很大的差異,首先,因為Python的動態型別,所以有些物件導向的性質對它來說就不是那麼重要,另外,因為Python追求簡潔,簡化了相當多的東西,所以很多的使用方法不同於傳統的物件導向,需要認識到這些差異才可以讓你使用Python的物件導向不會顯得很彆扭。

Java是一套對物件導向支援非常完整的語言,而Python是一套易於快速開發的語言,使用兩種語言說明物件導向是為了讓讀者更能了解物件導向的本質,而非語言本身。

本篇採用『大話設計模式』書中的物件導向篇範例。

類別(Class)與物件(Object) 首先來看物件導向的基本組成,類別(Class)與物件(Object)。

類別:建立物件的藍圖,描述所建立的物件共同的屬性和方法。

物件:一個自我包含的實體,物件包括屬性(Properties)和方法(Methods),屬性就是需要記憶的資訊,方法就是物件能夠提供的服務。

舉個例子,我想要創造一隻有名字的貓,她有喵喵叫的能力,在Java中可以寫成 /*Java*/ classCat{//{1} privateStringname;//{2} publicCat(Stringname){//{3} this.name=name;//{4} } publicStringshout(){//{5} return"Mynameis"+name+".meow~";//{6} } } publicclassTest{ publicstaticvoidmain(String[]args){//{7} Catcat=newCat("May");//{8} System.out.println(cat.shout());//{9} } } //output: //MynameisMay.meow~ {1}建構一個Cat的類別,類別不是物件,類別只是物件的藍圖。

{2}建立一個私有變數name,用來代表貓的名字,我們使用private的修飾詞讓它是私有的,也就是說外部環境沒辦法去讀取到這個變數,只有物件內部才可以讀取的到 {3}提供建造方法(constructor)來初始化這一個物件,初始化需要name的參數。

{4}在初始化的過程中,我們會將從外部讀取的name存入私有變數this.name裡,在Java裡頭,如果外部變數名稱與本地變數名稱相同,需要使用this來特別區分。

{5}創造一個公開的類別方法shout()。

{6}使用私有變數name讓貓可以自我介紹,再發出喵喵叫的聲音。

{7}Java只要遇到main就會去執行,方法main具有靜態方法的修飾詞static,也就是說Test不需要被實體化也能執行main這個方法。

{8}使用new來創造一個物件,在創造的過程會執行初始化,所以必須放入初始化需要的參數name,所以上面的新的物件有了"May"的名字。

{9}接下來使用cat.shout()去執行喵喵叫的動作,這個方法會回傳字串,再利用System.out.println的方法將字串顯示出來。

注意!在物件導向的習慣中,會用.來表示在那物件中的方法或屬性,所以cat.shout()就是執行在物件cat中的方法shout()。

再來看Python怎麼表示, ###Python3.4 classCat:#{1} def__init__(self,name):#{2} self.__name=name#{3} defshout(self):#{4} return"Mynameis"+self.__name+".meow~"#{5} defmain(): cat=Cat("May")#{6} print(cat.shout())#{7} if__name__=="__main__":#{8} main() #output: #MynameisMay.meow~ {1}建構Cat的類別,這是Python3的表示方法,如果是使用Python2.7的話,要寫成classCat(object):才可以。

{2}Python的初始化方法,Python在初始化之前會先自行執行__new__的方法,這個過程會產生一個新的物件,也就是實體化,而這個新的物件會以第一個參數的方法被帶入__init__的方法裡進行初始化,我們通常會命名這個變數為self,這裡的self已經是個物件而不是類別,那初始化的過程需要引入外部資訊name的參數來進行命名,所以第二個參數就要設name,記住喔!第一個參數是Python自動產生的,不是由外部帶入的,所以外部只要給name一個參數就足夠了。

{3}創造一個私有本地變數__name來將name存入,在Python當中以雙底線__開頭的變數會被視為是「私有的」,效果和Java的private接近,不過Python並沒有這麼嚴格禁止外部去讀取私有變數,所以需要配合工程師的自我規範。

{4}類別方法shout(),只要你不是靜態的類別方法,Python都會自動幫你帶入物件資訊當作第一個參數,通常命名為self,那為什麼靜態方法沒有自動帶入,因為靜態方法不用實體化,所以根本不擁有物件的資訊。

{5}使用到本地的self.__name變數 {6}創造一個物件,在創造的過程會執行初始化,所以必須放入初始化需要的參數name,所以上面的新的物件有了"May"的名字。

{7}接下來使用cat.shout()去執行喵喵叫的動作,這個方法會回傳字串,再利用print的方法將字串顯示出來。

注意!在物件導向的習慣中,會用.來表示在那物件中的方法或屬性,所以cat.shout()就是執行在物件cat中的方法shout()。

{8}在Python程式執行時,它的__name__會是"__main__",也就是說會去執行這個if判斷式下面的程式。

方法多載(MethodOverloading) 物件導向允許「使用不同的參數形式去實現同一個方法」,這就稱之為方法多載,這個方法涵蓋一般方法和構造初始化方法。

來延伸剛剛的貓的例子,假設今天我們允許用戶不去設定貓咪的名字,而程式會預先給貓咪No-Name的預設值,所以我們需要另外一個初始化方法是不用貓咪名字的參數形式。

Java的實現程式碼如下所示,如此一來只要碰到沒有參數的形式,程式會給予"No-Name"的名字去當作貓咪的名字,並進行初始化。

/*Java*/ classCat{ privateStringname; publicCat(Stringname){ this.name=name; } //methodoverloading publicCat(){ this("No-Name");//given"No-Name"asitsname } publicStringshout(){ return"Mynameis"+name+".meow~"; } } publicclassTest{ publicstaticvoidmain(String[]args){ Catcat=newCat();//no-argumantformat System.out.println(cat.shout()); } } //output: //MynameisNo-Name.meow~ 但在Python當中,不允許這種「相同方法名稱,卻又不同參數形式」,Python採用其他的方式來產生同樣的方法多載效果,如以下所示,我們可以看到Python使用default方法來實現多載,只要我們不給予name,它的default就是"No-Name"。

###Python3.4 classCat: def__init__(self,name="No-Name"):#name'sdefaultis"No-Name" self.__name=name defshout(self): return"Mynameis"+self.__name+".meow~" defmain(): cat=Cat()#no-argumentformat print(cat.shout()) if__name__=="__main__": main() #output: #MynameisNo-Name.meow~ 物件導向三大特性—封裝(Encapsulation) 還記得「低耦合,高內聚」的原則嗎?為了符合這原則,每個物件都要盡可能的去包含需要用到的屬性和方法,並且使得外部不能以不合理的方法去影響物件,這就稱之為「封裝」。

我們來看看上次的成果,我們就用這個例子來說明「封裝」。

###Python3.4 importsys classCalculation: def__init__(self,nums): self.__nums=nums#{1} fornuminself.__nums: self.__checkPositiveInteger(num) def__checkPositiveInteger(self,num):#{2} if(notisinstance(num,int))or(num<=0): raiseValueError("invalidpositiveinteger:"+str(num)) def__primeFactorize(self,num):#{3} prime_factorize=dict() i=2 while(num>1): ifnum%i==0: prime_factorize[i]=prime_factorize.get(i,0)+1 num/=i else: i+=1 returnprime_factorize deffindGCF(self): prime_factorize=list() fornuminself.__nums: prime_factorize.append(self.__primeFactorize(num)) common_prime=set(prime_factorize[0].keys()) forpfinprime_factorize[1:]: common_prime&=set(pf.keys()) gcf=1 forprimeincommon_prime: m=sys.maxsize forpfinprime_factorize: m=min(m,pf[prime]) gcf=gcf*(prime**m) returngcf deffindLCM(self): gcf=self.findGCF() lcm=gcf fornuminself.__nums: lcm*=int(num/gcf) returnlcm {1}使用私有變數,讓外部不能任意的改變self.__name變數,在這個例子當中,如果self.__name被任意改變,它將會逃過__checkPositiveInteger的檢查。

{2}&{3}不需要由外部讀取的方法,就盡量讓它是私有的。

再回到貓咪的這個例子,如果我們想要可以調控「叫聲次數」的話,可以這樣實現。

/*Java*/ classCat{ privateStringname=""; publicCat(Stringname){ this.name=name; } publicCat(){ this("No-Name"); } privateintshout_num=3;//{1} publicintgetShoutNum(){returnshout_num;}//{2} publicvoidsetShoutNum(intnum){//{3} if(num<0)thrownewjava.lang.IllegalArgumentException(); shout_num=num; } publicStringshout(){ Stringresult=""; for(inti=0;i



請為這篇文章評分?