Posted: 2017 年 11 月 30 日 in Uncategorized

給OOP初學者的建議:

先搞懂「資料跟行為在一起」就好,其它的慢慢來

初學者接觸OOP,幾乎都會有以下疑惑:
我到底為什麼要學OOP?OOP解決了什麼問題?書上這些範例就算不用OOP也寫得出來吧?
然後覺得「繼承」、「多型」、「介面」、「抽象類別」等等的名詞很難,覺得OOP很難。
其實這些名詞雖然重要,但對新手來說,本來就很難在一開始就搞懂。
建議先搞懂「資料跟行為在一起」是什麼,以及它的好處在哪,就可以了,其它的慢慢來。
什麼叫做「資料跟行為在一起」?
假設我們在開發一個「中英文互助學習網」,鼓勵中文人士與英語人士登入討論。
這個系統的貼文、留言功能會顯示「發文日期」。
發文日期要根據使用者的註冊身份(台灣人、英語人士)顯示不同格式(台灣格式、西方格式)。
下面就以這個日期格式的功能舉例說明「資料跟行為在一起」是什麼意思。
作法一:直接硬寫(不OOP、資料跟行為混在一起)
初學者通常會用最簡單、也最直覺的作法,直接硬寫出來,像這樣:
<? php

# 假設資料庫取出來的發文日期長這樣
$postDate = '2016-06-02';

if (/* 判斷是否顯示台灣格式 */) {
# 轉換成這樣 2016.6.2
$arr = explode('-', $postDate);
$year = $arr[0];
$month = $arr[1];
$day = $arr[2];
echo "$year.$month.$day";
} else { // 西方格式
# 轉換成這樣 6/2/2016
$arr = explode('-', $postDate);
$year = $arr[0];
$month = $arr[1];
$day = $arr[2];
echo "$month/$day/$year";
}

這種寫法的資料(日期)跟行為(轉換成各種格式)混在一起。
它的優點是寫起來很簡單,缺點則有兩個:
* 日期格式的邏輯會重複出現在很多地方,整段code會到處重複出現
* 整段code大概會塞在

或是的裡面,導致它跟HTML混在一起,很亂
作法二:自訂函數(不OOP、資料跟行為沒混在一起)
為了解決作法一遇到的問題,聰明的初學者很快就想到可以用「自訂函數」!就像這樣:
<? php
function localFormat($date){
$arr = explode(‘-‘, $date);
$year = $arr[0];
$month = $arr[1];
$day = $arr[2];
return “$year.$month.$day";
}

function englishFormat($date)
{
$arr = explode(‘-‘, $date);
$year = $arr[0];
$month = $arr[1];
$day = $arr[2];
return “$month/$day/$year";
}

$postDate = ‘2016-06-02’; # 假設資料庫取出來的發文日期長這樣

if (/* 判斷是否為台灣人身份 */) {
echo localFormat($postDate);
} else { // 英語人士身份
echo englishFormat($postDate);
}

這種寫法將行為(轉換成各種格式)用自訂函數給獨立出來,也大幅改善了作法一遇到的問題。
對小型的網頁程式來說,這招非常好用,不但開發快速、簡單,還漂亮地將資料跟行為拆開。
但是程式規模變大之後,為了將各種行為拆出來,會寫出很多自訂函數,類似這樣:
<? php
function localFormat($param)
{
// blah blah …
}
function englishFormat($param)
{
// blah blah …
}
function someTask($param)
{
// blah blah …
}
function anotherTask($param)
{
// blah blah …
}
function otherTask($param)
{
// blah blah …
}
//…

於是又衍生出三個問題:
1. 像localFormat、englishFormat這樣的函數名稱意義模糊,看不出是處理日期、人名,還是什麼東西的格式
2. 這些自訂函數各有不同的行為,全部放在一起顯得很亂,應該要想辦法分類、整理這些函數
3. 像localFormat、englishFormat這樣的函數,只吃特定格式的參數,最好能跟某種資料的形式綁在一起,以後要改程式時,能讓相關的資料跟行為一起被看到
問題1很好解決,只要替函數名稱加前綴字變成dateLocalFormat、dateEnglishFormat就行了。
問題2也很好解決,只要多開幾個檔案,把相關的函數放進同一個檔案就行了。
問題3就很棘手,資料跟行為拆開之後,如何在概念上又找方法整理在一起?
作法三:使用class(OOP、資料跟行為在一起)
正是這些處理資料、整理行為的問題,導致了OOP的誕生:
<? php
class Date
{
public $year;
public $month;
public $day;
public function __construct($date)
{

$arr = explode(‘-‘, $date);
$this->year = $arr[0];
$this->month = $arr[1];
$this->day = $arr[2];

}

public function localFormat()
{
return $this->year . ‘.’ .$this->month . ‘.’ . $this->day;
}

public function englishFormat()
{
return $this->month . ‘/’ .$this->day . ‘/’ . $this->year;
}
}

$postDate = ‘2016-06-02’; # 假設資料庫取出來的發文日期長這樣

$date = new Date($postDate);

if (/* 判斷是否為台灣人身份 */) {
echo $date->localFormat();
} else { // 英語人士身份
echo $date->englishFormat();
}

OOP的寫法,一次解決了前述三個問題:
問題1 => 現在從類別名稱就可以知道底下方法的意義了
問題2 => 現在相關的函數都整理進同一個類別底下成為方法了
問題3 => 現在資料的形式都統一在constructor處理一次,之後不管新增多少方法都不用處理資料了
這就是所謂的「資料跟行為在一起」,也正是OOP的核心概念。
利用這種方式整理程式碼、寫出一個又一個的類別,可以大幅提昇程式碼的品質。
結論
上述的作法一跟作法二並沒那麼糟糕,但確實會帶來一些問題。
對於小型的網頁程式來說,可能還算夠用。
但是隨著程式規模變大,如果將概念上相關的資料跟行為整理在一起,會很有幫助。
實務上也可以先從作法二開始寫起,直到發現某些資料跟行為關係密切,再拉出來整理成類別即可。
至於很多OOP教學會提到的「繼承」、「多型」、「介面」、「抽象類別」等等名詞,一時搞不懂沒有關係,你可能實務上也暫時用不到。之後找時間慢慢搞懂它們的用途就好。
光是知道「將資料跟行為放在一起」的技巧,就能夠開始寫OOP程式碼了。

發表留言