TIL_27 : 추상화, 캡슐화

42792 단어 TILOOPOOP

🙄 객체 지향 프로그래밍의 4가지 기둥


  • 추상화

  • 캡슐화

  • 상속

  • 다형성



🙄 추상화


➡ 추상화란?

  • 프로그래머들이 특정 코드를 사용할 때 필수적인 정보를 제외한 세부사항을 가리는 것
  • 변수 정의, 함수, 클래스를 사용하는 것도 추상화

👉 변수에 한번 값을 설정하면 그 이후 값을 몰라도 이름만으로 사용 가능
👉 함수의 이름, 파라미터, 기능만 알면 구체적인 동작 방식은 몰라도 잘 사용 가능


➡ 추상화 잘하기

  • 추상화를 잘 적용하면 본인이 만든 클래스를 동료가 잘 사용할 수 있고 반대로 동료가 만든 클래스도 내가 잘 사용 가능
  • 본인이 만든 것을 시간이 지나 다시 사용할 때도 잘 사용 가능

이름 잘 짓기

  • 클래스, 메소드, 변수 이름을 그 의미가 잘 담기게 지어야 함
  • 어디서 어떻게 사용할지 직관적으로 알 수 있다

문서화하기

  • 항상 이름에 모든 의미를 담기에는 한계가 존재
  • 문서화 = docstring : documentation string, 문서화 문자열
  • """ docstring """ 를 이용해 작성
  • 설명하고 싶은 클래스나 메소드 이름 바로 아래 써준다
class BankAccount:
    """은행 계좌 클래스"""
    interest = 0.02

    def __init__(self, owner_name, balance):
        """인스턴스 변수 : name(문자열), balance(실수형)"""
        self.owner_name = owner_name
        self.balance = balance

    def deposit(self, amount):
        """잔액 인스턴스 변수 balance를 파라미터 amount만큼 늘려주는 메소드"""
        self.balance += amount

    def withdraw(self, amount):
        """잔액 인스턴스 변수 balance를 파라미터 amount만큼 줄여주는 메소드"""
        if self.balance < amount:
            print("Insufficient balance!")
        else:
            self.balance -= amount

    def add_interest(self):
        """잔액 인스턴스 변수 balance를 이자율만큼 늘려주는 메소드"""
        self.balance *= 1 + BankAccount.interest

type hinting

  • 정적 언어처럼 type을 표시할 수 있는 기능 변수이름: type
  • return 값의 type : 함수 정의 부분 뒤에 -> type 작성
class BankAccount:
    """은행 계좌 클래스"""
    interest: float = 0.02

    def __init__(self, owner_name: str, balance: float) -> None:
        """인스턴스 변수 : name(문자열), balance(실수형)"""
        self.owner_name = owner_name
        self.balance = balance

    def deposit(self, amount: float) -> None:
        """잔액 인스턴스 변수 balance를 파라미터 amount만큼 늘려주는 메소드"""
        self.balance += amount

    def withdraw(self, amount: float) -> None:
        """잔액 인스턴스 변수 balance를 파라미터 amount만큼 줄여주는 메소드"""
        if self.balance < amount:
            print("Insufficient balance!")
        else:
            self.balance -= amount

    def add_interest(self) -> None:		# return 값이 없으므로 None
        """잔액 인스턴스 변수 balance를 이자율만큼 늘려주는 메소드"""
        self.balance *= 1 + BankAccount.interest
        
        
print(BankAccount.__doc__)
print()
help(BankAccount)

# 은행 계좌 클래스

# Help on class BankAccount in module __main__:
# 
# class BankAccount(builtins.object)
# |  BankAccount(owner_name: str, balance: float) -> None
# |  
# |  은행 계좌 클래스
# |  
# |  Methods defined here:
# |  
# |  __init__(self, owner_name: str, balance: float) -> None
# |      인스턴스 변수 : name(문자열), balance(실수형)
# |  
# |  add_interest(self) -> None
# |      잔액 인스턴스 변수 balance를 이자율만큼 늘려주는 메소드
# |  
# |  deposit(self, amount: float) -> None
# |      잔액 인스턴스 변수 balance를 파라미터 amount만큼 늘려주는 메소드
# |  
# |  withdraw(self, amount: float) -> None
# |      잔액 인스턴스 변수 balance를 파라미터 amount만큼 줄여주는 메소드
# |  
# |  ----------------------------------------------------------------------
# |  Data descriptors defined here:
# |  
# |  __dict__
# |      dictionary for instance variables (if defined)
# |  
# |  __weakref__
# |      list of weak references to the object (if defined)
# |  
# |  ----------------------------------------------------------------------
# |  Data and other attributes defined here:
# |  
# |  __annotations__ = {'interest': <class 'float'>}
# |  
# |  interest = 0.02


# Process finished with exit code 0



🙄 캡슐화


➡ 캡슐화란?

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

➡ 캡슐화의 필요성

  • 아래 예시 주석으로 설명
class Citizen:
    """주민 클래스"""
    drinking_age = 19        		   # 음주 가능 나이

    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 can_drink(self):
        """음주 가능 나이인지 확인하는 메소드"""
        return self.age >= Citizen.drinking_age

    def __str__(self):
        """주민 정보를 문자열로 리턴하는 메소드"""
        return self.name + "씨는 " + str(self.age) + "살입니다!"

jahyeon = Citizen("구자현", 24, "12345678")
young = Citizen("민정호", 5, "98765432")

print(jahyeon.resident_id)                # 12345678, 주민등록번호 유출 위험
young.age = -5        			  # 나이를 마음대로 변경 가능
print(young)           			  # 민정호씨는 -5살입니다!

young.age = 20
print(young.can_drink())                  # True, 음주 가능 나이로 조작 가능

➡ 객체 내부를 숨기는 법

  • 변수나 메소드 앞에 __ 를 붙이면 클래스 밖에서 접근이 불가능
class Citizen:
    """주민 클래스"""
    drinking_age = 19           # 음주 가능 나이

    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 can_drink(self):
        """음주 가능 나이인지 확인하는 메소드"""
        return self.__age >= Citizen.drinking_age

    def __str__(self):
        """주민 정보를 문자열로 리턴하는 메소드"""
        return self.name + "씨는 " + str(self.__age) + "살입니다!"

jahyeon = Citizen("구자현", 24, "12345678")
print(jahyeon.__resident_id)
# AttributeError: 'Citizen' object has no attribute '__resident_id'

print(jahyeon.__authenticate("12345678"))
# AttributeError: 'Citizen' object has no attribute '__authenticate'

➡ 객체의 메소드를 통해 변수 접근하기 #1

  • __age, __resident_id는 클래스 밖에서 직접 접근 불가
  • 주민의 나이를 알 수 없고 수정할 수도 없다
  • 무조건 숨기기만 해서는 안됨.
    👉 접근할 수 있는 메소드를 따로 만듦
class Citizen:
    """주민 클래스"""
    drinking_age = 19           # 음주 가능 나이

    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 can_drink(self):
        """음주 가능 나이인지 확인하는 메소드"""
        return self.__age >= Citizen.drinking_age

    def __str__(self):
        """주민 정보를 문자열로 리턴하는 메소드"""
        return self.name + "씨는 " + str(self.__age) + "살입니다!"

    def get_age(self):			# __age에 접근 할 수 있는 메소드 
        """숨겨 놓은 인스턴스 변수 __age의 값을 받아오는 메소드"""
        return self.__age

    def set_age(self, value):		# __age에 접근 할 수 있는 메소드
        """숨겨 놓은 인스턴스 변수 __age의 값을 설정하는 메소드"""
        self.__age = value

jahyeon = Citizen("구자현", 24, "12345678")

print(jahyeon.get_age())
jahyeon.set_age(25)
print(jahyeon.get_age())

# 24
# 25

➡ 객체의 메소드를 통해 변수 접근하기 #2

  • getter 메소드 : 변수의 값을 읽는 메소드
  • setter 메소드 : 변수의 값을 설정하는 메소드

getter / setter 메소드를 꼭 만들 필요는 없다
__resident_id 같은 민감한 정보는 값을 읽거나 설정 하면 안됨

👉 캡슐화 정리

  1. 클래스 밖에서 접근 못하게 할 변수, 메소드 정하기
  2. 변수나 메소드 이름 앞에 언더바 2개 붙이기
  3. 변수에 간접 접근할 수 있게 메소드 추가하기 ➡ getter / setter or 다른 용도의 메소드

➡ 객체의 메소드를 통해 변수 접근하기 #3

class Citizen:
    """주민 클래스"""
    drinking_age = 19           # 음주 가능 나이

    def __init__(self, name, age, resident_id):
        """이름, 나이, 주민등록번호"""
        self.name = name
        self.set_age(age)		# self.__age = age를 setter 메소드로 변경
        self.__resident_id = resident_id

    def get_age(self):			# __age에 접근 할 수 있는 메소드
        """숨겨 놓은 인스턴스 변수 __age의 값을 받아오는 메소드"""
        return self.__age

    def set_age(self, value):		# __age에 접근 할 수 있는 메소드
        """숨겨 놓은 인스턴스 변수 __age의 값을 설정하는 메소드"""
        if value < 0:
            print("나이는 0보다 작을 수 없습니다. 기본 값 0으로 나이를 설정하겠습니다.")
            self.__age = 0
        else:
            self.__age = value

jahyeon = Citizen("구자현", 24, "12345678")

print(jahyeon.get_age())

jahyeon.set_age(-10)
print(jahyeon.get_age())

jahyeon.set_age(22)
print(jahyeon.get_age())

jahyeon = Citizen("구자현", -10, "12345678")
print(jahyeon.get_age())

# 24

# 나이는 0보다 작을 수 없습니다. 기본 값 0으로 나이를 설정하겠습니다.
# 0

# 22

# 나이는 0보다 작을 수 없습니다. 기본 값 0으로 나이를 설정하겠습니다.
# 0

➡ decorator를 사용한 캡슐화

  • property : 변수의 값을 읽거나 설정하는 구문이 아예 다른 의미로 실행
  • 캡슐화 전 사용하던 코드를 캡슐화 후 수정할 필요 없음
class Citizen:
    """주민 클래스"""
    drinking_age = 19           # 음주 가능 나이

    def __init__(self, name, age, resident_id):
        """이름, 나이, 주민등록번호"""
        self.name = name
        self.age = age
        self.__resident_id = resident_id

    @property
    def age(self):				# _age의 getter 메소드 역할
        print("나이를 리턴합니다.")
        return self._age

    @age.setter
    def age(self, value):			# _age의 setter 메소드 역할
        print("나이를 설정합니다.")
        if value < 0:
            print("나이는 0보다 작을 수 없습니다. 기본 값 0으로 나이를 설정하겠습니다.")
            self._age = 0
        else:
            self._age = value
            
jahyeon = Citizen("구자현", 24, "12345678")

print(jahyeon.age)				# 같은 이름의 age 메소드를 호출

jahyeon.age = 20				# age.setter 호출, 대입 값을 파라미터로 넘김

print(jahyeon.age)

➡ 객체를 사용할 땐 최대한 메소드로

  • 변수 직접 사용을 최소화할수록 유지 보수가 쉬운 코드
  • 출생기준 나이가 0살에서 1살로 바뀌면 wjddn.age + 1로 모두 변경해야 함
class Citizen:
    """주민 클래스"""
    drinking_age = 19           # 음주 가능 나이

    def __init__(self, name, age, resident_id):
        """이름, 나이, 주민등록번호"""
        self.name = name
        self.age = age
        self.__resident_id = resident_id

    def can_drink(self):
        """음주 가능 나이인지 확인하는 메소드"""
        return self.age >= Citizen.drinking_age

# 객체 변수에 직접 접근 했을 때
wjddn = Citizen("박준규", 24, "13579")

# 음주 가능 나이인지 확인
if wjddn.age >= Citizen.drinking_age:			# 여기
    print("{}님은 음주 가능 나이입니다!".format(wjddn.name))

# 음주 가능 나이인지 확인
if wjddn.age >= Citizen.drinking_age:			# 여기
    print("들어오세요~!")

# 음주 가능 나이인지 확인
if wjddn.age >= Citizen.drinking_age:			# 여기
    print("무슨 술을 드시겠습니까?")
  • 메소드로 접근했다면 메소드 한개만 self.age + 1로 바꿔주면 됨
class Citizen:
    """주민 클래스"""
    drinking_age = 19           # 음주 가능 나이

    def __init__(self, name, age, resident_id):
        """이름, 나이, 주민등록번호"""
        self.name = name
        self.age = age
        self.__resident_id = resident_id

    def can_drink(self):
        """음주 가능 나이인지 확인하는 메소드"""
        return self.age >= Citizen.drinking_age		# 여기

# 메소드로 객체에 접근 했을 때
wjddn = Citizen("박준규", 24, "13579")

# 음주 가능 나이인지 확인
if wjddn.can_drink():
    print("{}님은 음주 가능 나이입니다!".format(wjddn.name))

# 음주 가능 나이인지 확인
if wjddn.can_drink():
    print("들어오세요~!")

# 음주 가능 나이인지 확인
if wjddn.can_drink():
    print("무슨 술을 드시겠습니까?")

좋은 웹페이지 즐겨찾기