封不封裝有差嗎?


你可以設計一個帳戶類別來持有一個銀行帳戶所需的資訊:
public class Account {
    String name;
    String id;
    double balance;
}

這只是單純將類別作為多個資料的集中地。如果要提款,你也許會這麼撰寫程式:
Account account = new Account();
...
account.balance =
account.balance - money;

如果money是來自於使用者的輸入,你也許應該防範一下被輸入負數:
if(money > 0) {
    account.balance = account.balance - money;
} else {
    throw new RuntimeException("不得為負數");
}

如果Account類別只是你一個人用,你也許會記得要作檢查,但如果別人打算使用Account類別,就不見得會記得這件事。為此,你也許會這麼寫:
public class Account {
    String name;
    String id;
    double balance;

    public void withdraw(double money) {
        if(money > 0) {
            account.balance = account.balance - money;
        } else {
            throw new RuntimeException("不得為負數");
        }
    }
}

這麼一來,使用Account的其他人,就只要呼叫withdraw()方法,就可以進行提款動作,也可獲得檢查是否為負數的行為:
Account account = new Account();
...
account.withdraw(100);

不過事實上,使用Account的人若打算這麼作,目前你的Account是沒什麼方法阻止的:
Account account = new Account();
...
double money = -10000;
account.balance = account.balance - money;

你設計的檢查機制沒派上用場,因為他可以直接存取Account物件的balance(如果存心要惡搞的話)。你應該讓他沒辦法直接存取Account的資料成員:
public class Account {
    private String name;
    private String id;
    private double balance;

    public void setName(String name) { this.name = name; }
    public void setId(String id) { this.id = id; }

    public void withdraw(double money) {
        if(money < 0) {
            throw new RuntimeException("不得為負數");
        }
        balance = balance - money;
    }
}

由於類別的資料成員用了private宣告,它們會是Account的私用成員,對使用Account的人來說,他看不到也存取不到private成員,就算知道內部private成員的名稱,撰寫了先前的程式碼:
double money = -10000;
account.balance = account.balance - money;

編譯器也不會讓這樣的程式碼通過編譯,這麼一來,使用Account的,就只能透過你所提供的公開方法來操作,或修改Account的內部狀態。

假設你的Account已經有人使用了,現在你發現,withdraw()應該再檢查一下餘額是否足夠,不然會被提款成為負餘額,所以你作了修改:
public class Account {
    private String name;
    private String id;
    private double balance;

    public void setName(String name) { this.name = name; }
    public void setId(String id) { this.id = id; }

    public void withdraw(double money) {
        if(money < 0) {
            throw new RuntimeException("不得為負數");
        }
        if(
balance - money < 0) {
            throw new RuntimeException("額餘不足");
        }
        balance = balance - money;
    }
}

你重新編譯這個類別之後,使用這個類別的客戶端並不用作任何修改,因為他們只使用公開的操作介面,對實際上withdraw()的內部實作一無所悉。

這是個封裝的簡單例子。「封裝」一如其名稱所示,將一些資訊封存包裝在物件之中,這些所謂被封裝的資訊包括了物件的內部狀態、行為的實作細節。由於封裝,你就只需要在意如何操作物件,以及物件之間如何互動。

就語法上而言,封裝是在定義類別時,將資料成員設定為private,使之無法被外界存取,資料成員既然設定為private,若需提供給外界存取時,則必須提供公開操作介面,具體來說也就是公開方法,這讓你有機會在方法實作時進行相關流程,確認對資料成員的存取方式。