All Articles

Encapsulation이 무엇인가요?

저번 포스팅인 “Class 가 무엇인가요?”에 이어서 이번에는 class의 3대 요소중 하나인 encapsulation에 대하서 설명 해보도록 하겠다. Encapsulation도 역시나 이해하기가 쉽지는 않은 개념이다. 그리고 개념적으로 이해해도 적용해서 사용하기는 어려운 개념이기도 하다. 그래서 반복적인 많은 실습이 꼭 필요한 개념들이므로 글만 읽고 넘어가기 보다 조금이라도 코드로 구현해봐야 정말 내 것으로 소화가 가능하다.

Encapsulation

발음도 어려운 encapsulation은 직역하면 “캡슐화” 이다. 캡슐은 안의 내용물은 드러나지 않는다. 마찬가지로 encapsulation은 안의 정보가 드러나지 않는 것을 말한다. Hiding infromation 혹은 hiding data라고 한다. 즉 클래스가 담고 있는 데이터를 노출 시키지 않는 것을 말하는 것이다. 클래스가 담고 있는 데이터를 클래스 외부에서의 접근은 막고 클래스 내부적으로만 접근을 허용하는 걸 말한다. 여기 까지는 어렵지 않지만, 왜 이런 짓을 해야 하는지가 와닿지 않을 수 있다. 1부에서 구현 했던 Car 클래스를 예제로 왜 encapsulation이 필요한지 알아보자.

class Car:
    def __init__(self, maker, year, model, ai_auto_drive_mode, options):
        self.maker              = maker
        self.year               = year
        self.model              = model
        self.ai_auto_drive_mode = ai_auto_drive_mode

    def drive(self):
        if self.ai_auto_drive_mode:
            print("AI가 대신해서 운전하고 있습니다. 목적지에 도착할 때까지 편히 누워서 넷플릭스나 시청하시지요!")
        else:
            print("AI는 무슨! 얼른 운전대 안 잡니?")

    def print_info(self):
        print(f"{self.maker}, {self.model} {self.year}")
from car import Car

car = Car(
    maker              = "BENZ",
    year               = 2020,
    model              = "E-Class",
    ai_auto_drive_mode = True
)

Car 클래스가 담고 있는 데이터들인 maker, year, model, 그리고 ai_auto_drive_mode 중 한번 설정 되면 값이 변할 수 있는 것은 없다 (ai_auto_drive_mode는 옵션 이기 때문에 바뀔 수 도 있겠다). maker 가 “BENZ” 였다가 “HYUNDAI”로 바뀔 수 도 없고 year가 2020년 에서 1990으로 바뀔 수도 없다. 하지만 현재 Car class는 데이터들을 언제든지 바꿀 수 있는 구조로 구현되어 있다.

car.maker = "HYUNDAI"
car.year  = 2050

car.print_info() 
# => "HYUNDAI, E-Class 2050"

위의 코드에서 볼 수 있듯이 “2050년식 현대 E-Class” 같은 존재 하지 않는 자동차의 데이터를 생성하는게 가능한 구조가 되는것이다. 심지어 더 이상한 데이터도 생성이 가능하다:

car.maker = [1,2,3,4,5]
car.year  = {
  "좋아하는 연애인은?" : "아이유",
  "닮은 연애인은?"     : "최은우! 최은우가 날 닮았지 내가 더 나이가 많으니깐"
}

위 예제는 문맥상 전혀 말이 안되지만 코드 상으로는 가능한 코드이다. 문제는 클래스의 데이터가 외부로 그대로 노출이 되어 있어서 사용자가 직접 수정이 가능한것이 문제이다. 클래스를 직접 구현한 사람이 아니라면 클래스의 작동 논리나 데이터의 정확한 목적과 의미에 대해서 오해하거나 잘못 알 수 있으므로 잘못된 데이터를 설정할 수 있는것이다! 그러므로 이러한 부적절한 사용을 방지하기 위해 클래스의 데이터들 중 직접 노출을 제한해야 하는 데이터들은 encapsulation을 통해서 접근을 아예 막거나 아니면 제한적 접근만 허용 하는 것이다. 자 이제 encapsulation을 사용하여 Car 클래스를 다시 구현해보자!

class Car:
    def __init__(self, maker, year, model, ai_auto_drive_mode):
        self._maker              = maker
        self._year               = year
        self._model              = model
        self._ai_auto_drive_mode = ai_auto_drive_mode

    def drive(self):
        if self.ai_auto_drive_mode:
            print("AI가 대신해서 운전하고 있습니다. 목적지에 도착할 때까지 편히 누워서 넷플릭스나 시청하시지요!")
        else:
            print("AI는 무슨! 얼른 운전대 안 잡니?")

    def print_info(self):
        print(f"{self.maker}, {self.model} {self.year}")

    def get_ai_auto_drive_mode(self):
        return self._ai_auto_drive_mode

    def set_ai_auto_drive_mode(self, value):
        # 2020년 전에는 AI 자동 주행이 없었으므로 구식 모델은 탑재 불가능
        if self._year >= 2020:
            self._ai_auto_drive_mode = value
        else:
            print("2020년 전에는 AI 자동 주행이 없었으므로 무조건 False값만 가능합니다")
            self._ai_auto_drive_mode = False

    def get_maker(self):
        return self._maker

    def get_year(self):
        return self._year

    def get_model(self):
        return self._model

    maker              = property(get_maker, None)
    year               = property(get_year, None)
    model              = property(get_model, None)
    ai_auto_drive_mode = property(get_ai_auto_drive_mode, set_ai_auto_drive_mode)

위 코드는 encapsulation을 적용한 Car 클래스 코드이다.
코드를 간략하게 설명하자면, 먼저 __init__ 메소드를 보면 이전의 코드와 다르게 각 데이터의 값을 저장하는 변수들의 이름에 _ (밑줄)이 붙어 있는것을 볼 수 있을것이다(예를 들어 _maker). 파이썬에서 변수나 함수 이름 앞에 _ 밑줄을 붙이면 해당 변수/함수는 private(한마디로 외부에서 직접 접근하면 안된다라는 뜻)이라고 말해주는 것이다.

그리고 파이썬의 property를 사용하여 각 데이터를 저장하고 있는 변수에 직접적인 접근은 막고 getter 함수를 통해서 값을 읽어들이게 하고 setter 함수를 통해서 값을 지정하게 할 수 있도록 하는것이다. setter 함수를 아예 생략해서 값 수정이 불가능하게 할 수도 있다. 예를 들어, maker는 getter 함수만 정의하고 setter 함수는 의도적으로 누락시키므로서 maker 변경이 불가능하게 구현했다. Property에 대해 더 자세한 정보는 공식문서를 참조하도록 하자.

또한 ai_auto_drive_mode 같은 경우 값이 충분히 바뀔 수가 있지만 (예를 들어, 처음에는 AI 자동 주행 옵션을 탑재 하지 않았다가 나중에 추가한 경우) 2020년 최신식 모델만 가능하고 예전 모델들은 탑재가 불가능 하다는 로직도 “setter” 메소드에 구현할 수 있다. 이런 것들은 전부 데이터에 대한 직접적인 접근을 제한하고 “getter”나 “setter” 메소드를 통해서만 접근을 허용하므로서 구현 가능한 기능들이다. 그리고 이게 바로 encapsulation 인것이다!

In [8]: car = Car(
   ...:     maker              = "BENZ",
   ...:     year               = 2019,
   ...:     model              = "E-Class",
   ...:     ai_auto_drive_mode = False
   ...: )

In [9]:

In [9]: car.maker
Out[9]: 'BENZ'

In [10]: car.year
Out[10]: 2019

In [11]: car.model
Out[11]: 'E-Class'

In [12]: car.maker = "Hyundai"
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-12-fd95d6e0e115> in <module>
----> 1 car.maker = "Hyundai"

AttributeError: can't set attribute

In [13]: car.year = 2020
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-13-1ef931dfcf6f> in <module>
----> 1 car.year = 2020

AttributeError: can't set attribute

In [14]: car.model = "Sonata"
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-14-0ebed2cc087f> in <module>
----> 1 car.model = "Sonata"

AttributeError: can't set attribute

In [15]: car.ai_auto_drive_mode
Out[15]: False

In [16]: car.ai_auto_drive_mode = True
2020년 전에는 AI 자동 주행이 없었으므로 무조건 False값만 가능합니다

In [17]: car.ai_auto_drive_mode
Out[17]: False

In [18]:

Encapsulation, 이제 어렵지 않죠? 😉

TMI: Encapsulation과 abstraction에 대한 차이가 궁금할수도 있을것 같다. 둘다 뭔가를 숨긴다는 거에서는 비슷하다. 차이점은 encapsulation은 데이터를 숨기는 것이고, abstraction은 implementation(실제 코드)를 숨기는 것이다. Abstraction의 가장 좋은 예가 함수와 클래스이다. 함수는 실제 구현된 코드를 몰라도 input과 output만 이해해서 사용할 수 있다.

클래스의 3대 요소중 하나인 encapsulation에 대해서 알아봤으니 이제 다음으로는 inheritance에 대해서 알아보도록 하겠다! 커밍순 😃