본문 바로가기
Language/Python

[Python] refactoring - 01. 추상화, 생성자주입을 통한 리팩토링

by 며루치꽃 2022. 4. 20.


목적

리팩토링할 코드를 가지고, 여러 번의 단계에 걸쳐 리팩토링을 진행한다.
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만 사용할 수 있게 된다.
  • 컴파일 레벨에서는 정적인 의존성을 가질 때인터페이스 객체를 사용하고, 실제 런타임 레벨에서는 생성자를 통해 구현체 들이 들어가게 된다.
  • 설계를 유연하게 가져갈 수 있다.

댓글