User
클래스는 사용자 정보를 나타내고 관리하는 역할만 한다는 것을 명확히 알 수 있음class User:
def __init__(self, username, password):
self.username = username
self.password = password
def save_to_database(self):
# 데이터베이스에 사용자 정보 저장
pass
def send_email(self, message):
# 사용자에게 이메일 전송
pass
User
클래스가 사용자 정보뿐만 아니라 데이터베이스 저장 및 이메일 전송 기능도 담당하고 있어, 클래스의 역할이 불명확합니다.class User:
def __init__(self, username, password):
self.username = username
self.password = password
class UserRepository:
def save(self, user):
# 데이터베이스에 사용자 정보 저장
pass
class EmailService:
def send_email(self, user, message):
# 사용자에게 이메일 전송
pass
User
, UserRepository
, EmailService
클래스가 각각 명확한 책임을 가지고 있습니다. User
는 사용자 정보를 관리하고, UserRepository
는 데이터베이스 저장을 담당하며, EmailService
는 이메일 전송을 담당class User:
# ... (생략)
def save_to_database(self):
# 데이터베이스에 사용자 정보 저장
pass
def send_email(self, message):
# 사용자에게 이메일 전송
pass
만약 이메일 전송 방식을 변경해야 한다면 User
클래스의 코드를 수정해야 하며, 이로 인해 예상치 못한 버그가 발생할 수 있습니다.
class EmailService:
def send_email(self, user, message):
# 사용자에게 이메일 전송
pass
EmailService
클래스만 수정하면 됩니다. 이는 다른 기능에 영향을 주지 않고 변경을 수행할 수 있게 합니다.class User:
# ... (생략)
def save_to_database(self):
# 데이터베이스에 사용자 정보 저장
pass
def send_email(self, message):
# 사용자에게 이메일 전송
pass
User
클래스를 테스트하려면 데이터베이스 저장과 이메일 전송 기능을 모두 테스트해야 하며, 이는 복잡성을 증가시킵니다.class UserRepository:
def save(self, user):
# 데이터베이스에 사용자 정보 저장
pass
UserRepository
클래스를 테스트할 때는 데이터베이스 저장 기능만 테스트하면 됩니다.class User:
# ... (생략)
def save_to_database(self):
# 데이터베이스에 사용자 정보 저장
pass
def send_email(self, message):
# 사용자에게 이메일 전송
pass
다른 곳에서 이메일 전송 기능을 사용하려면 User
클래스를 그대로 사용해야 합니다.
class EmailService:
def send_email(self, user, message):
# 사용자에게 이메일 전송
pass
EmailService
클래스를 재사용하여 이메일 전송 기능을 쉽게 사용할 수 있습니다.class Discount:
def __init__(self, customer_type):
self.customer_type = customer_type
def get_discount(self):
if self.customer_type == 'regular':
return 0.1
elif self.customer_type == 'vip':
return 0.2
elif self.customer_type == 'employee': # 새로운 조건 추가
return 0.3
get_discount
메서드를 수정해야 합니다. class Discount:
def get_discount(self):
return 0
class RegularDiscount(Discount):
def get_discount(self):
return 0.1
class VIPDiscount(Discount):
def get_discount(self):
return 0.2
class EmployeeDiscount(Discount): # 새로운 클래스 추가
def get_discount(self):
return 0.3
class Discount:
def get_discount(self):
return 0
class RegularDiscount(Discount):
def get_discount(self):
return 0.1
class VIPDiscount(Discount):
def get_discount(self):
return 0.2
class DiscountCalculator:
def calculate(self, discount: Discount):
return discount.get_discount()
DiscountCalculator
는 Discount
클래스의 서브 클래스를 사용하여 다양한 할인율을 계산할 수 있습니다. class Discount:
def get_discount(self):
return 0
class RegularDiscount(Discount):
def get_discount(self):
return 0.1
class VIPDiscount(Discount):
def get_discount(self):
return 0.2
class DiscountFactory:
def get_discount(self, customer_type):
if customer_type == 'regular':
return RegularDiscount()
elif customer_type == 'vip':
return VIPDiscount()
else:
return Discount()
DiscountFactory
클래스를 통해 고객 유형에 따라 적절한 할인 클래스를 반환합니다. Discount
클래스를 확장하여 새로운 클래스를 만들고, DiscountFactory
에서 이를 반환하도록 하면 됩니다. get_discount
메서드 자체가 OCP 원칙을 어기고 있는거 아닌가?import unittest
class TestDiscount(unittest.TestCase):
def test_regular_discount(self):
discount = RegularDiscount()
self.assertEqual(discount.get_discount(), 0.1)
def test_vip_discount(self):
discount = VIPDiscount()
self.assertEqual(discount.get_discount(), 0.2)
여기서는 각 할인 클래스를 독립적으로 테스트할 수 있습니다. 새로운 할인 클래스를 추가하더라도, 그 클래스만 별도로 테스트하면 됩니다.
클라이언트가 자신이 사용하지 않는 메서드에 의존하지 않도록 -> 인터페이스를 분리해야 한다는 원칙
클라이언트가 불필요한 의존성을 가지지 않도록 하여 코드의 유연성, 유지보수성, 재사용성을 높임
class Worker:
def work(self):
pass
def eat(self):
pass
class Human(Worker):
def work(self):
pass
def eat(self):
pass
class Robot(Worker):
def work(self):
pass
def eat(self):
raise Exception("Robots don't eat")
Robot
클래스는 eat
메서드를 사용할 필요가 없는데도 불구하고, Worker
인터페이스를 구현하면서 이 메서드를 포함하고 있습니다. Robot
클래스가 불필요한 의존성을 가지게 되는 예시입니다.class Workable:
def work(self):
pass
class Eatable:
def eat(self):
pass
class Human(Workable, Eatable):
def work(self):
pass
def eat(self):
pass
class Robot(Workable):
def work(self):
pass
Workable
과 Eatable
인터페이스를 분리하여, Robot
클래스가 work
메서드만 구현하게 하고, Human
클래스는 work
와 eat
메서드를 모두 구현class Drivable:
def drive(self):
pass
class Vehicle(Drivable):
def drive(self):
pass
class Human(Workable, Eatable, Drivable):
def work(self):
pass
def eat(self):
pass
def drive(self):
pass
Drivable
인터페이스를 추가하여, Vehicle
클래스와 Human
클래스가 드라이빙 기능을 구현하도록 하였습니다. class Workable:
def work(self):
pass
class Eatable:
def eat(self):
pass
class Human(Workable, Eatable):
def work(self):
pass
def eat(self):
pass
# 단위 테스트 예시
import unittest
class TestHuman(unittest.TestCase):
def test_work(self):
human = Human()
human.work()
# 추가적인 검증 로직
def test_eat(self):
human = Human()
human.eat()
# 추가적인 검증 로직
Human
클래스의 work
메서드와 eat
메서드를 독립적으로 테스트할 수 있습니다. 이는 테스트의 용이성을 높여줍니다.고수준 모듈(상위 수준의 정책 결정 및 비즈니스 로직을 포함하는 모듈)
이 저수준 모듈(구체적인 구현을 담당하는 모듈)
에 의존하지 않도록 하여, class LightBulb:
def turn_on(self):
pass
def turn_off(self):
pass
class Switch:
def __init__(self, bulb: LightBulb):
self.bulb = bulb
def operate(self):
if self.bulb.is_on:
self.bulb.turn_off()
else:
self.bulb.turn_on()
Switch
클래스(고수준 모듈)는 LightBulb
클래스(저수준 모듈)에 직접 의존합니다. LightBulb
의 구현이 변경되면 Switch
클래스도 수정해야 합니다.class Switchable:
def turn_on(self):
pass
def turn_off(self):
pass
class LightBulb(Switchable):
def turn_on(self):
pass
def turn_off(self):
pass
class Switch:
def __init__(self, device: Switchable):
self.device = device
def operate(self):
if self.device.is_on:
self.device.turn_off()
else:
self.device.turn_on()
Switch
클래스가 Switchable
인터페이스에 의존하여, LightBulb
의 구체적인 구현에 직접 의존하지 않습니다. LightBulb
가 변경되더라도 Switch
클래스에 영향을 미치지 않습니다.class Switchable:
def turn_on(self):
pass
def turn_off(self):
pass
class LightBulb(Switchable):
def turn_on(self):
pass
def turn_off(self):
pass
# 모의 객체(Mock)를 사용한 테스트
class MockSwitchable(Switchable):
def __init__(self):
self.is_on = False
def turn_on(self):
self.is_on = True
def turn_off(self):
self.is_on = False
class Switch:
def __init__(self, device: Switchable):
self.device = device
def operate(self):
if self.device.is_on:
self.device.turn_off()
else:
self.device.turn_on()
MockSwitchable
클래스가 Switchable
인터페이스를 구현하여, Switch
클래스를 테스트할 때 사용할 수 있음class Switchable:
def turn_on(self):
pass
def turn_off(self):
pass
class LightBulb(Switchable):
def turn_on(self):
pass
def turn_off(self):
pass
class LEDBulb(Switchable):
def turn_on(self):
pass
def turn_off(self):
pass
class Switch:
def __init__(self, device: Switchable):
self.device = device
def operate(self):
if self.device.is_on:
self.device.turn_off()
else:
self.device.turn_on()
Switchable
인터페이스를 구현하는 새로운 클래스 LEDBulb
를 추가함 Switch
클래스는 Switchable
인터페이스에 의존하므로, 새로운 전구 유형이 추가되더라도 기존 코드를 수정할 필요가 없음