2014年1月22日 星期三

[Java/多型性]多型性和強制轉換類別... (試過才知道)

真的稍微搞懂Java物件導向的人,都知道多型性是啥。

「參數型別宣告為父類別,則可以存入所有子類別。」.......講得很潦草,但大概是那樣。

可是這個特性具體來說會造成什麼影響?

子類別A強制轉型為子類別B時,會發生什麼事?

要測試多型性,自然要寫物件。以下有A(只是測試,就別花心思去取名了,)和A1/A2。

接著是初步測試用的程式......


可能有些人會理解、可能有些人不會,甚至有些人直接笑我蠢,但我真的想知道參數和函數對型別強制轉換的反應有何差異....

如果A2的test()內的傳入參數型別指定為A,因為多型性的理由,不用考慮一定都會順利執行。

但如果指定為A2,然後用A或A1強制轉型呢?



父類別似乎無法順利強制轉型為子類別,...不僅僅只是會損失資料而已,重新叫出了error訊息,訊息告訴我「無法如此轉型」。

顯然類別之間的型別轉換跟基礎數值的型別轉換不同,不是只會損失資料而已,而是根本上就限制了使用方式。

所以......所謂的「多型性」並不是種物件導向的特性,而是種人為操作/設計出來的規則。

不知道有沒有人能驗證這樣的解釋?



這次的實驗感覺很基礎很簡單很沒意義,但我不想知道怎樣可以成功,不想知道怎樣可以搞出「讓世人讚嘆的多型性應用」,但我對「怎樣操作多型性會失敗」很有興趣。就.......「浪費時間」吧!

2014年1月17日 星期五

[Java/Android]選取Spinner的內容同時改變Button的功能 (我會怎麼用純物件的方式寫這個功能)

(接續上一篇)還是這個功能/介面,資料處理方式可能是「各種加密方法」或「將資料傳向特定網站或資料庫」。

用「進化」的觀點來看,結構化程式設計技巧確實比物件化程式設計技巧更基礎。(前者在60~70年代出現並成熟、被發揚光大,後者雖然同時期出現,但Java卻是90年代的東西了。)

不用物件導向技巧還是可以在物件導向語言裡寫出可以運作良好的程式,只是...「維護」和「擴充」(特別是要由第三者來維護跟擴充)就很讓人「蛋疼」了。(女工程式請不要介意...)




我會先設計一個這樣的介面Interface(不是UI介面)。

看名稱應該就知道,這是擴充後要給Spinner/下拉彈出式選單作為內容的介面。

我擴充這個介面產生一個新類別,並在getText()中設定「return "功能一"」,如果用這個類別宣告實作物件,並將物件塞給Spinner的SpinnerAdapter,則選單中就會有「功能一」這個選項。以此類推...要多少有多少。(中間有一些設定SpinnerAdapter的細節,如果不知道自己推敲應變,那我這段描述會變得很怪。或者你知道使用其他Adapter來取代......)

然後再設計一個OnItemSelectedListener,──給Spinner使用的。這個OnItemSelectedListener具有「偵測」使用者選取了哪個Spinner選單內容的能力。(看到第22行的程式碼,那是個函數,裡頭有個「int position」,這就是「使用者選擇的參數」,──或者說是「選單中的第幾個選項」。)

Button b1就是「送出」鈕。藉著將這個Listener設定給Spinner,程式會即時、動態(使用者每選擇一次選單內容)的變換這個按鈕的OnClickListener。(list就是用來「裝」選單內容的地方/物件。)

(上面這個程式範例有嚴重的錯誤就是「它是OnItemClickListener」。但Spinner雖然保有這個屬性,但卻無法使用,執行Spinner的setOnItemClickListener......還沒成功過。幸運的是,除了名稱不一樣以外,它和OnItemSelectedListener結構與傳入參數都一模一樣。)



如下圖,這是Activity,可以看到我一共產生了三個選項,分別是「Test1」、「Test2」、「Test3」。(恕我省略HsuSpinnerAdapter的內容。因為就是個繼承自SpinnerAdapter的類別)



這支程式只會很可笑的把「Test1」、「Test2」、「Test3」作成小框框內的文字顯示在畫面上而已。但確實是「三個不同的功能」。



如果想要,可以自己設計獨特的SpinnerItem,然後放進list中,未來要擴充功能就會變得很簡單。

「難道不用物件導向技巧會變得很難寫嗎?」

這樣假設吧......今天有個「Test1.5」要放進「Test1」和「Test2」之間,那可能只要在ArrayList<String>的 「Test1」和「Test2」之間放進「Test1.5」,就會出現這個選項。

問題是「送出」按鈕的OnCLickLitener中,要將所有判斷式的position值重新修改,......其實也不難,但是如果又出現了「Test1.4」、「Test1.3」、「Test2.1」.......無限增加中,只要這個程式還活著、專案管理和企劃人員腦筋動個不停,這種事情就會發生。

當OnClickListener的程式碼長達八九百行、甚至一千五百行時,不停重複修改一支Class就不是讓人很愉快的事情了。

所以為何不在程式剛開始、專案剛起頭時,想好一個將來方便擴充的機制呢?

(文章寫到這裡,我又想到一個修改SpinnerItem的方法,將來就算要直接新增一個選單,──也就是一共會有兩個選單──也可以輕鬆擴充、修改的設計,......物件導向就是這麼迷人。)



上面的那個問題好像有點廢話,但問題是「有些工程師會認為像我這樣寫程式才是廢話。」

因為「物件架構」不是一翻兩瞪眼的東西,如果架構不可行,理論上有時後看不出來,要有具體程式碼才能驗證,而且驗證一次不夠,可能要兩三次。

第一次驗證失敗後,已經有一個功能和三千行程式碼,第二個功能進行設計完成時,要進行第二次驗證,結果又失敗,這時已經有六千行程式碼,結果這六千行幾乎都要打掉重寫,重第一個功能開始驗證......重複這樣的過程,直到確定架構可以重複運作在...「所有功能上」。

可能五六個星期已經過去了。

但是......直接抓起規格書,開始用土法煉鋼的「一個功能就一個物件/Activity」這樣寫,可能兩個禮拜就把初步規格書上所有的功能都完成了。

當這些工程師可以不斷一次又一次超前進度、快速精準完成專案時,「物件導向」就變成了「Java語言不方便的特性」而已。去精熟物件架構、去專研怎樣設計物件架構、累積設計物件架構的經驗.......只是一場笑話罷了。




這就是JAVA設計師的絕境與哀愁啊!物件導向其實好學!只要認真寫教學文、教學範例。甚至可以用Library重新包裝所有JAVA基礎程式指令,例如更精簡、更快速的Socket建立方式。寫JAVA?哪那麼難?

但是一堆人把JAVA當成單純的結構式語言、當成C/C++工程師找工作時的代替品,認為「要精通很多指令」(或說是「同一個指令要有很多次使用的經驗」)、「要能閱讀並掌握大量程式碼」、「要能快速、用直線思考的方式實現規格書的需求」......才是工程師之道。

2014年1月16日 星期四

[Java/Android]選取Spinner的內容同時改變Button的功能 (為何程式設計師流動率高?)

上面(假設)是個Android功能的UI,資料處理方式可能是「各種加密方法」或「將資料傳向特定網站或資料庫」。

EditText和Spinner的使用及設定方式,我不想花太多力氣詳述,(我講概念/心得/天馬行空亂撞的結果,不講別人寫死的技術規格。)

一般來說,程式寫法很多、怎樣寫都行,但我覺得最糟糕的寫法就是用匿名/無實體的類別搭配ArrayList<String>來作為SPinner的Adapter。



首先在會使用到這個介面的Activity中設定一個參數int p,同時設定一個OnItemClickListener,在這個介面的功能onItemClick(AdapterView<?> parent, View view, int position, long id)中,int position參數可以傳給p( p = position )。

「送出」按鈕的OnClickListener則是會判讀參數p的值,如果是0,就表示使用者選擇了方法一,如果是1,就表示使用者選擇了方法二...以此類推,然後在各個判別條件後寫入真正的「方法一/方法二........」的程式碼。



而且,這些Adapter/OnItemClickListener/OnClickListener都是用匿名/無實體的類別來設計。

程式碼全都擠在一支Activity的程式中。 (對這些人來說,Java的「物件導向」只是寫Java程式不得不應付的「規則」和「語法」罷了!類似「每支程式都是用class這串字當開頭」之類的限制。他們很擅長應用別人寫好的物件、把那些物件當成基礎指令套件一樣使用,他們知道怎麼把程式結構化,但他們並沒有真的在思考怎麼把程式物件化。)



我碰到一堆人把「這樣才是成熟的工程師寫程式該有的風格」當成聖經。

即使這個功能將來如果要新增Spinner中的「方法」好達到擴充功能的目的,結果接手的人找不到「方法」寫在什麼位置,而導致整個程式只能無條件要打掉重寫........(一個功能只有一個輸入框、一個彈出選項、兩顆按鈕的功能不太可能發生這種「接手的人找不到舊功能程式碼寫在什麼地方」的窘境,所以大家假設一下這個功能有三個輸入框,同時有兩個彈出選向來交叉決定「功能」,還有三個按鈕可以選取,同時這個功能要能應付至少六種客製化版本........)

怎辦?「換個人來寫程式。」

他們永遠不會發現「自己家的Java工程師流動率非常高,特別是菜鳥陣亡率奇高無比!」他們只會慢慢的歸納出一個結論:真的會寫程式的人好少。



(下一篇......)

2014年1月15日 星期三

[Java]用介面Interface使用「用介面Interface訂出來的參數架構」

 (接續上一篇「用介面Interface設計參數架構」...)

有點饒口的標題。

我相信(因為這也曾經困擾我)一個程式語言的語法好學習,迴圈、判斷式、運算子、函式定義、物件宣告......可是怎麼把這些「規矩」組合成「功能」,就很難學......

因為鮮少有人在教。

如果在網路上、在論壇上,明確的講出自己想要的功能,其實得到的往往是連「模糊」都稱不上的答案。當然不能怪那些程式老鳥們這種態度,畢竟「又沒錢拿」,如果菜鳥們、出學者們都乖乖掏個幾千、甚至幾萬塊去走進補習班和短期課程,學程式設計肯定會好混許多。



可是學習方式應該有很多種。從「規矩」到「具體功能」之間,明顯有個難以跨越的門檻,沒理由要讓「任大家自生自滅,可以自己領悟的人才有資格進入程式設計殿堂」變成放諸四海、天下人接要遵守的規矩。

所以我用我的風格寫這些文章,或許......有人看過這些文章後可以有比讀其他文章、鑽研被公開分享的範例更有效率的學會程式設計。



接續上一篇「用介面Interface設計參數架構」...

(別告訴我你不懂何謂參數......我還沒準備好寫那麼基礎的東西。)

用「介面設計參數」這話直覺上違反Java特性,因為寫在介面中的參數會變成final形態,是不能被修改的。

可是 (請注意接下來這段話特指物件導向,所以看完立刻以為「你挑出了明顯的錯誤和例外」的人請去重讀物件導向) ...「物件導向的特性讓參數和函式在記憶體中都只是個位址,差別在於參數的位置可能導向另一個存放數值的記憶體區塊,函數的位置則是導向存放一連串的運算式、指令、判斷式、迴圈的記憶體區塊, 共通點在於它們都是個「另一段記憶體區段的索引」罷了,(主要的差異在於:函數存的索引值不能改,可是參數可以。另外就是程式碼的寫法不同。)

所以.......「為何不直接把函數拿來當參數用?反正在多型性的程式世界中,編譯器才不會好奇為何設計者設計的父類別沒有參數只有函數。」

但這都是 「用介面Interface設計參數架構」中的內容。



這篇講的其實是「多形性」到底怎麼應用來寫程式。或是說「用多型性寫程式」到底是會寫出什麼樣的程式。

這個介面只有定義一個函式。而這個函式要求傳入一個屬性為ValueInterface的參數。

為何只定義一個函數?

這是因為「大家習慣用IDE編輯器來編寫Java」,而「IDE編輯器(這裡是指Eclipse)讀取註解時有些特性」。

下圖是個「實作」這個介面的程式/類別/物件...(我又迷失在專業術語中了。)

先不看函數中的程式碼,(此為物件導向寫作法的好習慣,)注意一下開頭的註解(第36到39行的程式碼),這個註解會在任何寫了Operate1這個物件名稱的地方跳出來。(只要把滑鼠游標一到上面,就會跳出這段程式碼。

像下圖...可以看到游標一到Operate1上,就自動彈出一個小視窗,顯示註解內容。

有趣的問題是ops這個矩陣中,不管是ops[0]、ops[1]、或ops[2],都有operate()這個函數。

可是如果把註解寫在Operate1的operate()函數上,游標移到接下來的程式碼中出現的operate函數時,什麼都不會顯示。
因為...這個operate()是介面ValueOperatorInterface的operate(),不是Operate1的,而我在介面中沒有寫任何註解,自然不會顯示。

詳細一點說明為何只設計一個函數:當這個物件只有一個函數時,你寫在建構子上的註解自然會是它唯一的功能說明,閱讀性理論上會好一點。可是我工作的地方,大部分的工程師都還是習慣結構導向寫法,物件導向...頂多就是「記得寫個建構子」,多型性?好像沒啥人真得在活用。



基於多型性的理由,我不會考慮直接用Operate1當成參數的型別/屬性,(但是這樣設計程式沒什麼錯,更沒什麼問題,就是有點可惜罷了。而且如果要這樣作,就沒有必要寫介面了。)

但說到所謂的多型性...我可以隨意組合ops矩陣的內容,而且都會成立、都可以精準運作。(除果出錯,絕對是程式出錯,而不是「多型性」這個原則有例外。)

有了多型性,我設計好了各種不同的Operator類別,然後在各種不同的程式中組合它們......我就可以快速擴建出各種不同功能的程式。




一般會反駁我這種寫法、認為這樣很多餘的人,都會認為...「與其我設計三種Operator類別,不如我在ValueInterface中額外增加三個函數,(甚至直接就用物件寫,不需要經過設計ValueInterface的步驟,)然後把本來要寫在三種Operator中的程式碼般到三個函數中。」(參考一下我平常測試的方法...這次不附上測試方法了。不過,如果覺得這樣測試根本不對......根本不重要啦!原因...繼續看下去。)

所以我一開始要解釋參數與函數的差異......

我原始的想法是認為......一個只有兩個函數(一個讀取參數、一個寫入參數)的介面會在記憶體中佔去兩個單位的記憶體,一個有五個函數的介面自然就是在記憶體中佔去五個單位的記憶體。如果用矩陣大量操作這個ValueInterface,例如說「現在要1000個ValueInterface」,想想看「2*1000」和「5*1000」,會有多少差距?

但後來發現......其實不會有差。設計兩個類別,都只有一個參數,然後甲類別有兩個函數,乙類別有五個,分別用一個ArrayList無限量去「產生、並收納」,結果會發現...我在Android模擬器下分別去執行,會發現大概都在89300000次的執行上下時,會出現「OutOfMemory」的錯誤。

反過來,如果將乙類別的函數全砍掉,改成八個參數,(參數的結構不詳述,)結果執行次數一口氣就掉到14300000左右而已。

簡單來說,我對於在物件導向機制下Java應用記憶體的方式假想試錯誤的,「函數並不會隨著類別宣告為實體物件而增加佔去的記憶體空間,只有參數會。」不管類別A被產生了幾個物件實體,函數都導向/共用同一個記憶體區塊。

(所以我上面說「測試方法對不對」並不重要......仔細研究了一下物件導向的機制,確實如此。)

我並不能夠說服自己:這樣寫,程式的效能和記憶體的使用效率比較高。──並不會!但我還是繼續這樣寫程式。



為何要這樣寫程式?因為......

專案方便擴充!提高程式碼利用度!

如果不盡可能徹底使用物件導向,一個程式未來如果要擴充功能,難度明顯會高許多。

上面提到的「與其我設計三種Operator類別,不如我在ValueInterface中額外增加三個函數,(甚至直接就用物件寫,不需要經過設計ValueInterface的步驟,)然後把本來要寫在三種Operator中的程式碼般到三個函數中。」假設一支程式經過無數次的改寫擴充後,已經從三個函數增加到十三個函數,每個函數的平均工能從三十行程式碼增加到六十行程式碼......然後今天又要再增加兩個新函數,然後有至少五個舊函數的功能要修改........請便吧!看到這種程式碼,我每次都有罷工的衝動。

把要新增加的功能額外獨立成一支程式/類別,然後再要擴充的地方增加一個參數。像下圖,我只要新設計一個實作 ValueOperatorInterface的類別,然後在switch中找個地方插入它即可......

雖然這樣沒有徹底作到「舊程式永遠不會膨脹」,但是修改的難度低、幅度永遠不會過大,而且程式的邏輯/模式永遠不會變化太大,不會因為逐次擴充/修改/維護,而失去原樣。

這個範例使用的是switch指令。假想一下今天使用的是「if...else...」然後每一段「...」裡頭都有數百行程式碼,第一次拿到程式碼、又要再上頭擴充功能的人,可能就會迷失在追蹤各種if條件值中,──可能很難搞懂自己新增加的if條件為何沒有順利發揮作用,到底是哪個if、或哪個if中的if把程式的執行判斷權拿走了。(「執行判斷權」?我又發明了一個莫名其妙的名詞。)

當那「數百行程式碼」被縮減成一個物件和它的函數時,if就很好追蹤了。(就像這個switch......天哪!比這個switch還好用。)



另外,如果不盡可能徹底使用物件導向卻又牽涉到「版本控管」......先假定物件導向跟專案管理無關,一般的專案管理就只能期望專案管理軟體夠強、或專案團隊製定的專案管理流程夠嚴謹.......好笑的是:JAVA物件導向的精神之一就包含了解決上述問題。

使用Java撰寫程式不善用物件導向特性,卻期望「專案管理」軟體代勞......我不知道這叫什麼,只是發現這想法/態度/策略挺普遍的。

有機會在介紹一個更明確的「多型性」寫作法。

2014年1月13日 星期一

[Java]用介面Interface「設計參數架構」

標題所謂的「參數架構」是指擁有/繼承自同一個父類別的所有子類別們,都會有相似的參數型態。

如果類別A有個屬性為開放(public)的參數X,那繼承類別A的類別B和類別C都會有參數X。

如果我在程式中宣告了一個參數Y,這個Y的種類為類別A,(這個時候或許應該稱Y為物件Y,或實作Y,.......這方面的名詞我一向很不講究,)那我不管塞進了類別B或類別C,後續的程式都可以在參數Y中另外獲得參數X。(當要讀取或修改「Y.X」時不會發生錯誤。)

或許可以稱這樣的目的為「設計參數架構」。



學Java都會學物件導向。

教學範本大多從Object的extends開始教起,其次才會講到怎麼自己設計介面/接口。

結果......大家最後都直接習慣設計Object、擴充自己寫的(或別人寫的)Object,然後沒啥人在用介面/接口。

我個人用介面的頻率算挺高的...事實上,我覺得寫Java程式就是先訂出Interface,並且已Interface為程式邏輯/流程設計和運作的基礎/核心。

「但是Interface有很多限制,寫在裡頭的參數都會被強制轉成final形態,如此一來不管是開放(public)、私用(private)、限制(protected),都沒有意義了。」

(用前面參數Y作範例來解釋的意思是:如果把參數X1寫在介面D中,則繼承/擴充介面D的類別E和類別F也會有參數X1,但這個X1是個「常數」,只能讀取,對它作修改......不會出錯,但不會有意義,X1的值會永遠保持在設計者寫在程式碼中的值,如果我明確寫了X1=0,不管我後來寫了什麼程式碼,X1就是0。)

不懂我在描述的狀況是什麼?或不曾有過類似的疑惑和需要?

反正.......

介面/接口/Interface也是可以拿來幫參數訂架構的。

這是一個介面。(public interface後面的ValueInterface是這個 介面/接口/Interface的名稱......)

像這樣......  我就可以用a()這個函式/接口來產生一個「參數a」了......

如果要把「參數a」傳給「參數b」,我就用「b = a();」。

如果要把「參數a」加上「參數b」,我就用「a( b );」。

像以下的Value0...



 注意到我想要達成的效果了嗎?可能還不夠明顯......

總之,凡是介面ValueInterface的子類別,都會有參數a。

至於參數a(第91行)的「真面目」,可能可以是(x+y),參數a(第94行)則是(y = i)。

感覺上沒什麼不同,只是想證明「也可以用介面形式來達到設計參數」。這些範例並不是非常有意義、更不用提效率與價值了。



這樣作具體來說有任何好處嗎?

Java的設計是「一個物件只能繼承一個物件」,如果類別甲繼承自類別乙,雖然它會完整的繼承類別乙的數值結構,但如果它同時也需要類別丙的數值結構呢?

如果類別丙的數值結構是在介面之下用我這裡所寫的形式,這就不會是問題了。

一連串的類別可以任意搭配一系列的介面......這裡可以有也是用來「設計參數架構」的介面丙一、丙二、丙三.......

可以用丙一和丙二搭配出類別一,用丙一和丙三搭配出類別二。不管是類別一或類別二,都可以塞進屬性為丙一的參數中。

「能」這樣設計程式不就是物件導向的特性嗎?作為全物件導向語言之一,使用Java卻不善用這種特性與思維......何必呢?它的「可攜性」可沒那麼強大。