목적
리팩토링할 코드를 가지고, 여러 번의 단계에 걸쳐 리팩토링을 진행한다.
As-is에서 나온 개선해야할 부분을 To-be를 통해 개선한다.
리팩토링 전 문제점 파악
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
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 User:
def __init__(self, store: Store):
self.money = 0
self.store = store # 의존성 주입(구현체 대신에 인터페이스가 들어간다) -> 런타임에서 생성자 주입
self.belongs = []
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("잔돈이 부족합니다")
해당 코드의 문제점
- User 입장에서 store의 product의 attribute에 직접 접근해서 상품을 꺼내고 있다
- User 입장에서 store에게 돈을 받는 행위가 있다.
- Store에 있는 상품과 돈을 마음대로 접근할 수 있다.
- User가 상품의 속성을 알고있으면 알고 있을수록 결합도는 높아진다
개선할 점
- 중요한 정보는 캡슐화를 통해 숨기고 객체가 외부에서 사용할 수 있는 메소드만을 정의만 해주고 사용하라고 지시한다
- Store의 책임을 정의하고 캡슐화한다
- User의 결제 로직을 수정한다
- User도 캡슐화한다
to-be
- 외부에서 재고를 제외한 진열된 물품을 보여줄 수 있게 한다
- 상점은 상품을 고객에게 제공할 수 있어야한다
- 상점은 고객에게 돈을 받아야한다
리팩토링
생성자 주입을 통한 관심사 분리
As-is
class EricStore:
def __init__(self):
self.money = 0
self.name = "에릭상점"
self.products = {
1: {"name": "키보드", "price": 30000},
2: {"name": "모니터", "price": 50000},
}
- 기존에는 생성자에 물품을 제시하였다.
To-be
class EricStore:
def __init__(self, products):
self.money = 0
self.name = "에릭상점"
self.products = products
- 외부에서 생성자를 통한 의존성 주입(생성자 주입)을 통해 관심사를 분리한다
- 생성자 주입을 통해 외부에서 의존 관계를 설정함으로서 어떤 구현 객체가 들어올지 알 수 없어서 변경에 유리하다
캡슐화(상점)
As-is
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
- 캡슐화가 되어있지않아 구현체에서 set_money, set_products 등의 메서드를 이용하여 상점의 속성을 직접 접근하고 있다.
To-be
class Store(ABC):
@abstractclassmethod
def __init__(self):
self.money = 0
self.name = ""
self.products = {}
@abstractclassmethod
def show_product(self, product_id):
pass
@abstractclassmethod
def give_product(self, product_id):
pass
@abstractclassmethod
def take_money(self, money):
pass
- 해당 속성에 직접 접근하는 대신 메서드를 통한 접근을 통해 직접 접근을 막는다.
As-is
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
- attribute에 직접 접근하여 의도하지 않은 값을 수정할 노출이 있다.
To-be
class EricStore:
def __init__(self, products):
self._money = 0
self.name = "에릭상점"
self._products = products
def set_money(self, money):
self._money = money
def set_products(self, products):
self._products = products
def show_product(self, product_id):
return self._products[product_id]
def give_product(self, product_id):
self._products.pop(product_id) # products에 product_id를 key로 가지는 value를 지운다
def take_money(self, money):
self._money += money
- 캡슐화를 통한 직접 접근을 제한한다
- 구현체에서의 private 접근제어자(underscore)을 이용하여 관리할 속성들을 private하게 관리한다
- 외부 메서드나 public 접근제어자를 통해 접근하게 만든다.
캡슐화(User)
As-is
class User:
def __init__(self, store: Store):
self.money = 0
self.store = store # 의존성 주입(구현체 대신에 인터페이스가 들어간다) -> 런타임에서 생성자 주입
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("잔돈이 부족합니다")
- User 입장에서 store의 product의 attribute에 직접 접근해서 상품을 꺼내고 있다
- User 입장에서 store에게 돈을 받는 행위가 있다.
To-be
- 유저가 가지고 있는 돈도 생성자 주입을 통해 받는다
- 상황에 따라서 getter, setter가 필요없을 경우 메서드를 삭제한다
- get, set 보다 직관적인 메서드 이름으로 네이밍을 하는 것이 좋다
As-is
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("잔돈이 부족합니다")
- 유저가 마음대로 store에 있는 상품을 꺼내고 있다
- 유저가 가게의 돈을 올리고 있다.
To-be
def purchase_product(self, product_id):
product = self.see_product(product_id=product_id)
if self.money >= product["price"]:
self.store.give_product(product_id=product_id) # 상점에서 상품 꺼내기
self.money -= product["price"] # 사용자가 돈 내기
self.store.take_money(product["price"]) # 상점에서 돈 받기
self.belongs.append(product)
return product
else:
raise Exception("잔돈이 부족합니다")
- 위에서 구현한 give_product() 를 이용하여 고객이 상점의 물품을 건드리는 것이 아닌, 상점이 고객에게 물품을 제공하는 것으로 바꾼다
- take_money() 를 이용하여 상점이 스스로 물품의 돈을 얻을 수 있도록 한다
- 중요한 속성에 직접 접근하지 못하게 하여서 side-effect를 줄인다.
'Language > Python' 카테고리의 다른 글
[Python] refactoring - 01. 추상화, 생성자주입을 통한 리팩토링 (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 |
댓글