Single responsibility principle
모든 클래스는 단 한 가지의 책임만을 갖고, 클래스 안에 정의되어있는 모든 기능은 이 하나의 책임을 수행하는데 집중되어있어야한다.
같이 수정해야될 것들은 묶고, 따로 수정해야할 것은 빼는 것
하나의 클래스가 하나의 책임만 수행하면 된다.
class Ship:
"""배 클래스"""
def __init__(self, fuel, fuel_per_hour, supplies, num_crew):
"""연료량, 시간당 연료 소비량, 물자량, 선원 수를 인스턴스 변수로 갖는다"""
self.fuel = fuel
self.fuel_per_hour = fuel_per_hour
self.supplies = supplies
self.num_crew = num_crew
def report_fuel(self):
"""연료량 보고 메소드"""
print("현재 연료는 {}l 남아 있습니다".format(self.fuel))
def load_fuel(self, amount):
"""연료 충전 메소드"""
self.fuel += amount
def report_supplies(self):
"""물자량 보고 메소드"""
print("현재 물자는 {}명분이 남아 있습니다".format(self.supplies))
def load_supplies(self, amount):
"""물자 보급 메소드"""
self.supplies += amount
def distribute_supplies_to_crew(self):
"""물자 배분 메소드"""
if self.supplies >= self.num_crew:
self.supplies -= self.num_crew
return True
print("물자가 부족하기 때문에 배분할 수 없습니다")
return False
def report_crew(self):
"""선원 수 보고 메소드"""
print("현재 선원 {}명이 있습니다".format(self.num_crew))
def load_crew(self, number):
"""선원 승선 메소드"""
self.num_crew += number
def run_engine_for_hours(self, hours):
"""엔진 작동 메소드"""
if self.fuel > self.fuel_per_hour * hours:
self.fuel -= self.fuel_per_hour * hours
print("엔진을 {}시간 동안 돌립니다!".format(hours))
else:
print("연료가 부족하기 때문에 엔진 작동을 시작할 수 없습니다")
Python
복사
배 클래스 → 연료, 크루, 엔진, 물자
GOD Object → 하나의 클래스가 여러 개의 책임을 수행하는 그런 모습
클래스를 단일 책임 원칙에 맞게 쪼개준다.
class Ship:
"""배 클래스"""
def __init__(self, fuel, fuel_per_hour, supplies, num_crew):
"""연료량, 시간당 연료 소비량, 물자량, 선원 수를 인스턴스 변수로 갖는다"""
self.fuel_tank = FuelTank(fuel)
self.crew_manager = CrewManager(num_crew)
self.supply_hold = SupplyHold(supplies,self.crew_manager)
self.engine = Engine(self.fuel_tank, fuel_per_hour)
class FuelTank:
def __init__(self,fuel):
self.fuel = fuel
def report_fuel(self):
"""연료량 보고 메소드"""
print("현재 연료는 {}l 남아 있습니다".format(self.fuel))
def load_fuel(self, amount):
"""연료 충전 메소드"""
self.fuel += amount
def user_fuel(self,amount):
"""연료 사용 메소드"""
if self.fuel - amount >= 0:
self.fuel -= amount
return True
print("연료가 부족합니다!")
return False
class Engine:
def __init(self,fuel_tank,fuel_per_hour):
self.fuel_tank = fuel_tank
self.fuel_per_hour = fuel_per_hour
def run_for_hours(self, hours):
"""엔진 작동 메소드, 연료 탱크 인스턴스를 사용"""
if self.fuel_tank.use_fuel(self.fuel_per_hour * hours):
print("엔진을 {}시간 동안 돌립니다!".format(hours))
return True
print("연료가 부족하기 때문에 엔진 작동을 시작할 수 없습니다")
return False
class CrewManager:
def __init__(self,num_crew):
self.num_crew = num_crew
def report_crew(self):
"""선원 수 보고 메소드"""
print("현재 선원 {}명이 있습니다".format(self.num_crew))
def load_crew(self, number):
"""선원 승선 메소드"""
self.num_crew += number
class SupplyHold:
def __init__(self,supplies,crew_manager):
self.supplies = supplies
self.crew_manager = crew_manager
def report_supplies(self):
"""물자량 보고 메소드"""
print("현재 물자는 {}명분이 남아 있습니다".format(self.supplies))
def load_supplies(self, amount):
"""물자 보급 메소드"""
self.supplies += amount
def distribute_supplies_to_crew(self):
"""물자 배분 메소드"""
if self.supplies >= self.crew_manager.num_crew:
self.supplies -= self.crew_manager.num_crew
return True
print("물자가 부족하기 때문에 배분할 수 없습니다")
return False
Python
복사
Ship 클래스는 책임을 자신이 직접 수행하지 않는다. 관련된 클래스에 위임해서 시킨다.
전체 코드 길이는 길어도 한 클래스의 길이를 짧게 하는게 좋다
코드를 작성할 때, 단일 책임 원칙 신경 쓰기
나중에 코드가 너무 복잡해서 수정하기가 너무 어렵다
이번 챕터에서 배운 단일 책임 원칙(Single Responsibility Principle)을 정리해볼게요.
단일 책임 원칙의 정의는,
"모든 클래스는 단 한 가지의 책임만을 갖고, 클래스 안에 정의되어 있는 모든 기능은, 이 하나의 책임을 수행하는데 집중되어 있어야 한다."
입니다. 간단히 말해서, 하나의 클래스로 너무 많은 일을 하지 말고, 딱 한 가지 책임만 수행하라는 뜻입니다.
물론 어디까지가 한 가지 책임이라고 할 수 있는지는, 사람들마다 생각이 다르고, 상황에 따라서도 다릅니다.
SOLID 원칙을 만든 개발자, Robert C. Martin은
“단일 책임 원칙은 같이 수정해야될 것들은 묶고, 따로 수정해야될 것들은 분리하는 것”이라고 했습니다.
그러니까 서로 관련있는 내용들을 적절한 크기의 한 클래스로 만들라는 뜻입니다.
프로그램의 유지 보수 차원에서 말하자면 한 클래스는 한가지 책임에 관한 변경사항이 생겼을 때만 코드를 수정하게 되는 구조가 좋은 구조라는 뜻이구요.
사실 어떤 클래스를 보고, 그 클래스가 단일 책임 원칙을 지켰는지를 판단하는 건 쉽지 않습니다.
어떤 프로그램을 만들고 있는지에 따라, 개발자에 따라 그 생각은 다르기 때문입니다.
중요한 것은 코드를 작성할 때, 내가 단일 책임 원칙을 지키고 있는지 신경쓰는 것입니다.
클래스를 작성할 때마다, 이 클래스가 너무 많은 책임을 갖고 있는 건 아닌지 항상 의심해야 합니다.
하나의 클래스 안에 여러 클래스로 분리할 수 있을 것 같은 변수와 메소드들이 많다면, 그 클래스는 지금 단일 책임 원칙을 어기고 있을 가능성이 높습니다.
단일 책임 원칙을 지키지 않아도 처음에는 별 문제가 없을 수 있습니다.
하지만 프로그램의 크기가 커질수록 단일 책임 원칙을 지키지 않은 것 때문에 코드를 수정하기가 점점 힘들어지는 걸 발견하게 되실 겁니다.
그러니까 항상 수정하기 편한 상태의 코드를 유지하고 싶다면, 이번 챕터에서 배운 ‘단일 책임 원칙’을 늘 기억하세요.
Plain Text
복사
개방 폐쇄 원칙
확장에 열려 있다는 건 기존 기능에 확장할 수 있다는 것이고, 수정에 닫혀있다는 건 한 번 작성한 코드를 바꾸지 않아도 되는 것이다.
코드를 수정하지 않아도 기존 기능을 확장할 수 있어야한다.
class AppleKeyboard:
"""애플 키보드 클래스"""
def __init__(self):
"""키보드 인풋과 터치바 인풋"""
self.keyboard_input = ""
def set_keyboard_input(self, input):
"""키보드 인풋 저장 메소드"""
self.keyboard_input = input
def send_keyboard_input(self):
"""키보드 인풋 전송 메소드"""
return self.keyboard_input
class KeyboardManager:
def __init__(self):
"""키보드 관리 클래스"""
self.keyboard = None
def connect_to_keyboard(self, keyboard):
"""키보드 교체 메소드"""
self.keyboard = keyboard
def get_keyboard_input(self):
"""유저가 키보드로 입력한 내용을 받아오는 메소드"""
if isinstance(self.keyboard, AppleKeyboard):
return self.keyboard.send_keyboard_input()
elif isinstance(self.keyboard,SamsungKeyboard):
return self.keyboard.give_user_input()
class SamsungKeyboard:
def __init__(self):
self.user_input = ""
def save_user_input(self,input):
self.user_input = input
def give_user_input(self):
return self.user_input
keybord_manager = KeyboardManager()
apple_keyboard = AppleKeyboard()
keybord_manager.connect_to_keyboard(apple_keyboard)
apple_keyboard.set_keyboard_input("안녕하세요")
print(keybord_manager.get_keyboard_input())
JavaScript
복사
KeyboardManager 클래스는 현내 개방폐쇄 원칙을 지키지 못했다.
from abc import ABC,abstractmethod
class Keyboard(ABC):
@abstractmethod
def save_input(self,content:str)->None:
pass
@abstractmethod
def send_input(self)->str:
pass
class AppleKeyboard(Keyboard):
"""애플 키보드 클래스"""
def __init__(self):
"""키보드 인풋과 터치바 인풋"""
self.keyboard_input = ""
def save_input(self, input):
"""키보드 인풋 저장 메소드"""
self.keyboard_input = input
def send_input(self):
"""키보드 인풋 전송 메소드"""
return self.keyboard_input
class KeyboardManager():
def __init__(self):
"""키보드 관리 클래스"""
self.keyboard = None
def connect_to_keyboard(self, keyboard):
"""키보드 교체 메소드"""
self.keyboard = keyboard
def get_keyboard_input(self):
"""유저가 키보드로 입력한 내용을 받아오는 메소드"""
self.keyboard.send_input()
class SamsungKeyboard(Keyboard):
def __init__(self):
self.user_input = ""
def save_input(self,input):
self.user_input = input
def send_input(self):
return self.user_input
JavaScript
복사
keyboardManager는 개방폐쇄원칙을 지킨다. keyboard라는 변수는 다형성을 가지고 잇다.
리스코프 치환원칙
부모 클래스의 인스턴스를 사용하는 위치에 자식 클래스의 인스턴스를 대신 사용했을 때,
코드가 의도대로 작동해야한다
부모 클래스의 행동규약을 자식클래스가 어기지않도록 해라
자식 클래스에서 부모 클래스의 변수와 메소드를 오버라이딩할 때 리스코프 치환원칙을 어길 수 있다.
오버라이딩을 잘못하는 경우
1.
변수의 타입을 바꾸거나, 메소드의 파라미터 또는 리턴값의 타입 or 갯수를 바꾸는 경우
2.
자식 클래스가 부모 클래스의 의도와 다르게 메소드를 오버라이딩 하는 경우
1. 변수의 타입을 바꾸거나, 메소드의 파라미터 또는 리턴값 타입, 갯수를 바꾸는 경우
class Employee:
"""직원 클래스"""
company_name = "코드잇 버거"
raise_percentage = 1.03
def __init__(self, name, wage):
self.name = name
self._wage = wage
def raise_pay(self):
"""직원 시급을 인상하는 메소드"""
self._wage *= self.raise_percentage
@property
def wage(self):
return self._wage
def __str__(self):
"""직원 정보를 문자열로 리턴하는 메소드"""
return Employee.company_name + " 직원: " + self.name
class Cashier(Employee):
"""리스코프 치환 원칙을 지키지 않는 계산대 직원 클래스"""
burger_price = 4000
def __init__(self, name, wage, number_sold=0):
super().__init__(name, wage)
self.number_sold = number_sold
def raise_pay(self, raise_amount):
"""직원 시급을 인상하는 메소드"""
self.wage += self.raise_amount
@property
def wage(self):
return "시급 정보를 알려줄 수 없습니다"
Python
복사
raise_pay 와 wage의 getter의 경우 리스코프 치환원칙 위반이다.
raise_pay라고 하는 함수가 파라미터를 받는다 → Employee 클래스에서 raise_pay는 파라미터를 받지 않는다라는 규약을 어긴다.
wage → Employee클래스에서 숫자를 리턴한다는 규약을 어겼다.
class Employee:
"""직원 클래스"""
company_name = "코드잇 버거"
raise_percentage = 1.03
def __init__(self, name, wage):
self.name = name
self._wage = wage
def raise_pay(self):
"""직원 시급을 인상하는 메소드"""
self._wage *= self.raise_percentage
@property
def wage(self):
return self._wage
def __str__(self):
"""직원 정보를 문자열로 리턴하는 메소드"""
return Employee.company_name + " 직원: " + self.name
class Cashier(Employee):
"""계산대 직원 클래스"""
raise_percentage = 1.05
burger_price = 4000
def __init__(self, name, wage, number_sold=0):
super().__init__(name, wage)
self.number_sold = number_sold
def take_order(self, money_received):
"""손님이 낸 돈을 받아 주문 처리를 하고 거스름돈을 리턴한다"""
if Cashier.burger_price > money_received:
print("돈이 충분하지 않습니다. 돈을 다시 계산해서 주세요!")
return money_received
else:
self.number_sold += 1
change = money_received - Cashier.burger_price
return change
def __str__(self):
return Cashier.company_name + " 계산대 직원: " + self.name
Python
복사
수정한 Cashier 클래스의 코드에는 raise_pay , wage 메소드가 없군요. 리스코프 치환 원칙을 지키려면 부모 클래스의 메소드를 그냥 물려받는 것이 가장 안전하고 빠른 해결책이겠죠? 만약 오버라이딩을 하고 싶다면 메소드의 파라미터와 리턴값의 타입과 개수를 맞춰서 오버라이딩하면 됩니다. take_order 메소드는 Employee 클래스에는 없고, Cashier 클래스에서 아예 새로 추가한 메소드이므로 자유롭게 정의해도 상관없습니다.
2. 자식 클래스가 부모 클래스의 의도와 다르게 메소드를 오버라이딩 하는 경우
class Rectangle:
"""직사각형 클래스"""
def __init__(self, width, height):
"""세로와 가로"""
self.width = width
self.height = height
def area(self):
"""넓이 계산 메소드"""
return self.width * self.height
@property
def width(self):
"""가로 변수 getter 메소드"""
return self._width
@width.setter
def width(self, value):
"""가로 변수 setter 메소드"""
self._width = value if value > 0 else 1
@property
def height(self):
"""세로 변수 getter 메소드"""
return self._height
@height.setter
def height(self, value):
"""세로 변수 setter 메소드"""
self._height = value if value > 0 else 1
class Square(Rectangle):
def __init__(self, side):
super().__init__(side, side)
@property
def width(self):
"""가로 변수 getter 메소드"""
return self._width
@width.setter
def width(self, value):
"""가로 변수 setter 메소드"""
self._width = value if value > 0 else 1
self._height = value if value > 0 else 1
@property
def height(self):
"""세로 변수 getter 메소드"""
return self._height
@height.setter
def height(self, value):
"""세로 변수 setter 메소드"""
self._width = value if value > 0 else 1
self._height = value if value > 0 else 1
Python
복사
정사각형 → 직사각형 행동규약을 지키기 어렵기 때문에 상속을 안하는게 답이다.
개발자끼리 협업할 때 중요하다!
메소드를 의도한 방향대로 이끌어가기 위해 중요하다.
이번 챕터에서 배운 리스코프 치환 원칙의 내용을 한번 정리해보겠습니다. 이 원칙의 정의는
"부모 클래스의 인스턴스를 사용하는 위치에 자식 클래스의 인스턴스를 대신 사용했을 때 코드가 원래 의도대로 작동해야 한다" 입니다.
이 정의에 있는 내용대로 자식 클래스의 인스턴스가 부모 클래스의 인스턴스 대신 사용되어도 문제가 없으려면 2가지 조건을 만족해야 합니다.
첫번째는, 형식적인 측면에서 자식 클래스가 오버라이딩하는 변수와 메소드가 부모 클래스에 있는 형식과 일치해야 합니다. 변수의 경우에는 그 타입, 메소드의 경우에는 파라미터와 리턴값의 타입 및 개수가 그 형식입니다. 이런 형식적인 측면을 지키지 않으면 프로그램 실행 시에 에러가 발생하게 됩니다.
두번째는, 내용적인 측면에서 자식 클래스가 부모 클래스의 메소드에 담긴 의도, 그러니까 부모 클래스의 행동 규약을 위반하지 않는 겁니다. 이전 영상들에서 본
•
정사각형-직사각형 예시
•
음악 플레이어 예시
처럼 이 경우에는 프로그램을 실행해도 에러가 나지는 않습니다. 하지만 예상했던 결과와는 전혀 다른 결과를 프로그램이 내놓게 됩니다.
에러는 안 나는데 프로그램의 동작이 우리의 예상을 벗어난다는 것은 좀 무서운 일인데요. 예시로 봤던 코드들은 모두 내용이 짧았어서 이런 문제를 쉽게 발견했을 수도 있습니다. 하지만 만약 코드의 양이 많고 여러 객체 간의 관계가 복잡한 프로그램에서 이런 문제가 발생한다면 보이지 않는 치명적인 모순이 프로그램에 오랫동안 숨어있을 수 있습니다. 이런 일이 발생하지 않도록 하려면 자식 클래스를 설계할 때 부모 클래스의 행동 규약을 벗어나지 않도록 유의해야겠죠?
이전 영상에서 본 것처럼 리스코프 치환 원칙은 협업하는 개발자 사이의 신뢰를 위한 원칙이기도 합니다.
•
부모 클래스의 코드, 다양한 자식 클래스의 인스턴스들을 가져다 쓰는 코드를 작성하는 개발자 A와,
•
실제로 다양한 자식 클래스의 내용을 작성하는 개발자 B가 있다고 합시다.
개발자 A는 개발자 B가 리스코프 치환 원칙을 지키면서(부모 클래스의 행동 규약을 위반하지 않으면서) 자식 클래스를 작성할 것이라고 믿고, 코드를 짤 텐데요. 개발자 B가 이 믿음을 깨버린다면 나중에 최종 완성된 프로그램을 실행할 때
1.
에러가 나거나,
2.
에러가 나지는 않더라도 프로그램이 원래 의도와는 달리 비정상적으로 실행되는 현상
이 발생할 수 있겠죠? 그러니까 상속 관계를 적용할 때는 리스코프 치환 원칙을 늘 머릿속에 갖고 있어야 합니다.
인터페이스 분리 원칙
파이선에는 없다
추상 클래스에서 추상 메소드만 있고 일반 메소드가 없는 것 → 인터페이스
클래스가 사용하지 않을 메소드에 의존할 것을 강요하면 안 된다.
추상 클래스를 상속받으면 자식 클래스들은 무조건 오버라이딩해야하기 때문...
from abc import ABC, abstractmethod
class IMessage(ABC):
@property
@abstractmethod
def content(self):
"""추상 getter 메소드"""
pass
@abstractmethod
def edit_content(self, new_content: str) -> None:
"""작성한 메시지를 수정하는 메소드"""
pass
@abstractmethod
def send(self, destination: str) -> bool:
"""작성한 메시지를 전송하는 메소드"""
pass
class Email(IMessage):
def __init__(self, content, owner_email):
"""이메일은 그 내용과 보낸 사람의 이메일 주소를 인스턴스 변수로 가짐"""
self._content = content
self.owner_email = owner_email
@property
def content(self):
"""_content 변수 getter 메소드"""
return self._content
def edit_content(self, new_content):
"""이메일 내용 수정 메소드"""
self._content = self.owner_email + "님의 메일\n" + new_content
def send(self, destination):
"""이메일 전송 메소드"""
print("{}에서 {}로 이메일 전송!\n내용: {}").format(self.owner_email, destination, self._content)
return True
class TextMessage(IMessage):
def __init__(self, content):
"""문자 메시지는 그 내용을 인스턴스 변수로 가짐"""
self._content = content
@property
def content(self):
"""_content 변수 getter 메소드"""
return self._content
def edit_content(self, new_content):
"""문자 메시지 내용 수정 메소드"""
self._content = new_content
def send(self, destination):
"""문자 메시지 전송 메소드"""
print("{}로 문자 메시지 전송!\n내용: {}").format(destination, self._content)
class TextReader:
"""인스턴스의 텍스트 내용을 읽어주는 클래스"""
def __init__(self):
self.texts = []
def add_text(self, text: IMessage):
"""인스턴스 추가 메소드, 파라미터는 IMessage 인터페이스를 상속받을 것"""
self.texts.append(text)
def read_all_texts(self):
"""인스턴스 안에 있는 모든 텍스트 내용 출력"""
for text in self.texts:
print(text.content)
Python
복사
iMessage 인터페이스를 더 작은 인터페이스 두 개로 나눠줘야한다.
이렇게 큰 역할들을 다 가지고 있는 거를 뚱뚱한 인터페이스라고 한다
from abc import ABC, abstractmethod
class IText(ABC):
@property
@abstractmethod
def content(self):
"""추상 getter 메소드"""
pass
@abstractmethod
def edit_content(self, new_content: str) -> None:
"""작성한 메시지를 수정하는 메소드"""
pass
class ISendable(ABC):
@abstractmethod
def send(self, destination: str) -> bool:
"""작성한 메시지를 전송하는 메소드"""
pass
class Email(IText, ISendable):
def __init__(self, content, owner_email):
"""이메일은 그 내용과 보낸 사람의 이메일 주소를 인스턴스 변수로 가짐"""
self._content = content
self.owner_email = owner_email
@property
def content(self):
"""_content 변수 getter 메소드"""
return self._content
def edit_content(self, new_content):
"""이메일 내용 수정 메소드"""
self._content = self.owner_email + "님의 메일\n" + new_content
def send(self, destination):
"""이메일 전송 메소드"""
print("{}에서 {}로 이메일 전송!\n내용: {}").format(self.owner_email, destination, self._content)
return True
class TextMessage(IText, ISendable):
def __init__(self, content):
"""문자 메시지는 그 내용을 인스턴스 변수로 가짐"""
self._content = content
@property
def content(self):
"""_content 변수 getter 메소드"""
return self._content
def edit_content(self, new_content):
"""문자 메시지 내용 수정 메소드"""
self._content = new_content
def send(self, destination):
"""문자 메시지 전송 메소드"""
print("{}로 문자 메시지 전송!\n내용: {}").format(destination, self._content)
class Memo(IText):
def __init__(self):
self._content = content
@property
def content(self):
return self._content
def edit_content(self, new_content: str) -> None:
self._content = new_content
class TextReader:
"""인스턴스의 텍스트 내용을 읽어주는 클래스"""
def __init__(self):
self.texts = []
def add_text(self, text: IText):
"""인스턴스 추가 메소드, 파라미터는 IMessage 인터페이스를 상속받을 것"""
self.texts.append(text)
def read_all_texts(self):
"""인스턴스 안에 있는 모든 텍스트 내용 출력"""
for text in self.texts:
print(text.content)
Python
복사
인터페이스를 두 개로 작게 쪼겠다. 이래야 필요없는 메소드를 강요하지 않는다.
의존 관계 역전 원칙
Dependency inversion principle
상위 모듈은 하위 모듈의 구현 내용에 의존하면 안된다
상위 모듈과 하위 모듈 모두 추상화된 내용에 의존해야한다.
A클래스 → 사용하는 클래스 : 상위 모듈
B 클래스 → 사용당하는 클래스 : 하위 모듈
GameCharacter클래스 → 상위
Sword 클래스 → 항위
class Sword:
"""검 클래스"""
def __init__(self, damage):
self.damage = damage
def slash(self, other_character):
"""검 사용 메소드"""
other_character.get_damage(self.damage)
class GameCharacter:
"""게임 캐릭터 클래스"""
def __init__(self, name, hp, sword: Sword):
self.name = name
self.hp = hp
self.sword = sword
def attack(self, other_character):
"""다른 유저를 공격하는 메소드"""
if self.hp > 0:
self.sword.slash(other_character)
else:
print(self.name + "님은 사망해서 공격할 수 없습니다.")
def change_sword(self, new_sword):
"""검을 바꾸는 메소드"""
self.sword = new_sword
def get_damage(self, damage):
"""캐릭터가 공격받았을 때 자신의 체력을 깎는 메소드"""
if self.hp <= damage:
self.hp = 0
print(self.name + "님은 사망했습니다.")
else:
self.hp -= damage
def __str__(self):
"""남은 체력을 문자열로 리턴하는 메소드"""
return self.name + "님은 hp: {}이(가) 남았습니다.".format(self.hp)
Python
복사
GameCharacter에서 attack 메소드가 Sword 클래스에 의존한다.
어떻게 할까?
from abc import ABC, abstractmethod
class IWeapon(ABC):
@abstractmethod
def use_on(self,other_character):
pass
class Sword(IWeapon):
"""검 클래스"""
def __init__(self, damage):
self.damage = damage
def use_on(self, other_character):
"""검 사용 메소드"""
other_character.get_damage(self.damage)
class Gun(IWeapon):
def __init__(self,damage,num_rounds):
self.damage = damage
self.num_rounds = num_rounds
def use_on(self,other_character):
if self.num_rounds > 0 :
other_character.get_damage(self.damage)
self.num_rounds -= 1
else:
print("총알이 없어 공격할 수 없습니다.")
class GameCharacter:
"""게임 캐릭터 클래스"""
def __init__(self, name, hp, weapon: IWeapon):
self.name = name
self.hp = hp
self.weapon = weapon
def attack(self, other_character):
"""다른 유저를 공격하는 메소드"""
if self.hp > 0:
self.weapon.use_on(other_character)
else:
print(self.name + "님은 사망해서 공격할 수 없습니다.")
def change_sword(self, new_sword):
"""검을 바꾸는 메소드"""
self.sword = new_sword
def get_damage(self, damage):
"""캐릭터가 공격받았을 때 자신의 체력을 깎는 메소드"""
if self.hp <= damage:
self.hp = 0
print(self.name + "님은 사망했습니다.")
else:
self.hp -= damage
def __str__(self):
"""남은 체력을 문자열로 리턴하는 메소드"""
return self.name + "님은 hp: {}이(가) 남았습니다.".format(self.hp)
Python
복사
"상위 모듈은 하위 모듈의 구현 내용에 의존하면 안 된다. 상위 모듈과 하위 모듈 모두 추상화된 내용에 의존해야 한다."
추상 클래스로 상위 모듈과 하위 모듈 사이에 추상화 레이어를 만드는 것입니다.