지난번까지 SOLID principle의 SRP까지 알아보았다.
이번에는 Open-Closed, Dependency Inversion Principle에 관해 알아볼 차례이다.
Open-Closed
개방-폐쇄 원칙(Open-Closed Principle)은 소프트웨어 개체(클래스, 모듈, 함수 등등)는 확장에 대해 열려 있어야 하고, 수정에 대해서는 닫혀 있어야 한다는 프로그래밍 원칙이다.
쉽게 말하자면 다음을 충족한다고 볼 수 있다.
즉, 쉽게 추가할 수 있도록 하되, 수정하지 않고 추가만 할 수 있도록 하는 원칙이라고 할 수 있다.
예시를 통해 알아보자.
# 기능이 추가될 때마다 수정이 필요한 코드
class Animal: # 동물에 관한 클래스
def __init__(self, a_type): # 생성자
self.a_type = a_type
def hey(animal:Animal): # 동물의 울음소리 -> 클래스 변수를 지정.
if animal.a_type == 'Cat':
print('meow')
elif animal.a_type == 'Dog':
print('bark')
else: # 에러가 날 경우
raise Error('wrong a_type')
kitty = Animal('Cat')
bingo = Animal('Dog')
cow = Animal('Cow')
sheep = Animal('Sheep')
hey(cow) # 에러
hey(sheep) # 에러 -> 출력이 되기 위해서는 hey 메서드를 수정해야 한다.
해당 코드는 hey 메서드를 계속해서 추가해 주어야 한다.
# 기능이 추가되어도 수정이 필요하지 않은 코드
class Animal:
def speak(self):
pass
class Cat(Animal):
def speak(self):
print('meow')
class Dog(Animal):
def speak(self):
print('bark')
class Sheep(Animal):
def speak(self):
print('meh')
class Cow(Animal):
def speak(self):
print('moo')
def hey(animal:Animal):
animal.speak()
cow = Cow()
sheep = Sheep()
hey(cow)
hey(sheep)
그러나 해당 코드는 각자의 울음소리를 각각의 클래스로 만들어서 추가할 경우, 수정 필요없이 클래스를 추가하면 될 수 있도록 만들었다.
Dependency Inversion Principle
의존관계 역전 원칙은 소프트웨어 모듈들을 분리하는 특정 형식을 지칭한다.
이 원칙을 따르면, 상위 계층이 하위 계층에 의존하는 전통적인 의존관계를 반전(역전)시킴으로써 상위 계층이 하위 계층의 구현으로부터 독립되게 할 수 있다.
이에 대한 코드 예시를 통해 알아보자.
# 기본 코드
class Cat:
def speak(self):
print('meow')
class Dog:
def speak(self):
print('bark')
class Zoo:
def __init__(self):
self.cat = Cat()
self.dog = Dog()
# 추가된 코드
class Sheep:
def speak(self):
pass
class Cow:
def speak(self):
pass
class Zoo:
def __init__(self):
self.cat = Cat()
self.dog = Dog()
self.sheep = Sheep()
self.cow = Cow()
# 의존관계가 있도록 지정한다.
# 해당 관계를 도식화하면 다음과 같다.
'''
-------> Sheep
l
Zoo -----> Cat
l
-------> Dog
'''
여기서 Zoo 클래스는 Cat, Dog 클래스들에 의존관계가 있다.
동물들이 늘어나면 Zoo 클래스를 계속 수정해야 한다.
class Animal:
def speak(self):
pass
class Cat(Animal):
def speak(self):
print('meow')
class Dog(Animal):
def speak(self):
print('bark')
class Zoo:
def __init__(self):
self.animals = []
def addAnimal(self, animal:Animal):
self.animals.append(animal)
def speakAll(self):
for animal in self.animals:
animal.speak()
'''
Cat
-------> Animal <---------- Zoo
Dog
'''
그러나 해당 코드는 Animal 추상클래스에 의해 역전되었고, Zoo 클래스는 각 Cat, Dog 등의 클래스로부터 독립되었다.
이는, 추가를 할 때마다 수정을 할 필요가 없어졌다는 것을 의미한다.
예시 하나를 더 들어보자.
# DIP 따르지 않음 -> 수정이 필요
# 저수준 모듈: 전등 클래스
class Light:
def turn_on(self):
print("Light turned on")
def turn_off(self):
print("Light turned off")
# 저수준 모듈: 팬 클래스
class Fan:
def turn_on(self):
print("Fan turned on")
def turn_off(self):
print("Fan turned off")
# 고수준 모듈: 스위치 클래스
class Switch:
def __init__(self):
self.light = Lihgt()
self.fan = Fan()
def turn_light_on(self):
self.light.turn_on()
def turn_light_off(self):
self.light.turn_off()
def turn_fan_on(self):
self.fan.turn_on()
def turn_fan_off(self):
self.fan.turn_off()
# 예시의 사용
switch = Switch()
switch.turn_light_on()
switch.turn_light_off()
switch.turn_fan_on()
switch.turn_fan_off()
# DIP를 따름
from abc import ABC, abstractmethod # 추상 클래스와 추상 메서드를 사용하기 위한 모듈
# 추상 인터페이스 정의
class Switchable(ABC):
@abstractmethod
def turn_on(self):
pass
@abstractmethod
def turn_off(self):
pass
# 저수준 모듈: 전등 클래스
class Light(Switchable):
def turn_on(self):
print("Light turned on")
def turn_off(self):
print("Light turned off")
# 저수준 모듈: 팬 클래스
class Fan(Switchable):
def turn_on(self):
print("Fan turned on")
def turn_off(self):
print("Fan turned off")
class Switch:
def __init__(self, device):
self.device = device
def turn_on(self):
self.device.turn_on()
def turn_off(self):
self.device.turn_off()
# 예시의 사용
light = Light()
fan = Fan()
switch1 = Switch(light)
switch1.turn_on()
switch1.turn_off()
switch2 = Switch(fan)
switch2.turn_on()
switch2.turn_off()
바로 위의 코드는 추상 클래스와 추상 메서드를 이용해서 의존관계를 역전시킨 것을 볼 수 있다.