객체 지향 프로그래밍

is Yoon·2023년 8월 22일
0

객체 지향 프로그래밍이란?

  • 레슨 수 : 35강
  • 난이도 : 초급

강의 소개 : 객체 지향 프로그래밍에 대해 알려주는 곳은 많지만, 이론부터 실전까지 제대로 알려주는 곳은 많지 않습니다. 이번 토픽을 통해 직접 프로그램을 설계하고 파이썬 코드로 구현해 보면서 객체 지향 프로그래밍을 마스터하세요!




기본 개념

객채

  • 속성(변수)행동(메소드)으로 이루어짐

객체 지향 프로그래밍

  • 프로그램을 여러 개의 독립된 객체들과 그 객체들 간의 상호작용으로 파악하는 프로그래밍 접근법
  • 프로그램을 객체들과 객체들간의 소통으로 바라보는 것

객체 지향 프로그래밍으로 프로그램을 만들려면

  1. 프로그램에 어떤 객체들이 필요할지 정한다.
  2. 객체들의 속성과 행동을 정한다.
  3. 객체들이 서로 어떻게 소통할지 정한다.

클래스

  • 객체(인스턴스)의 틀
class ClassName :   
	pass
# 클래스 이름은 항상 앞 글자를 대문자로 쓴다.

instance1 = ClassName()
instance2 = ClassName()
# 같은 클래스를 호출했어도 서로 다른 인스턴스가 된다.

객체인스턴스는 굳이 따르자면 다르긴 하다!


인스턴스 변수

  • 정의 : 인스턴스명.속성이름 = "속성(인스턴스 변수)에 넣을 값"
  • 사용 : 인스턴스명.속성이름
  • 인스턴스가 각자 가지고 있는 값
  • 인스턴스 자신만의 속성
  • 미리 정의해야 사용 가능하다.

인스턴스 메소드

  • 사용 : 인스턴스명.메소드이름(인스턴스) = 인스턴스.메소드이름()
    - 인스턴스를 자동으로 (첫 번째 파라미터)로 할당함
  • 인스턴스 변수를 사용하거나, 인스턴스 변수에 값을 설정하는 메소드
  • 첫 번째 파라미터를 self 라고 써야 한다.

특수 메소드

  • 특정 상황에서 자동으로 호출되는 메소드
  • 더블 언더바 (던더)로 표현
  • __init__ - 인스턴스가 생성될 때 자동으로 호출되는 메소드
    • 인스턴스 생성과 인스턴스 변수 초깃값 설정을 한 줄에 할 수 있게 된다.
  • __str__ - print() 함수가 사용될 때 자동으로 호출되는 메소드
    - 인스턴스 생성과 인스턴스 변수 초깃값 설정을 한 줄에 할 수 있게 된다.

클래스 변수

  • 정의 : 클래스명.클래스변수명
  • 사용 : 클래스명.클래스변수명 or 인스턴스명.클래스변수명
  • 한 클래스의 모든 인스턴스가 공유하는 속성

데코레이터

  • 다른 함수를 꾸며주는 역할을 한다. (기존의 함수에 새로운 기능을 추가한다.)

클래스 메소드

  • 클래스 변수의 값을 읽거나 설정하는 메소드
  • 인스턴스 변수는 사용 불가능하다.
  • 첫 번째 파라미터로 cls를 쓰며, 클래스가 자동으로 전달된다.
  • 메소드 정의 위에 @classmethod 데코레이터를 표시

정적 메소드 (static method)

  • 인스턴스 변수, 클래스 변수를 전혀 다루지 않는 메소드
  • 자동 전달되는 파라미터가 없다.
  • 메소드 정의 위에 @staticmethod 데코레이터를 표시
  • 어떤 속성을 다루지 않고, 단지 기능(행동)적인 역할만 하는 메소드를 정의할 때 사용한다.

# 인스턴스 메소드
def __str__(self):
    return "사용자: {}, 이메일: {}, 비밀번호: ******".format(self.name, self.email)

# 클래스 메소드    
@classmethod
def number_of_users(cls):
    print("총 유저 수는: {}입니다".format(cls.count))

# 정적 메소드    
@staticmethod
def is_valid_email(email_address):
    return "@" in email_address



파이썬에 대해

  • 순수 객체 지향 언어 파이썬
  • 파이썬에 있는 모든 것이 객체이다!

가변 vs 불변 타입

가변 타입 객체 - 한 번 생성한 인스턴스의 속성 변경 가능 (ex. 리스트, 딕셔너리, 직접 작성하는 클래스)
불변 타입 객체 - 한 번 생성한 인스턴스의 속성 변경 불가능 (ex. 튜플, 불린, 문자열, 정수 등등)

절차 지향 프로그래밍


# 반복적으로 사용하는 코드를 함수로 정의한다
def print_person_info(person_name, person_age, person_gender):
    # 사람의 이름, 나이, 성별을 파라미터로 받으면 받은 정보를 이해할 수 있는 문자열로 출력해주는 함수
    print("사람 한 명을 소개합니다")
    print("{}님은 {}살이고 {}입니다".format(person_name, person_age, person_gender))
    
def is_underage(person_age):
    # 사람의 나이를 파라미터로 받아서 미성년자인지를 리턴해주는 함수
    return person_age < 20
    
# 영훈이의 정보
young_name = "영훈"
young_age = 10
young_gender = "남자"
    
# 윤수의 정보
yoonsoo_name = "윤수"
yoonsoo_age = 20
yoonsoo_gender = "남자"
    
# 영훈/윤수 정보 출력
print_person_info(young_name, young_age, young_gender)
print_person_info(yoonsoo_name, yoonsoo_age, yoonsoo_gender)
    
# 영훈/윤수가 미성년자인지 출력
print(is_underage(young_age))
print(is_underage(yoonsoo_age))
절차 지향 프로그래밍객체 지향 프로그래밍
프로그램 안에서 서로 관련된 동작들만 묶어서 관리한다.관련된 동작들을 관련된 데이터와도 함께 묶어서 관리한다.
프로그램을 만들 때 데이터와 함수를 합칠 수 없다.프로그램을 만들 때 데이터와 함수를 합칠 수 있다.
프로그램을 명령어들을 순서대로 실행하는 것으로 본다.프로그램을 객체들이 순서대로 소통하는 과정으로 본다.

유용한 함수들

max(), min()

sum()

  • 리스트, 튜플은 숫자형 요소들의 합을 리턴한다.
  • 딕셔너리는 key들의 합을 리턴한다.

ternary expression

불린 값에 따라 다른 값을 리턴하는 구문

condition = True
condition_string = "nice" if condition else "not nice"
print(condition_string)      # => nice

list comprehension

int_list = [1, 2, 3, 4, 5, 6]
squares = [x**2 for x in int_list]
print(squares)               # [1, 4, 9, 16, 25, 36]

zfill 메소드

  • “text”.zfill(k)
  • 문자열을 최소 몇 자리 이상을 가진 문자열로 변환하는 메소드
  • 모자란 부분은 왼쪽에 0을 채워준다.
  • 문자열을 예쁘고 통일감있게 출력하고자 할 때 자주 사용된다.
print("1".zfill(6))           # 000001
print("333".zfill(2))      # 333

다른 문자열로 채우는 방법으로는 rjust(), ljust()가 있다.

text1 =123.rjust(5,-)
text2 =123.ljust(5,-)
print(text1)
print(text2)
—123
123–

모듈

변수, 함수, 클래스 등을 모아놓은 파일

from 모듈명 import 불러올 변수명/함수명/클래스명
  • radint 함수 : 두 정수 사이에서 랜덤한 정수를 리턴하는 함수
  • uniform 함수 : 두 수 사이의 랜덤한 소수를 리턴하는 함수
from random import randint, uniform
x = randint(1, 20)
y = uniform(0, 1)




실습

게임 캐릭터 만들기

# 나의 풀이
class GameCharacter:
    # 게임 캐릭터 클래스
    def __init__(self, name, hp, power):
        # 게임 캐릭터는 속성으로 이름, hp, 공격력을 갖는다
        self.name = name
        self.hp = hp
        self.power = power

    def is_alive(self):
        # 게임 캐릭터가 살아있는지(체력이 0이 넘는지) 확인하는 메소드
        return True if self.hp > 0 else False

    def get_attacked(self, damage):
        """
        게임 캐릭터가 살아있으면 공격한 캐릭터의 공격력만큼 체력을 깎는 메소드
        조건:    
            1. 이미 캐릭터가 죽었으면 죽었다는 메시지를 출력한다
            2. 남은 체력보다 공격력이 더 크면 체력은 0이 된다.
        """
        self.damage = damage
        if self.hp > 0 :
            if self.hp > self.damage :
                self.hp = self.hp - self.damage
            else :
                self.hp = 0
        else :
            print(f"{self.name}님은 이미 죽었습니다.")

    def attack(self, other_character):
        # 게임 캐릭터가 살아있으면 파라미터로 받은 다른 캐릭터의 체력을 자신의 공격력만큼 깎는다
        other_character.get_attacked(self.power)

    def __str__(self):
        # 게임 캐릭터의 의미있는 정보를 포함한 문자열을 리턴한다
        return f"{self.name}님의 hp는 {self.hp}만큼 남았습니다."
# 정답 풀이

class GameCharacter:
    # 게임 캐릭터 클래스
    def __init__(self, name, hp, power):
        self.name = name
        self.hp = hp
        self.power = power

    def is_alive(self):
        return True if self.hp > 0 else False

    def get_attacked(self, damage):
        if self.is_alive() :
            self.hp = self.hp - damage if self.hp >= damage else 0
        else :
            print(f"{self.name}님은 이미 죽었습니다.")

    def attack(self, other_character):
        other_character.get_attacked(self.power)
        # 공격한 캐릭터의 공격력을 파라미터로 넘기면, 다른 캐릭터에 대미지가 그 공격력만큼 입혀진다.

    def __str__(self):
        # 게임 캐릭터의 의미있는 정보를 포함한 문자열을 리턴한다
        return f"{self.name}님의 hp는 {self.hp}만큼 남았습니다."
  1. return True if self.hp > 0 else False >> return self.hp > 0
  • True, False 값을 지정하지 않아도 자동으로 값에 따라 T/F를 반환한다.
  1. self.damage = damage를 굳이 선언할 필요가 없다. 이 메소드에서 한 번만 사용하는 메소드이기 때문에.
  2. 다른 메소드를 호출하는 방법은 self.is_alive() 이다.
  3. If문을 한 줄로 표현할 수도 있다.
# 게임 캐릭터 인스턴스 생성                        
character_1 = GameCharacter("Ww영훈전사wW", 200, 30)
character_2 = GameCharacter("Xx지웅최고xX", 100, 50)

# 게임 캐릭터 인스턴스들 서로 공격
character_1.attack(character_2)
character_2.attack(character_1)
character_2.attack(character_1)
character_2.attack(character_1)
character_2.attack(character_1)
character_2.attack(character_1)

# 게임 캐릭터 인스턴스 출력
print(character_1)
print(character_2)
# 실행 결과
Ww영훈전사wW님은 이미 죽었습니다.
Ww영훈전사wW님의 hp는 0만큼 남았습니다.
Xx지웅최고xX님의 hp는 70만큼 남았습니다.



블로그 유저 만들기

class Post:
    # 게시글 클래스
    def __init__(self, date, content):
        # 게시글은 속성으로 작성 날짜와 내용을 갖는다
        self.date = date
        self.content = content

    def __str__(self):
        # 게시글의 정보를 문자열로 리턴하는 메소드
        return "작성 날짜: {}\n내용: {}".format(self.date, self.content)
    
    
class BlogUser:
    # 블로그 유저 클래스
    def __init__(self, name):
        """
        블로그 유저는 속성으로 이름, 게시글들을 갖는다
        posts는 빈 배열로 초기화한다
        """
        self.name = name
        self.posts = []          # 블로그 유저의 게시글을 담는다.

    def add_post(self, date, content):
        # 새로운 게시글 추가 - posts에 추가
        new_post = Post(date, content)   # Post class에 파라미터 넣어 생성
        self.posts.append(new_post)       # posts list에 새로운 게시글 추가

    def show_all_posts(self):
        # 블로그 유저의 모든 게시글 출력
        for post in self.posts:
            print(post)     # print() 에 의해 아래 __str__ 내용 리턴받는다.

    def __str__(self):
        # 간단한 인사와 이름을 문자열로 리턴
        return f"안녕하세요 {self.name}입니다."
    
    

# 블로그 유저 인스턴스 생성
blog_user_1 = BlogUser("성태호")

# 블로그 유저 인스턴스 출력(인사, 이름)
print(blog_user_1)

# 블로그 유저 게시글 2개 추가
blog_user_1.add_post("2019년 8월 30일", """
오늘은 내 생일이었다.
많은 사람들이 축하해줬다.
행복했다.
""")

blog_user_1.add_post("2019년 8월 31일", """
재밌는 코딩 교육 사이트를 찾았다.
코드잇이란 곳인데 최고다.
같이 공부하실 분들은 www.codeit.kr로 오세요!
""")

# 블로그 유저의 모든 게시글 출력
blog_user_1.show_all_posts()

시계 만들기

# 나의 코드

class Clock :
    
    def __init__(self, hour, min, sec) :
        self.hour = hour
        self.min = min
        self.sec = sec
    
    def set(self, hour, min, sec) :
        self.hour = hour
        self.min = min
        self.sec = sec
    
    def tick(self) :
        self.sec += 1
        
                
        if self.sec >= 60 :
            self.sec -= 60
            self.min += 1
        else : 
            pass
        
        if self.min >= 60 :
            self.min -= 60
            self.hour += 1
        else :
            pass
        
        if self.hour >= 24 :
            self.hour -= 24
        else :
            pass
        
    def __str__(self) :
        return str(self.hour).zfill(2) + ":" + str(self.min).zfill(2) + ":" + str(self.sec).zfill(2)

# 1시 30분 48초인 시계 인스턴스 생성
clock = Clock(1, 30, 48)
    
# 13초를 늘린다
for i in range(13):
    clock.tick()
    
# 시계의 현재 시간 출력
print(clock)
# 모범 답안

class Counter:
    """
    시계 클래스의 시,분,초를 각각 나타내는데 사용될 카운터 클래스
    """

    def __init__(self, limit):
        """
        인스턴스 변수 limit(최댓값), value(현재까지 카운트한 값)을 설정한다.
        인스턴스를 생성할 때 인스턴스 변수 limit만 파라미터로 받고, value는 초깃값 0으로 설정한다.
        """    
        self.limit = limit
        self.value = 0


    def set(self, new_value):
        """
        파라미터가 0 이상, 최댓값 미만이면 value에 설정한다.
        아닐 경우 value에 0을 설정한다.
        """
        if 0 <= new_value < self.limit:
            self.value = new_value
        else:
            self.value = 0


    def tick(self):
        """
        value를 1 증가시킨다.
        카운터의 값 value가 limit에 도달하면 value를 0으로 바꾼 뒤 True를 리턴한다.
        value가 limit보다 작은 경우 False를 리턴한다.
        """
        self.value += 1

        if self.value == self.limit:
            self.value = 0
            return True
        return False


    def __str__(self):
        """
        value를 최소 두 자릿수 이상의 문자열로 리턴한다. 
        일단 str 함수로 숫자형 변수인 value를 문자열로 변환하고 zfill을 호출한다. 
        """
        return str(self.value).zfill(2)
    

class Clock:
    """
    시계 클래스
    """
    HOURS = 24 # 시 최댓값
    MINUTES = 60 # 분 최댓값
    SECONDS = 60 # 초 최댓값

    def __init__(self, hour, minute, second):
        """
        각각 시, 분, 초를 나타내는 카운터 인스턴스 3개(hour, minute, second)를 정의한다.
        현재 시간을 파라미터 hour시, minute분, second초로 지정한다.
        """
        self.hour = Counter(Clock.HOURS)
        self.minute = Counter(Clock.MINUTES)
        self.second = Counter(Clock.SECONDS)
        
        self.set(hour, minute, second)


    def set(self, hour, minute, second):
        """현재 시간을 파라미터 hour시, minute분, second초로 설정한다."""
        self.hour.set(hour)
        self.minute.set(minute)
        self.second.set(second)


    def tick(self):
        """
        초 카운터의 값을 1만큼 증가시킨다.
        초 카운터를 증가시킬 때, 분 또는 시가 바뀌어야하는 경우도 처리한다.
        """
        if self.second.tick() :
            if self.minute.tick() :
                self.hour.tick()
        

    def __str__(self):
        """
        현재 시간을 시:분:초 형식으로 리턴한다. 시, 분, 초는 두 자리 형식이다.
        예시: "03:11:02"
        """
        return f”{self.hour}:{self.minute}:{self.second}“
        

clock = Clock(1, 30, 48)
clock.set(2, 59, 58) 
for i in range(5) :
    clock.tick()
print(clock)
  1. if문에 반드시 else가 있을 필요는 없다.
  2. 다른 클래스를 호출하려면 그냥 클래스명()을 입력하면 된다.
    이 때, 파라미터를 ()안에 쓰면 된다.
  3. self.시분초는 최대 시분초를 지정하고, 현재 시간은 set()을 이용한다.
  • self.set(hour, minute, second)
  • self.시분초.set(시분초)
  1. self.시분초.tick()은 다른 클래스 Countertick()을 불러온다.






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

  • 레슨 수 : 54강
  • 난이도 : 중급

강의 소개 : 추상화, 캡슐화, 상속, 다형성… 네 가지 핵심 요소만 제대로 이해하면, 누구나 객체 지향 프로그래밍 코드를 작성할 수 있습니다. 파이썬으로 직접 실습을 하면서 하나씩 공부하다 보면, 객체 지향 프로그래밍을 금방 마스터할 수 있을 거예요.




1. 추상화(Abstraction)

프로그래머들이 특정 코드를 사용할 때 필수적인 정보를 제외한 세부사항을 가리는 것 (꼭 필요한 부분만 보이도록 드러낸 것)
ex) 변수나 함수, 클래스를 사용하는 것

추상화 잘하는 방법

  • 변수, 클래스, 메소드 이름 잘 짓기
  • 문서화(docstring, documentation string) 하기 : 주석처리로 정보를 입력하기
  • help(ClassName) 이용하면 정보가 출력된다.
  • Type hinting 이용 가능
    변수명 뒤에 :type 을 작성하면 된다.
    메소드의 리턴값은 뒤에 -> type 을 작성하면 된다.

추천 영상 찾는 메소드의 문서화 - 널리 쓰이는 3가지 포맷

def find_suggestion_videos(self, number_of_suggestions=5):
# Google docstring

"""유저에게 추천할 영상을 찾아준다
Parameters:
  number_of_suggestions (int): 추천하고 싶은 영상 수
    (기본값은 5)
    
Returns:
  list: 추천할 영상 주소가 담긴 리스트
"""
# reStructuredText (파이썬 공식 문서화 기준)

"""유저에게 추천할 영상을 찾아준다
    
:param number_of_suggestions: 추천하고 싶은 영상 수
  (기본값은 5)
:type number_of_suggestions: int
:returns: 추천할 영상 주소가 담긴 리스트
:rtype: list
"""
# NumPy/SciPy (통계, 과학 분야에서 쓰이는 Python 라이브러리)

"""유저에게 추천할 영상을 찾아준다
    
Parameters
----------
number_of_suggestions: int
  추천하고 싶은 영상 수 (기본값은 5)
    
Returns
-------
list 
  추천할 영상 주소가 담긴 리스트
"""



2. 캡슐화(Encapsulation)

  1. 객체의 일부 구현 내용에 대한 외부로부터의 직접적인 액세스를 차단하는 것
  • __변수명 처럼 변수명 앞에 __를 붙이면, 이 변수는 해당 클래스 내에서만 사용할 수 있게 된다. 클래스 밖에서 접근하면 에러가 발생한다.
  • __ 를 붙인 변수나 메소드는 네임 맹글링되어 _클래스명__변수명 형태의 새로운 이름을 갖게 된다. 따라서 _클래스명__변수명을 통해서는 접근이 가능하다. (사실 파이썬에서는 언어 자체에서 캡슐화를 지원하지 않는다)
  1. 객체의 속성과 그것을 사용하는 행동하나로 묶는 것
  • 숨겨진 변수에 접근 가능한 메소드가 있다면 클래스 밖에서도 접근 가능하게 된다.
  • 변수에 접근하는 통로를 메소드로 제한했다. = 속성과 행동을 하나로 묶었다.

  • getter 메소드 - 변수의 값을 읽는 메소드
    메소드 위에 @property 입력하면 getter 메소드로 사용할 수 있다. 변수/메소드 호출할 때 이 메소드 호출된다.
  • setter 메소드 - 변수의 값을 설정하는 메소드
    메소드 위에 메소드명.setter 입력하면 setter 메소드로 사용할 수 있다. 변수/메소드 값을 설정할 때 이 메소드 호출된다.

파이썬의 문화에 따라.. 파이썬에서는 변수나 메소드 앞에 `_` 언더바 하나를 붙이면 직접 접근하지 않기로 경고하는 것이다. 변수를 직접 접근하지 않게 하는 것이 유지보수 하기 쉬운 코드이다.

3. 상속(Inheritance)

두 클래스 사이에 부모-자식 관계를 설정하는 것

  • mro() - 메소드 검색 순서.
    어떤 부모 클래스를 가지는지 보여준다.
  • isinstance(검사할 인스턴스, 기준 클래스) - 어떤 인스턴스가 주어진 클래스의 인스턴스인지 알려준다. 불린 값으로 리턴한다.
  • issubclass(검사할 클래스, 기준이 되는 부모 클래스) - 한 클래스가 다른 클래스의 자식 클래스인지 알려준다. 불린 값으로 리턴한다.

오버라이딩(Overriding)

부모 클래스의 내용을 자식 클래스에서 덮어쓰는 것

  • super() - 부모 클래스의 메소드 호출 가능
    여기서는 self 파라미터를 받지 않는다.
    ex. super().__init__(name, wage) 부모 클래스의 name과 wage 정의 내용을 가져온다.

다중 상속

하나의 클래스가 여러 개의 클래스를 상속 받는 것

  • super() 이용 불가능 - 어떤 부모 클래스를 말하는 것인지 모르니까
  • 상속받는 순서에 따라 mro가 설정된다.

다중 상속의 단점을 보완하기 위해..
부모클래스끼리 같은 이름의 메소드를 갖지 않도록 하고, 같은 이름의 메소드는 자식클래스에서 오버라이딩을 하여야 한다.
부모클래스를 참조할 때의 애매모호함 때문에 되도록 다중 상속을 하지 않는 것이 바람직하다.



4. 다형성(Polymorphism)

하나의 변수가 서로 다른 클래스의 인스턴스를 가리키는 성질 (인스턴스가 그 메소드를 갖고 있어야만 다형성이 성립된다.)

상속을 활용한 다형성

  1. 상속을 통해 isinstance()를 한 번만 써서 사용할 수 있다.
  2. 부모 클래스를 추상 클래스 (여러 클래스들의 공통점을 추상화해서 모아놓은 클래스)로 만들어서 사용할 수 있다.
  • from abc import ABC, abstractmethod
  • class ClassName(ABC) ABC 상속받기
  • @abstractmethod 추상 메소드(자식 클래스가 반드시 오버라이딩 해야 하는 메소드) 만들기
    데코레이터가 없는 일반 메소드가 있어도 상관 없다.
  • 자식 클래스가 추상 클래스를 상속받은 후, 오버라이딩하면 일반 클래스로 사용 가능하다.

추상 클래스 다중 상속은 일반적으로 많이 사용한다.
다중 상속받는 부모 추상 클래스들이 추상 메소드로만 이뤄져 있으면 아무 문제 없이 다중 상속받을 수 있다. 다중 상속받는 부모 추상 클래스들 간에 이름이 겹치는 일반 메소드가 있으면 일반 클래스를 다중 상속받을 때와 동일한 문제가 생길 수 있다.

파이썬 EAFP 코딩 스타일

LBYL Look Before You Leap (뛰기 전에 살펴보라)
EAFP Easier to Ask for Forgiveness than Permission (허락보다 용서가 쉽다) - 일단 먼저 빨리 실행하고, 문제가 생기면 처리한다.

  • isinstance() 대신 try except 구문 사용
  • 처음부터 정확하게 설계하는 방식보다 문제가 생기면 예외처리를 하는 방식으로 더 효율적이고 자유로운 언어이다.





견고한 객체 지향 프로그래밍

  • 레슨 수 : 29강
  • 난이도 : 중급

강의 소개 : 객체 지향 프로그래밍을 어떻게 하면 더 잘할 수 있을까요? 이번 토픽에서는 객체를 설계하는 다섯 가지 원칙에 대해 배웁니다. 이걸 각 원칙의 앞 글자만 따서 SOLID라고 부르죠. 한 가지씩 살펴보면서, 복잡한 코드도 깔끔하고 수정하기 쉬운 ‘견고한' 코드로 만드는 방법을 알아봅시다.



1. 단일 책임 원칙 (Single Responsibility Principle)

모든 클래스는 단 한가지의 책임만을 갖고, 클래스 안에 정의되어 있는 모든 기능은 이 하나의 책임을 수행하는데 집중되어 있어야 한다.
즉, 하나의 클래스로 너무 많은 일을 하지 말고, 딱 한 가지 책임만 수행해야 한다. <-> God Object
같이 수정해야될 것들은 묶고, 따로 수정해야될 것들은 분리하면 된다.


2. 개방 폐쇄 원칙 (Open-Closed Principle)

클래스는 확장에 열려있어야 하며, 수정에는 닫혀있어야 한다. 즉, 기존 클래스의 코드를 수정하지 않고도 기능을 확장할 수 있어야 한다.
이는 추상 클래스 활용으로 해결된다.


3. 리스코프 치환 원칙 (Liskov Substitution Principle)

부모 클래스의 인스턴스를 사용하는 위치에 자식 클래스의 인스턴스를 대신 사용했을 때 코드가 원래 의도대로 작동해야 한다. 즉, 부모 클래스의 행동규약을 자식 클래스가 위반하지 말아야 한다는 것이다.

자식 클래스가 오버라이딩을 잘못 하는 경우,
1. (형식적인 측면) 자식 클래스가 부모 클래스의 변수의 타입을 바꾸거나 메소드의 파라미터 또는 리턴값의 타입이나 갯수를 바꾸는 경우
2. (내용적인 측면) 자식 클래스가 부모 클래스의 의도와 다르게 메소드를 오버라이딩 하는 경우
자식 클래스가 부모 클래스의 행동규약을 위반하게 된다.


4. 인터페이스 분리 원칙 (Interface Segregation Principle)

추상 클래스 중에서 추상 메소드만 있고 일반 메소드는 없는 것을 인터페이스라고 한다.

클래스가 사용하지 않을 메소드에 의존(메소드를 갖는다)할 것을 강요하면 안 된다. 즉, 클래스가 나중에 사용하지도 않을 메소드를 가지도록 강제하면 안 된다.
상속받는 자식 클래스가 자신에게 필요하지도 않은 메소드를 굳이 오버라이딩 하게 되면 안 된다.

이를 해결하려면 기능, 역할에 따라 더 작은 추상클래스 역할 인터페이스(role interface) 로 분리해야 한다.


5. 의존 관계 역전 원칙 (Dependency Inversion Principle)

상위 모듈은 하위 모듈의 구현 내용에 의존하면 안 된다. 상위 모듈과 하위 모듈 모두 추상화된 내용에 의존해야 한다.

  • 상위 모듈 : 다른 클래스를 사용하는 주된 클래스
  • 하위 모듈 : 사용되는 클래스

즉, 상위 모듈이 하위 모듈을 사용할 때 직접 인스턴스를 가져다가 쓰지 말라는 것이다. 의존하게 되면 하위 모듈에 변화가 있을 때마다 상위 모듈의 코드를 자주 바꿔줘야 하기 때문이다.

이에 대한 해결책은 추상 클래스로 상위 모듈과 하위 모듈 사이에 추상화 레이어를 만드는 것이다.

  • 상위 모듈에는 추상 클래스의 자식 클래스의 인스턴스를 사용한다는 가정 하에 그 하위 모듈을 사용하는 코드를 작성해두면 되고,
  • 하위 모듈은 추상 클래스의 추상 메소드들을 구현(오버라이딩)만 하면 된다.

profile
planning design development with data

0개의 댓글