---
# System prepended metadata

title: 封裝(Encapsulation)
tags: [OOP]

---

---
title: 封裝(Encapsulation)
tags: OOP
---

物件導向特性 - 封裝(Encapsulation)
===

示意圖
![](https://i.imgur.com/NkkuG37.jpg)
[圖片來源](https://www.block.tw/blog/arduino-nano-pin/)

- 使用模組不需了解其內部詳細運作，只需了解如何使用它。
- 而模組只需提供外部所需的介面，並保證運行正常。
- 對模組而言也不希望外部知道它內部的詳細資訊。


封裝(Encapsulation)
===

將物件內部的資料隱藏起來，只能透過物件本身所提供的<font color="#F00">**介面(interface)**</font>取得物件內部屬性或者方法，物件內部的細節資料或者邏輯則隱藏起來，其他物件即無法瞭解此物件的內部細節，若不經過允許之窗口(即此物件提供之方法)便無從更動此物件內之資料。


訪問權限
===

|          |       public       |      private       |     protected      |
|:--------:|:------------------:|:------------------:|:------------------:|
| 類別外部 | :heavy_check_mark: |                    |                    |
| 類別內部 | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
| 子類別 | :heavy_check_mark: |                    | :heavy_check_mark: |

- 所以對本身類別而言，public就是開放給外面的介面。
- 一般而言，變數基本上設為非public，若要讀寫此變數，須使用public的method操作。


程式範例
===

- 假如今天有一個明星，要統計他的得讚數，在同一時間中一個粉絲只能幫喜愛的明星得讚數+1。

若將得讚數設為public權限，若一個粉絲給明星的票數多的時候可能會出現與事實不合得問題。
```cpp=
#include <thread>

using namespace std;

class SuperStar
{
public:
    SuperStar(){}
    unsigned int likedCount = 0;
private:
};

void like(SuperStar* role, unsigned int likeCount)
{
    for(unsigned int i = 0; i < likeCount; i++)
    {
        role->likedCount++;
    }
}

int main(int argc, char *argv[])
{
    unsigned int likeCount = 1e5, fansNum = 10;

    SuperStar HatanoYui;
    thread* fans[fansNum];

    for(unsigned int i = 0; i < fansNum; i++)
    {
        fans[i] = new thread(like, &HatanoYui, likeCount);
    }

    for(unsigned int i = 0; i < fansNum; i++)
    {
        fans[i]->join();
        delete(fans[i]);
        fans[i] = nullptr;
    }

    cout<<HatanoYui.likedCount<<endl;

    return 0;
}
```

所以須將其改設為非public，並再寫public的method提供介面，因為此問題為[race condition](https://zh.wikipedia.org/zh-tw/%E7%AB%B6%E7%88%AD%E5%8D%B1%E5%AE%B3)，所以可在public的method用mutex解決，讓使用此class的code能更放心的使用。
且更能限制在一個時間內得讚數只能+1。

```cpp=
#include <thread>
#include <mutex>

using namespace std;

class SuperStar
{
public:
    SuperStar(){}
    unsigned int getLikedCount()const;
    void like();
private:
    unsigned int likedCount = 0;
    mutex mutex;
};

unsigned int SuperStar::getLikedCount()const
{
    return this->likedCount;
}

void SuperStar::like()
{
    mutex.lock();
    this->likedCount++;
    mutex.unlock();
}

void like(SuperStar* role, unsigned int likeCount)
{
    for(unsigned int i = 0; i < likeCount; i++)
    {
        role->like();
    }
}

int main(int argc, char *argv[])
{
    unsigned int likeCount = 1e5, fansNum = 10;

    SuperStar HatanoYui;
    thread* fans[fansNum];

    for(unsigned int i = 0; i < fansNum; i++)
    {
        fans[i] = new thread(like, &HatanoYui, likeCount);
    }

    for(unsigned int i = 0; i < fansNum; i++)
    {
        fans[i]->join();
        delete(fans[i]);
        fans[i] = nullptr;
    }

    cout<<HatanoYui.getLikedCount()<<endl;

    return 0;
}
```
若更改需求，可在同一個時間按讚數為任意整數，可將class更改如下，而使用此class的code也會絲毫不受影響。
```cpp=
class SuperStar
{
public:
    SuperStar(){}
    unsigned int getLikedCount()const;
    void like(int n = 1);
private:
    unsigned int likedCount = 0;
    mutex mutex;
};

unsigned int SuperStar::getLikedCount()const
{
    return this->likedCount;
}

void SuperStar::like(int n)
{
    mutex.lock();
    this->likedCount += n;
    mutex.unlock();
}
```
補充:

程式範例使用的mutex來解決race condition的問題，但在此可使用[原子操作(atomic operation)](https://zh.m.wikibooks.org/zh-tw/C%2B%2B/Atomic)會更好，可提高程式效率。


結論
===
封裝限制使用界面最主要是為了讓使用者更安全的使用。
