프로필사진

IT Anthology/encyclopedia

[객체 지향 프로그래밍] 캡슐화

다각 2022. 3. 14. 22:49

* 이 글은 코드잇의 <객체 지향 프로그래밍> 코스를 수강하고 정리한 글입니다.
* 나중에라도 제가 참고하기 위해 정리해 두었으며, 모든 내용을 적은 것이 아닌,
필요하다고 생각되는 부분만 추려서 정리한 것임을 미리 밝힙니다.


캡슐화란?

  1. 객체의 일부 구현 내용에 대한 외부로부터의 직접적인 액세스를 차단하는 것
  2. 객체의 속성과 그것을 사용하는 행동하나로 묶는 것

1. 외부로부터의 직접적인 액세스를 차단

변수나 메소드를 초기 정의 시 __를 앞에 붙이면 클래스 밖에서 호출할 수 없음
예시는 아래와 같다.

Class Citizen:
  """주민 클래스"""
  def __init__(self, name, age, resident_id):
      """이름, 나이, 주민등록번호"""
      self.name = name
      self.__age = age
      self.__resident_id = resident_id
  def authenticate(self, id_field):
      """본인이 맞는지 확인하는 메소드"""
      return self.__resident_id == id_field
  def __str__(self):
      """주민 정보를 문자열로 리턴하는 메소드"""
      return self.name + "씨는 " + str(self.__age) + "살입니다!"

일때, __age__resident_id는 밖에서 접근하지 못한다.
한편, __str__ 메소드는 잘 실행되는데, 이는 메소드 이름 앞 뒤에 밑줄 2개가 있는 특수 메소드이기 때문이다.
(실제로 __resident_id__resident_id__로 바꾸면 또 일반 변수처럼 사용할 수 있다.)
따라서 은닉하고 싶은 정보는 변수나 메소드 이름 앞에만 밑줄 2개(__)를 붙여야 한다!

2. 객체의 속성과 그것을 사용하는 행동을 하나로 묶는 것

(1) 정의

: 외부에서 직접적인 접근이 불가능한 변수에 대해 접근할 수 있는 메소드를 만드는 것
: 즉, 객체의 메소드를 통해 변수 접근

-> __age 변수는 오직 그를 둘러싼 세가지 메소드를 통해서만 발현이 가능하다!
여기서 잠깐!!
스아실... __age 변수에 접근할 방법이 있긴 하다.
_Citizen__agedㅘ 같은 방식으로 접근하면 되는 것...!
사실 변수나 메소드 이름 앞에 밑줄 2개(__)를 붙이면 파이썬 내에서 네임 맹글링(name mangling)이 일어나는 것!
이게 어찌된 일일까?
사실 파이썬은 언어 차원에서 캡슐화를 지원하지 않기 때문이다. 다른 객체 지향 언어인 Java에서는 private 키워드를 통해 외부로부터의 접근을 차단할 수도 있는데 말이다.
그래서 파이썬 개발자들은 _변수_메소드 형식으로 (아무런 기능이 없음에도 불구하고) 클래스 외부에서 직접적으로 접근하지 말라는 경고를 보낸다.

캡슐화의 단계

  1. 클래스 밖에서 접근 못하게 할 변수, 메소드 정하기
  2. 변수나 메소드 이름 앞에 언더바 2개 붙이기
  3. 변수에 간접 접근할 수 있게 메소드 추가하기

캡슐화의 실행

(1) 캡슐화에 자주 사용되는 메소드

: 변수의 값을 읽어내는 getter 메소드와, 변수의 값을 설정하는 setter 메소드가 자주 사용된다.
: 하지만 이는 필수는 아니며, 위의 예시에에서 authenticate 함수의 경우에는 민감정보이기 때문에 getter와 setter 없이 사용된다.
: 파라미터 자체의 제약 사항을 setter 메소드에서 잘못된 값이 설정되는 것을 막을 수 있다. 예시는 아래와 같다.

Class Citizen:
"""주민 클래스"""
  def __init__(self, name, age, resident_id):
      """이름, 나이, 주민등록번호"""
      self.name = name
      self.set_age(age)
      self.__resident_id = resident_id
  def set_age(self, value):
      """숨겨놓은 인스턴스 변수 __age의 값을 설정하는 메소드"""
      if value < 0:
          print("나이는 0보다 작을 수 없습니다. 기본값 0으로 나이를 설정합니다.")
          self.__age = 0
      else:
          self.__age = value

(2) 조금은 간편할지도 모르는 캡슐화

: 코딩 다 한 다음에서야 특정 변수를 숨기고 싶어지면 어떻게 해야 할까?
: 모두 수정하는 건 귀찮으니 데코레이터 함수를 사용한다.

Class Citizen:
  def __init__(self, name, age, resident_id):
      """이름, 나이, 주민등록번호"""
      self.name = name
      self._age = age
      self._resident_id = resident_id
  @property
  def age(self):
      return self._age
  @age.setter
  def age(self, value):
      if value < 0:
          print("나이는 0보다 작을 수 없습니다. 기본값 0으로 나이를 설정합니다.")
          self._age = 0
      else:
          self._age = value

그럼 외부에서 클래스를 사용할 때 일일이 함수명을 바꿔주지 않아도 된다.
<before> 기존에 Citizen 클래스를 이용한 부분은 다 수정해야 한다.

young = Citizen("young", 15, "987654")
print(young.get_age()) # 출력: 15
young.set_age(30)
print(young.get_age()) # 출력: 30

<after> 기존에 Citizen 클래스를 이용한 부분에 대한 수정 없이 이용할 수 있다.

young = Citizen("young", 15, "987654")
print(young.age) # 출력: 15
young.age = 30
print(young.age) # 출력: 30

(3) 후일을 위한 기약

: 객체를 사용할 땐 최대한 메소드를 통해야 유지보수가 쉬움