[객체 지향] 리팩토링을 통한 객체 지향 설계 연습

상황

  • 사용자가 상점에서 상품을 구매하는 코드를 구현한다.
  • 사용자와 상점은 돈을 가지고 있고, 거래가 일어나면 돈을 주고받는다.

리팩토링 전 코드

상점 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class SixminiStore:
    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

사용자 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class User:
    def __init__(self):
        self.money = 0
        self.store = SixminiStore()
        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("잔돈이 부족합니다")

실행 코드

1
2
3
4
if __name__ == "__main__":
    user = User()
    user.set_money(100000)
    user.purchase_product(product_id=1)

리팩토링 후 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
# 1. 다른 Store가 들어오면 어떻게 될까?
# 개선점
#  1. Store를 추상화한다.
#  2. 의존성 주입을 한다

# 2. Store에 있는 상품과 돈을 마음대로 접근할 수 있다.
# 개선점
#  1. Store의 책임을 정의하고 캡슐화한다.
#  2. User의 결제 로직을 수정한다.
#  3. User도 캡슐화해보자!

# 3. User가 많은 행위를 책임지고 있다. Store가 판매하는 책임을 가져야 하지 않을까?
# 개선점
#  1. 상점에서 상품을 판매하는 행위를 추상화하고 구체적인 로직을 해당 메서드로 옮긴다.

# 4. product가 책임을 가져야 하지 않을까?
# 개선점
#  1. 딕셔너리 타입을 클래스(데이터클래스) 객체로 변환하자.

from abc import ABC, abstractmethod
from dataclasses import dataclass


@dataclass
class Product:
    name: str
    price: int


class Store(ABC):
    @abstractmethod
    def __init__(self):
        self._money = 0
        self.name = ""
        self._products = {}

    @abstractmethod
    def show_product(self, product_id):
        pass

    @abstractmethod
    def sell_product(self, product_id, money):
        pass


class SixminiStore(Store):
    def __init__(self, products):
        self._money = 0
        self.name = "식스마켓"
        self._products = products

    def set_money(self, money: int):
        self._money = money

    def set_products(self, products):
        self._products = products

    def show_product(self, product_id):
        return self._products[product_id]

    def sell_product(self, product_id, money):
        # Validation 코드는 최소화
        product = self.show_product(product_id=product_id)
        if not product:
            raise Exception("상품이 존재하지 않는다")

        self._take_money(money=money)
        try:
            _product = self._take_out_product(product_id=product_id)
            return _product
        except Exception as e:
            self._return_money(money)
            raise e

    def _take_out_product(self, product_id):
        return self._products.pop(product_id)

    def _take_money(self, money):
        self._money += money

    def _return_money(self, money):
        self._money -= money

class User:
    def __init__(self, money, store: Store):
        self._money = money
        self.store = store
        self.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):
        product = self.store.show_product(product_id=product_id)
        return product

    def purchase_product(self, product_id):
        product = self.see_product(product_id=product_id)
        price = product.price
        if self._check_money_enough(price=price):
            self._give_money(money=price)
            try:
                my_product = self.store.sell_product(product_id=product_id, money=price)
                self._add_belong(my_product)
                return my_product
            except Exception as e:
                self._take_money(money=price)
                print(f"구매중 문제가 발생했습니다 {str(e)}")
        else:
            raise Exception("잔돈이 부족합니다")

    def _check_money_enough(self, price):
        return self._money >= price

    def _give_money(self, money):
        self._money -= money

    def _take_money(self, money):
        self._money += money

    def _add_belong(self, product):
        self.belongs.append(product) # List에 값을 추가할 때 append 메서드를 사용

if __name__ == "__main__":
    store = SixminiStore(
        products={
            1: Product(name="키보드", price=30000),
            2: Product(name="냉장고", price=500000)
        }
    )
    user = User(money=100000, store=store)
    user.purchase_product(product_id=2)
    print(f"user의 잔돈 : {user.get_money()}")
    print(f"user가 구매한 상품 : {user.get_belongs()}")
0%