iT邦幫忙

2021 iThome 鐵人賽

DAY 7
0
Software Development

幫自己搞懂物件導向和設計模式系列 第 7

關於封裝

什麼是封裝

In object-oriented programming (OOP), encapsulation refers to the bundling of data with the methods that operate on that data, or the restricting of direct access to some of an object's components. Encapsulation is used to hide the values or state of a structured data object inside a class, preventing direct access to them by clients in a way that could expose hidden implementation details or violate state invariance maintained by the methods.

封裝限制了外部環境存取、操作物件內部屬性和方法的能力,另一方面,也隱藏了物件當中方法實作的細節。

限制存取權限

以先前的例子來看,在 BaseballPlayer 類別當中有個屬性 name,當建立實例之後,它讓外部環境(那些呼叫 jeter.name 的人)可以取得 name 的資料

class BaseballPlayer {
  name: string

  constructor(name: string) {
    this.name = name
  }
}

const jeter = new BaseballPlayer('jeter')
jeter.name            // jeter

然而我們先前沒有提到的是,其實外部環境有機會修改這個值:

jeter.name = 'peter'
jeter.name            // peter

如此一來 jeter 變成 peter,就天下大亂了!

所以在物件導向程式語言當中,我們可以透過

  • public
  • private
  • protected

的標註來限制外部環境對於物件內部屬性和方法的操作。

譬如以下面的例子來說,我們讓 name 屬性變為 private,讓外部環境無法存取,而同一時間,我們建立了 getName 方法來讓外部環境呼叫並取得 name 的值

class BaseballPlayer {
  private name: string

  constructor(name: string) {
    this.name = name
  }

  getName(): string {
    return this.name
  }
}

const jeter = new BaseballPlayer('jeter')
jeter.getName()            // jeter

所以如果有人想用先前同樣的方法取得 name 或是修改 name 的話,就會造成錯誤

jeter.name                 // error
jeter.name = 'peter'       // error

private 讓外部環境無法操作物件內部的屬性和方法,相反過來,public 就是完全公開。在沒有任何標注的情況下,預設所有屬性和方法都是公開的。

比較特別的是 protected,它讓外部環境無法操作物件內部的屬性和方法,但是可以讓繼承的類別使用 protected 的屬性和方法。

隱藏實作細節

以下面這個例子來看,getName 方法將 firstNamelastName 兩個屬性的資料整合在一起

class BaseballPlayer {
  private firstName: string
  private lastName: string

  constructor(firstName: string, lastName: string) {
    this.firstName = firstName
    this.lastName = lastName
  }

  private getFullName(): string {
    return `${this.firstName} ${this.lastName}`
  }

  getName(): string {
    return this.getFullName().toUpperCase()
  }
}

所以當我們呼叫 getName 的時候,會出現全名。但實際上,外部環境呼叫 getName 的時候,其實可以完全不需要知道這個方法內部是如何運作的,只要 output 的結果如預期就行了。

所以對外部環境來說,實作細節被封裝起來了。如果以現實生活的例子來說,當我們投錢進入販賣機的時候,只期待我們可以拿到想喝的飲料,而不需要去管販賣機究竟是如何處理硬幣、冷藏飲料、選取飲料等等

const jeter = new BaseballPlayer('derek', 'jeter')
jeter.getName()    // DEREK JETER

為什麼要封裝

在物件導向程式設計的世界當中,有許許多多的物件,擁有各種不同的屬性,用各種不同的方式與這個世界互動。

然而在沒有任何限制的情況下,如同最一開始 BaseballPlayer 的例子,jeter 很有可能突然就被改成 peter,一瞬間就認不出在你旁邊一起打球的人是誰了,甚至連打球的方式都會不一樣了。

而真實世界當中,各種東西的屬性和方法本來就無法被任意存取或竄改,每個物件都是有限的,譬如我正在使用的電腦不會突然從 13 吋變成 15 吋,在外面路上跑的公車不會突然就飛上天。

另一方面,封裝隱藏了實作細節,讓他人可以更快更方便的操作物件。譬如剛剛提到的販賣機,如果每一個想買飲料的人都先必須知道販賣機的運作原理的話,那麼這世界其實就會變得很沒有效率和麻煩。

封裝限制了互動權限,隱藏了實作細節,目的在於提高程式的安全性、穩定性、易用性,並降低錯誤的發生。


上一篇
關於抽象
下一篇
關於繼承
系列文
幫自己搞懂物件導向和設計模式30

1 則留言

1
Ken Chen
iT邦新手 5 級 ‧ 2021-09-22 14:04:08

jeter 變成 peter,就天下大亂了!

Peter 可能是我家隔壁的鍵盤全壘打王 XDD

TD iT邦新手 5 級 ‧ 2021-09-23 08:46:46 檢舉

XD

我要留言

立即登入留言