객체지향 리팩토링

jjin·2024년 3월 21일
0

0. 기존 코드

class GrabStore:
    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):
        self.money = 0
        self.store = GrabStore()
        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)

문제 1. 다른 Store가 온다면?

문제 1. 다른 Store가 온다면?
1) Store 추상화
2) User에 Store 의존성 주입

from abc import ABC, abstractmethod

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

	@abstractmethod
    def set_money(self, money): #보통 이렇게 안씀. 구체적 함수명으로
		pass
    
	@abstractmethod
    def set_products(self, products):
        self.products = products

	@abstractmethod
    def get_money(self):
        return self.money

	@abstractmethod
    def get_products(self):
        return self.products

class GrabStore(Store):
    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 FruitsStore(Store):
	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 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_a = User(store=GrabStore())
    user_b = User(store=FruitsStore())

문제 2. Store에 있는 상품과 돈을 마음대로 접근할 수 있다.

문제 1. 다른 Store가 온다면?
1) Store 추상화
2) User에 Store 의존성 주입

문제 2. Store에 있는 상품과 돈을 마음대로 접근할 수 있다.
1) Store의 책임을 정의하고 캡슐화
2) User의 결제 로직 수정
3) User 캡슐화


from abc import ABC, abstractmethod

class Store(ABC):
	@abstractmethod
    def __init__(self):
    	self.money = 0
        self.name = ""
        self.products = {}
        
    @abstractmethod
    def show_product(self, product_id):
    	pass
    
    @abstractmethod
    def give_product(self, product_id):
    	pass
        
    @abstractmethod
    def take_money(self, money):
    	pass

class GrabStore(Store):
    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 get_money(self):
    #    return self._money
    #def get_products(self):
    #    return self._products

    def show_product(self, product_id):
    	return self._products[product_id]
    
    def give_product(self, product_id):
    	self._products.pop(product_id)
        
    def take_money(self, money):
    	self._money += money

class User:
    def __init__(self, money, 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 set_store(self, store):
    	self.store = store

    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.show_product(product_id=product_id)
        return product

    def purchase_product(self, product_id):
        product = self.see_product(product_id)
        if self.money >= product["price"]:
            self.store.give_product(product_id)  # 상점에서 상품 꺼내기
            self.money -= product["price"]  # 사용자가 돈 내기
            self.store.take_money(product["price"])  # 상점에서 돈 받기
            self.belongs.append(product)
            return product
        else:
            raise Exception("잔돈이 부족합니다")

if __name__ == "__main__": # 컴파일 타임엔 추상화, 런타임에 구현체 대입
	store = Grabstore(
    	products = {
        	1: {"name": "키보드", "price": 30000},
            2: {"name": "모니터", "price": 50000}
        }
    )
    user = User(money=100000, store=store)
    user.purchase_product(product_id=1)
    print(f"user가 구매한 상품: {user.get_belongs()}")

문제 3. 상품 판매 책임이 유저에 있다

문제 1. 다른 Store가 온다면?
1) Store 추상화
2) User에 Store 의존성 주입

문제 2. Store에 있는 상품과 돈을 마음대로 접근할 수 있다.
1) Store의 책임을 정의하고 캡슐화
2) User의 결제 로직 수정
3) User 캡슐화

문제 3. 상품 판매 책임이 User에 있다
1) Store에서 판매를 추상화하고, 구체 로직을 이동한다.

from abc import ABC, abstractmethod

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
        
    @abstractmethod
    def take_money(self, money):
    	pass

class GrabStore(Store):
    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 sell_product(self, product_id, money):
    	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 set_money(self, money):
    #    self._money = money

    #def set_belongs(self, belongs):
    #    self.belongs = belongs

	def set_store(self, store):
    	self.store = store

    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.show_product(product_id=product_id)
        return product

    def purchase_product(self, product_id):
        product = self.see_product(product_id)
        price = product["price"]
        if self._money >= price:
            self._give_money(money=price)
            try:
            	my_product = self.store.sell_product(product_id=product_id, money=price)
            	self._add_belong(my_product) # sell_product에만 의존
            except Exception as e:
            	self._take_money(money=price)
                print(f"구매 중 문제 발생 {str(e)}")
            return product
        else:
            raise Exception("잔돈이 부족합니다")

	def _give_money(self, money):
    	self._money -= money
        
    def _take_money(self, money):
    	self._money += money
        
    def _add_belong(self, money):
    	self.belongs.append(product)

if __name__ == "__main__": # 컴파일 타임엔 추상화, 런타임에 구현체 대입
	store = Grabstore(
    	products = {
        	1: {"name": "키보드", "price": 30000},
            2: {"name": "모니터", "price": 50000}
        }
    )
    user = User(money=100000, store=store)
    user.purchase_product(product_id=1)
    print(f"user가 구매한 상품: {user.get_belongs()}")

문제 4. product가 책임을 가지자

문제 1. 다른 Store가 온다면?
1) Store 추상화
2) User에 Store 의존성 주입

문제 2. Store에 있는 상품과 돈을 마음대로 접근할 수 있다.
1) Store의 책임을 정의하고 캡슐화
2) User의 결제 로직 수정
3) User 캡슐화

문제 3. 상품 판매 책임이 User에 있다
1) Store에서 판매를 추상화하고, 구체 로직을 이동한다.

문제 4. product가 책임을 가지자, 리터럴을 없애자
1) 딕셔너리 타입을 클래스(데이터클래스: 필드데이터, 타입체킹 도움) 객체로 변환
2) 구체적 로직 새 메서드로 추출

from abc import ABC, abstractmethod
from dataclasses import dataclass

@dataclass
class Product:
	name: str
    price: int
 
# product = Product(name=name, price=price)
# product.name로 접근 가능

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
        
    @abstractmethod
    def take_money(self, money):
    	pass

class GrabStore(Store):
    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 sell_product(self, product_id, money):
    	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 set_money(self, money):
    #    self._money = money

    #def set_belongs(self, belongs):
    #    self.belongs = belongs

	def set_store(self, store):
    	self.store = store

    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.show_product(product_id=product_id)
        return product

    def purchase_product(self, product_id):
        product = self.see_product(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) # sell_product에만 의존
            except Exception as e:
            	self._take_money(money=price)
                print(f"구매 중 문제 발생 {str(e)}")
            return product
        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, money):
    	self.belongs.append(product)

if __name__ == "__main__": # 컴파일 타임엔 추상화, 런타임에 구현체 대입
	store = Grabstore(
    	products = {
        	1: Product(name="키보드", price=30000),
            2: Product(name="모니터", price=50000)
        }
    )
    user = User(money=100000, store=store)
    user.purchase_product(product_id=1)
    print(f"user가 구매한 상품: {user.get_belongs()}")
profile
진짜

0개의 댓글

관련 채용 정보