목적
리팩토링할 코드를 가지고, 여러 번의 단계에 걸쳐 리팩토링을 진행한다.
As-is에서 나온 개선해야할 부분을 To-be를 통해 개선한다.
리팩토링 전 문제점 파악
As-is
class EricStore:
def __init__(self) -> None:
self.money = 0
self.name = "에릭상점"
self.products = {
1: {"name": "키보드", "price": 30000},
2: {"name": "모니터", "price": 50000},
}
def set_money(self, money):
self.money = money
def set_products(self, products):
self.products = products
def get_money(self):
return self.money
def get_products(self):
return self.products
class User:
def __init__(self):
self.money = 0
self.store = EricStore()
self.belongs = []
def set_money(self, money):
self.money = money
def set_belongs(self, belongs):
self.belongs = belongs
def get_money(self):
return self.money
def get_belongs(self):
return self.belongs
def get_store(self):
return self.store
def see_product(self, product_id):
products = self.store.get_products()
return products[product_id]
def purchase_product(self, product_id):
product = self.see_product(product_id)
if self.money >= product["price"]:
self.store.products.pop(product_id) # 상점에서 상품 꺼내기
self.money -= product["price"] # 사용자가 돈 내기
self.store.money += product["price"] # 상점에서 돈 받기
self.belongs.append(product)
return product
else:
raise Exception("잔돈이 부족합니다")
if __name__ == "__main__":
user = User()
user.set_money(100000)
user.purchase_product(product_id=1)
해당 코드의 문제점
- 여러개의 Store을 가질 수 없다. 현재는 EricStore 밖에 가질 수 없다
- Store가 변경되었을 때 문제가 된다
- User가 다른 Store을 갈 수 없다
개선할 점
- 추상화를 통해 공통적인 특징을 뽑아낸다
- 추상화를 통해 상점의 공통적인 특징을 뽑아낸다
- User가 다른 store을 갈 수 있게 의존성 주입을 통해 상점을 넣어준다
리팩토링
from abc import ABC, abstractclassmethod
class Store(ABC):
@abstractclassmethod
def __init__(self):
self.money = 0
self.name = ""
self.products = {}
@abstractclassmethod
def set_money(self, money):
pass
@abstractclassmethod
def set_products(self, products):
pass
@abstractclassmethod
def get_money(self):
pass
@abstractclassmethod
def get_products(self):
pass
To-be
- 파이썬은 추상클래스를 통해 인터페이스를 구현
- 추상클래스를 구현하기 위해서는 ABC 모듈을 import하고
@abstractclassmethod
를 통해 명시한다. - 추상클래스에서 공통 속성(attribute)과 공통 메서드(method)를 정의한다
- 추상클래스를 구현하는 구현 클래스들은 추상클래스에서 정의된 메서드를 override할 수 있다.
인터페이스를 구현한 구현 클래스
class EricStore:
def __init__(self):
self.money = 0
self.name = "에릭상점"
self.products = {
1: {"name": "키보드", "price": 30000},
2: {"name": "모니터", "price": 50000},
}
def set_money(self, money):
self.money = money
def set_products(self, products):
self.products = products
def get_money(self):
return self.money
def get_products(self):
return self.products
class FruitStore:
def __init__(self):
self.money = 0
self.name = "과일상점"
self.products = {
1: {"name": "바나나", "price": 30000},
2: {"name": "사과", "price": 50000},
}
def set_money(self, money):
self.money = money
def set_products(self, products):
self.products = products
def get_money(self):
return self.money
def get_products(self):
return self.products
생성자 주입(DI)
As-is
class User:
def __init__(self):
self.money = 0
self.store = EricStore()
self.belongs = []
- 현재 유저는 생성자를 통해 초기화되는데 이 때, EricStore에만 종속이 되어있어서 EricStore만 사용할 수 있음
- 만약 EricStore가 아닌 다른 Store을 사용하려면 그 때마다 코드를 변경해야한다.
To-be
class User:
def __init__(self, money, store: Store):
self.money = money
self.store = store # 의존성 주입(구현체 대신에 인터페이스가 들어간다) -> 런타임에서 생성자 주입
self.belongs = []
- 인터페이스를 사용해 저수준의 코드(구현체)와 고수준의 코드(인터페이스)를 구별하였다
- 클라이언트 입장에서 수준이 구별이 되어있기 때문에 의존성을 주입할 수가 있다.
- 의존성 주입을 이용하여 고수준의 코드를 넣어서 유연하게 사용할 수 있게한다. 저수준의 코드(구현체)를 넣는 순간 해당 Store만 사용할 수 있게 된다.
- 컴파일 레벨에서는 정적인 의존성을 가질 때는 인터페이스 객체를 사용하고, 실제 런타임 레벨에서는 생성자를 통해 구현체 들이 들어가게 된다.
- 설계를 유연하게 가져갈 수 있다.
'Language > Python' 카테고리의 다른 글
[Python] refactoring - 02. 캡슐화, DI를 통한 리팩토링 (0) | 2022.04.20 |
---|---|
[Python] Type Hint, Callable types (0) | 2021.12.19 |
[Python] 컴포지션(composition) (0) | 2021.12.16 |
[Python] 캡슐화(encapsulation) - @property 와 getter와 setter의 개념 (0) | 2021.12.14 |
[Python] 네임스페이스(namespace)에 대한 이해 (0) | 2021.12.08 |
댓글