값 객체를 설명하기 위해 예시를 하나 들어보겠다. 여기 성명을 저장하기 위한 full_name이라는 변수가 있다.
full_name: str = "youmin park"
성명을 저장할때는 요구사항에 따라 달라 질 수 있는데, 단순하게 이름 전체만 저장해서 보여줘야 한다면 위와 같은 형태로 저장해도 문제가 없을 것이다.
그런데, 요구사항이 달라져서 성 또는 이름을 따로 각각 가져와야 하는 경우가 생기면 어떻게 해야할까? full_name이라는 변수에서 성과 이름을 따로 가져오려고 한다면, 아래와 같이 공백
을 기준으로 가져와야 한다.
first_name, last_name = fullname.split(" ")
하지만 공백으로 해당 데이터를 가져오려고 한다면 아래와 같은 경우는 문제가 발생한다. 의도와 다르게 성 다음에 이름이 오는 경우이다.
full_name: str = "park youmin"
이런 문제를 해결하기 위해서 값객체를 만들게 된다.
from dataclasses import dataclass
@dataclass(frozen=True)
class FullName:
first_name: str
last_name: str
위에서는 full_name을 문자열 타입으로 다뤘지만, 새로 FullName
이라는 클래스를 정의했다. 성이 필요하면 last_name
을, 이름이 필요하면 first_name
을 사용하면 된다.
FullName
클래스 처럼 시스템에서 어떤 처리를 해야하는지에 따라 적합하게 값을 나타내는 객체 를 값 객체라고 한다.
여기 어떤 사람의 사원번호이 있다.
employee_id: str = "20210101-001"
이 사원번호를 값 객체로 표현하면 이렇게 표현 할 수 있다.
from dataclasses import dataclass
from datetime import date
@dataclass(frozen=True)
class EmployeeId:
hire_date: date
entry_order: int
def __str__(self):
return f"{self.hire_date.strftime('%Y%m%d')}-{self.entry_order:03d}"
위의 정보를 통해서 사원번호는 들어온 날짜와 순서로 구성되어 있다는것을 알 수 있다.
EmployeeId
클래스는 @dataclass(frozen=True)
데코레이터를 사용하므로써 변경 할 수 없다는것을 알 수 있다. hire_date
는 date type으로 잘못된 date로 사원번호를 만들려고 할때 ValueError
가 발생한다.위의 2가지처럼 변경불가능한 사원번호의 특성과 date의 validation을 통해 데이터의 무결성이 보장된다. 만약 값객체 없이 string으로 사용을 했다면 사원번호를 사용할려는 곳마다 해당 로직을 구현해서 코드의 복잡도가 올라가게 될 것이다.
오늘 421번 버스를 타고 출근했다. 어떤 사람이 421버스에 타고 버스카드를 찍는 순간 이미 처처리된 카드입니다
라는 음성이 들렸다. 이 사람은 방금 421번 버스를 탔는데, 왜 이미 처리된 카드라고 나왔을까?
그 이유는 우리가 환승을 할때 버스는 값 객체로 취급되기 때문이다. 값 객체에서는 값이 같으면 같은 객체로 취급된다.
from datetime import datetime, timedelta
class BusBoardingInformation:
def __init__(self, card_id: int, last_get_off_time: datetime, bus_number: int):
self.card_id = card_id
self.last_get_off_time = last_get_off_time
self.bus_number = bus_number
def can_transfer(self, bus_number: int) -> bool:
current_time = datetime.utcnow()
time_since_last_get_off = current_time - self.last_get_off_time
time_threshold = timedelta(minutes=30)
if time_since_last_get_off < time_threshold:
return False
if self.bus_number == bus_number:
voice_message("이미 처처리된 카드입니다")
return False
return True
can_transfer
함수는 환승이 가능한 여부를 알려주고, 만약 같은 버스번호로 요청하면 voice_message("이미 처처리된 카드입니다")
를 호출하고 False
로 리턴한다. 환승 시스템에서 같은 번호의 버스는 같은 버스로 취급하기 때문이고, 그래서 값객체라고 할 수 있다.
값 객체(Value Object)는 시스템에서 고유한 값을 나타내는 데 사용되며, 원시 데이터 타입만 사용하여 소프트웨어를 개발할 수 있지만, 값 객체를 도입함으로써 코드의 표현력과 유지보수성을 향상시킬 수 있다. 이는 값 객체가 특정 도메인 규칙을 코드로 명확하게 정의할 수 있기 때문이다.