[C++] 은닉성(Data Hiding)

👻 은닉성

은닉성(Data Hiding)이란 몰라도 되는 것은 깔끔하게 숨기겠다는 의미이다. 캡슐화(Encapsulation)라고도 한다. (엄연히 말하자면 다른 의미를 가지지만 대부분은 비슷한 의미로 사용한다.)

왜 숨길까?

  • 정말 위험하고 건드리면 안 되는 경우
  • 다른 경로로 접근하길 원하는 경우

접근 지정자를 사용하여 해당 클래스 내의 속성에 대한 접근 범위를 지정할 수 있고 범위에 따라 캡슐화가 가능해진다.


🌱 건드리면 안 되는 경우

자동차를 설계해보자.

자동차는 여러가지 부품이 조립되어 만들어져있다. 일반 구매자 입장에서 사용하는 것은 핸들, 페달, 문 등이 있고 몰라도 되는 것들은 엔진, 각종 전기선 등을 이야기할 수 있다. 몰라도 되는 것들은 오히려 건드리면 큰일나는 것들이기 때문에 숨기는 것이 더욱 안전할 것이다.

class Car
{
public:
    void MoveHandle() {}
    void PushPedal() {}
    void OpenDoor() {}

    // 건드리면 안 되는 것들
    void DisassembleCar() {}    // 차를 분해한다.
    void RunEngine() {} // 엔진을 구동시킨다.
    void ConnectCircuit() {}    // 전기선을 연걸시킨다.

public:
    // 핸들
    // 페달
    // 엔진
    // 문
    // 각종 전기선
};

이렇게 그대로 만들게 되면 건드리면 안 되는 것들에 접근이 쉽게 가능해진다. 이러한 기능들을 접근 불가능하도록 막기 위해(특정한 경로로만 접근할 수 있도록 하기 위해) 접근 지정자를 설정해줘야 한다.


🪐 접근 지정자

우리가 클래스 내에서 계속 사용해왔던 public이 바로 (멤버) 접근 지정자이다.

  • public : 전체 공개
  • protected : 자식 클래스에서만 사용 가능
  • private : 클래스 내부에서만 사용 가능

public에서 private으로 갈수록 접근 범위가 좁아지고, 이러한 접근 지정자를 사용하여 함수의 접근 범위를 지정할 수 있다.

class Car
{
public:
    void MoveHandle() {}
    void PushPedal() {}
    void OpenDoor() {}

private:
    // 건드리면 안 되는 것들
    void DisassembleCar() {}    // 차를 분해한다.
    void RunEngine() {} // 엔진을 구동시킨다.
    void ConnectCircuit() {}    // 전기선을 연걸시킨다.

public:
    // 핸들
    // 페달
    // 엔진
    // 문
    // 각종 전기선
};

private으로 지정해 준 함수들은 외부에서 호출할 수 없다.

중간 단계인 protected는 외부에선 사용할 수 없지만 상속을 받았을 경우 자식 클래스 내에서 사용이 가능한 정도의 접근 범위를 가진다.

class Car
{
public:
    void MoveHandle() {}
    void PushPedal() {}
    void OpenDoor() {}

protected:
    // 건드리면 안 되는 것들
    void DisassembleCar() {}    // 차를 분해한다.
    void RunEngine() {} // 엔진을 구동시킨다.
    void ConnectCircuit() {}    // 전기선을 연걸시킨다.

public:
    // 핸들
    // 페달
    // 엔진
    // 문
    // 각종 전기선
};

class SuperCar : public Car     // 상속 접근 지정자
{
public:
    void PushRemoteController()
    {
        // private은 사용불가. protected는 사용 가능
        RunEngine();
    }
}

protected로 접근 지정자를 설정하면 해당 클래스를 상속받은 자식 클래스 내부에서까지 사용가능하고 외부에선 여전히 호출 불가능하다.


🌱 다른 경로로 접근하길 원하는 경우

버서커의 특징을 설계해보자.

RPG 게임 중, 버서커라는 직업 하나가 있다. 해당 직업은 체력이 특정 수 이하로 떨어지게 되면 능력치가 더 높아진다거나 모드를 발동시키는 조건을 가지고 있다. 해당 기능을 구현해보며 은닉성의 필요성에 대해 조금 더 알아보자.

우선 기본 체력은 100으로 세팅되어 있고 체력이 50 이하로 떨어지면 버서커 모드가 발동된다고 가정해보자.

class Berserker
{
public:
    // 사양 : 체력이 50 이하로 떨어지면 버서커 모드 발동 (강해짐)

    void SetBerserkerMode()
    {
        cout << "매우 강해짐!" << endl;
    }

public:
    int _hp = 100;
};

int main()
{
    Berserker b;

    b._hp = 20;
    if (b._hp < 50)
        b.SetBerserkerMode();
}

버서커 모드 발동 조건의 로직은 이렇다. _hp가 변경되는 순간마다 체크를 해서 버서커 모드의 발동여부를 정해줘야하는데, 혹시나 나중에 체크하는 부분이 사라지거나 다르게 수정되면 로직이 제대로 동작되지 않게된다.

결국, 문제는 _hp에 직접 접근을 하게 되면서 발생하게 되는데, 이러한 문제를 해결하기 위해 캡슐화를 진행한다.


🪐 캡슐화

캡슐화(Encapsulation)란, 연관된 데이터와 함수를 논리적으로 묶어놓은 것을 의미한다. 위의 예시에서 캡슐화를 진행하게되면 _hp를 건드리는 순간, _hp의 값을 체크하는 부분까지 한 데 묶어진 상태로 만들 수 있게된다.

class Berserker
{
public:
    int GetHp() { return _hp; }

    void SetHp(int hp)
    {
        _hp = hp;
        if (_hp <= 50)
            SetBerserkerMode();
    }

    // 사양 : 체력이 50 이하로 떨어지면 버서커 모드 발동 (강해짐)
private:
    void SetBerserkerMode()
    {
        cout << "매우 강해짐!" << endl;
    }

private:
    int _hp = 100;
};

_hp 와 버서커 모드를 private으로 숨긴다. 그런 다음 _hp의 값이 변경될 때마다 체크해주는 부분을 추가하여 묶어주었다. 이렇게 되면 외부에선 _hp의 값을 가져오거나 설정까지만 하게되고 나머지 부분은 내부에서 자동으로 해주기 때문에 오류가 날 가능성이 적어지게된다. 조금 더 안정화된 작업이라 볼 수 있다.


🌱 상속 접근 지정자

이전에 봤던 접근 지정자는 클래스 내에서 사용한 멤버 접근 지정자이다. 상속 접근 지정자는 상속 받을 부모 클래스의 앞에 붙이게 되는 접근 지정자를 의미한다. 일반 접근 지정자와 같이 public, protected, private 세 종류가 있다.

내가 상속 받은 부모 클래스를 나의 자식 클래스에게 어떤 방식으로 상속할지에 대한 기준을 정해준다.

  • public : 공개적인 상속
    • 부모님의 유산 설계 그대로 상속된다.
    • public 👉 public
    • protected 👉 protected
  • protected : 보호받은 상속
    • 자손 클래스에게만 상속된다. 외부 접근이 불가능하다.
    • public 👉 protected
    • protected 👉 protected
  • private : 개인적인 상속
    • 자손 클래스에게도 물려주지 않는다. 상속받은 나까지만 사용할 수 있다.
    • public 👉 private
    • protected 👉 private

다음은 private 상속을 사용한 경우이다.

class SuperCar : private Car
{
public:
    void PushRemoteController()
    {
        // 나 까지만 사용하고 막아버릴 것임.
        RunEngine();
    }
};

class TestSuperCar : public SuperCar
{
public:
    void PushRemoteController()
    {
        // 사용 불가능
        RunEngine();
    }
};

이렇게 되면 SuperCar에서는 RunEngine을 사용할 수 있지만 결국 나 까지만 사용하게 되고, 나를 상속받은 나의 자식 클래스, 즉 자손 클래스에서는 public으로 나를 상속받았다해도 RunEngine을 사용할 수 없다.


👻 글을 마치며

이번 시간에는 객체 지향 프로그래밍의 특징 중 은닉성에 대해 알아보았다. 접근 사용자를 지정하는 게 중요한 것 같은데 상당히 헷갈리는 부분인 것 같다. 특히 상속 접근 지정자는 상속 받는 관계까지 포함되어 있다보니 조금 어려운 것 같다. 제대로 이해한 게 맞는지 연습을 좀 더 해보면서 확실히 익혀야 할 것 같다. 😂


소스코드 보러가기


출처
인프런 Rookies님 강의

Categories:

Updated:

Leave a comment