22.01.14_[02DDD]값객체란?
본문 바로가기
DDD

22.01.14_[02DDD]값객체란?

by KyeongMin 2022. 1. 18.
728x90
반응형

1.값객체란?

  • v시스템 특유의 값을 표현 하기 위해 정의하는 객체를 값 객체
    • 프로그래밍 언어에는 원시 데이터 타입이 있음
    • 원시데이터 타입만 사용해 시스템을 개발할 수 있으나 때로는 특유의 값을 정의해야 할 때가 있음

1.1 원시데이터 값으로 성명 표현

var fullName = "naruse mananobu";
Console.WriteLine(fullName);// naruse mananobu 값이 출력됨
  • 성만 출력하고 싶거나, 성과 이름 순으로 출력하고 싶은 다양한 요구사항이 있는 경우에 아래와 같이 우선 성씨만 출력하는 경우이다.

1.2 이름 중 성씨만 출력

var fullName = "naruse mananobu";
var tokens = fullName.Split(' ');// ["naruse", "mananobu" ] 배열이됨
var lastName = tokens[0];
console.WriteLine(lastName);// naruse 가 출력됨
  • 위가 제대로 동작한다고 생각이 들 수 있지만 이름을 쓰는 관습에 따라서
    • 실제 성은 smith인데 성씨를 뒤에 쓰는 관습에 따라서 john smith라고 쓴다면
    • 위의 코드에 적용이 된다면 이름이 john이 출력이 되기 때문에 제대로 동작하지 않을 수 있음

2.이를 해결하기 위한 방법

  • 대개 객체지향 프로그래밍에서는 클래스를 사용함

2.1 클래스로 만들기

Class FullName
{
    public FullName (string firstName, string lastName)
    {
        FirstName = firstName;
        LastName = lastName;
    }
    
    public string FirstName { get; set; }
    public string LastName  { get; set; }
}

2.1.1 FullName 클래스의 LastName 프로퍼티 사용

var fullName = new FullName("masanobu", "naruse");
Console.WriteLine(fullName.LastName); // naruse가 출력
  • 시스템에서 어떤 처리를 해야 하는지에 따라 값을 나타내는 표현이 정해짐
  • FullName 클래스는 이 시스템의 필요에 맞는 성명을 나타내는 표현
    • 객체이기도 하고 동시에 값이기도함
      • 이를, 값 객체라고 함

3.값의 성질과 값 객체의 구현

  • 값에도 일정한 성질이 있음
    • 이 값의 성질이 값 객체를 이해하는 열쇠

3.1 값의 성질 대표적 세가지

  • 변하지 않음
  • 주고받을 수 있음
  • 등가성을 비교할 수 있음

3.2 값의 불변성

var greet = "안녕하세요";
Console.WriteLine(greet); // 안녕하세요 출력됨
greet = "Hello";
Console.WriteLine(greet); // Hello가 출력됨
  • 위에 처럼 값을 수정할 수 있다고 보편적으로 생각함
    • 하지만 우리가 값을 수정할 때는 새로운 값을 대입
    • 대입은 값을 수정하는 과정이 아님
      • 대입을 통해 수정되는 것은 변수의 내용이고, 값 자체가 수정되는것 아님

3.2.1 값을 수정하는 의사 코드

var greet = "안녕하세요";
greet.ChangeTo("Hello"); // 실제로는 이런 메소드 없음
Console.WriteLine(greet); // Hello가 출력됨
  • 이해가 안된다고 하더라도 위와 같은것이 허용이 되면 아래와 같은 경우도 허용한다는 소리
"안녕하세요".ChangeTo("Hello"); 
Console.WriteLine("안녕하세요"); // Hello가 출력됨
  • 이런것이 허용이 되면 개발자들은 혼란스러울 것
  • 즉, 위와 같이 값을 수정한다면 안심하고 값을 사용 할 수 없음
    • 결론, 1이라는 숫자가 갑자기 0이 되면 안되고, 1이라는 숫자는 항상 1이어야 하는 것처럼 값은 변하지 않는다는 것임

3.2.2 일반적으로 볼 수 있는 값 수정

var fullName = new FullName("masanobu", "naruse");
fullName.ChangeLastName("sato");
  • 대부분 개발자 입장에서는 자연스러움
    • 그러나, FullName 클래스를 값으로 간주할 경우 부자연스러움
  • FullName은 시스템 특유의 값을 표현하는 값 객체
    • FullName도 값이기도함
    • 그래서 변하지 않아야함
    • 즉, FullName 클래스에 값을 수정하는 기능을 제공하는 ChangeLastName같은 메서드가 정의되면 안됨

3.3 교환가능하다

  • 값은 불변일지라도 값을 수정할 필요는 있음

3.3.1 평소 값을 수정하는 방식

// 숫자값 수정
var num = 0;
num = 1;

// 문자값 수정
var c = '0';
c = 'b';

// 문자열값 수정
var greet = "안녕하세요";
greet = "hello";
  • 모두 변수에 값을 대입하는 코드
  • 즉, 대입문 자체가 값의 수정을 나타내는 방법
    • 값이든 값객체든 그 값자체를 수정하면 안되지만 대입문을 통해 교환의 형식으로 표현됨

3.3.2 값 객체를 수정하는 방법

var fullName = new FullName("masanobu", "naruse");
fullName = new FullName("masanobu", "sato");

3.4 등가성 비교 가능

  • 같은 종류의 값끼리는 비교할 수 있음
Console.WriteLine(0 == 0);
Console.WriteLine('a' == 'b');
Console.WriteLine( "hello" == "hello");
  • 표현식 0 == 0에서 좌변의 0과 우변의 0은 인스턴스로서는 별개의 존재이지만
    • 같은 값으로 취급됨
  • 값은 값 자신이 아니라 값을 구성하는 속성을 통해 비교된다는 점
  • 값 객체도 값 객체를 구성한느 속성(인스턴스 변수)을 통해 비교됨

3.4.1 값 객체 간의 비교

var nameA = new FullName("masanobu", "naruse");
var nameB = new FullName("masanobu", "naruse");

// 두 인스턴스를 비교
Console.WriteLine(nameA.equals(nameB)); // 인스턴스를 구성하는 속성이 같아서 true

3.4.2 속성값을 꺼내 직접 비교하기

var nameA = new FullName("masanobu", "naruse");
var nameB = new FullName("john", "smith");

var compareResult = nameA.FirstName == nameB.FirstName &&
                    nameA.LastName == nameB.LastName;
Console.WriteLine(compareResult);
  • 언뜻보면 자연스러운 코드 같지만 FullName 객체가 값이라는 사실을 생각하면 부자연스러운 코드임

속성을 꺼내 직접 비교하는 방식을 숫자에 적용한 코드

Console.WriteLine(1.Value == 0.Value); //false?
  • 위와 같은 코드는 본적이 없을 것 값의 값을 꺼낸다는 것은 자연스러운것이 아님
  • 값 객체는시스템 고유의 값임, 결국 값
    • 따라서, 값의 속성을 꺼내 비교하는 것이 아니라, 값과 마찬가지로 직접 값끼리 비교하는 방식이 자연스러움

3.4.3 값끼리 직접 비교하기

var nameA = new FullName("masanobu", "naruse");
var nameB = new FullName("jhon", "smith");

var compareResult = nameA.equals(nameB);
Console.WriteLine(compareResult);

// 연산자 오버라이드를 활용할 수도 있음
var compareResult2 = nameA == nameB;
Console.Write(compareResult2);
  • 위 처럼 자연스러운 코드를 사용하려면 값 객체를 비교하는 매서드를 제공해야 함

비교 메서드를 제공하는 FullName 클래스

class FullName : IEquatable<FullName>
{
    public FullName(string firstName, string lastName)
    {
        FirstName = firstName;
        LastName = lastName;
    }
    public string FirstName {get;}
    public string LastName {get;}
    
    public bool Equals(FullName other)
    {
        if(ReferenceEquals(null, other)) return false;
        if(ReferenceEquals(this, other)) return true;
        return string.Equals(FirstName, other.FirstName)
            && string.Equals(LastName, other.LastName);
    }
    
    public override bool Equals(object obj)
    {
        if(ReferenceEquals(null, obj)) return false;
        if(ReferenceEquals(this, obj)) return true;
        if(obj.GetType() != this.GetType()) return false;
        return Equals((FullName) obj);
    }
    // C#에서 Equals를 오버라이드하려면 GetHashCode를 함께 오버라이드해야 한다.
    public override int GetHashCode()
    {
        unchecked
        {
            return ((FirstName != null ? FirstName.GetHashCode() : 0) * 397)
                ^ (LastName != null ? LastName.GetHashCode() : 0);
		}
	}
}
  • 위의 코드가 C#에서 비교를 구현하는 전형적인 코드
  • 두 값 객체를 비교하려면 값 객체의 속성을 꺼내 비교하는 대신 Equals 메서드를 쓰면 됨
    • 위의 방법으로 값 객체도 값과 같은 방법으로 비교할 수 있음

객체를 비교하는 코드를 자연스럽게 만든다는 목적만으로 이만큼의 코드를 작성하는 게 찜찜할 수 있지만 위의 경우는 명확한 장점이 있음

  • 값 객체에 인스턴스 변수를 추가할 때 이 장점을 알 수 있음

4.속성을 추가해도 수정이 필요 없음

  • 성과 이름 사이에 미들네임이 있는 경우
  • 이 미들네임을 표현하기 위해 FullName 클래스에 속성을 추가해야 하는 상황이라면?
    • 값 객체에 비교를 위한 메서드가 없어서 코드에서 속성을 직접 꺼내 비교해야 할 경우, 새로 추가된 속성이 생겼을때 비교하는 코드를 모두 수정해야함

4.1 속성을 직접 비교할 경우 새로운 속성 추가하기

var compareResult = nameA.FirstName == nameB.FirstName && nameA.LastName == nameB.LastName && nameA==MiddleName == nameB.MiddleName;
// 위에 처럼 조건식이 추가됨
  • 한 곳의 경우 그냥 고칠 수 있지만 여러 곳인 경우는 수정이 어렵다
  • 그래서 값 객체에서 직접 비교 수단을 제공하면 단순하고 지루한 작업을 피할 수 있음
  • Before
class FullName : IEquatable<FullName>
{
... 
    public bool Equals(FullName other)
    {
        if(ReferenceEquals(null, other)) return false;
        if(ReferenceEquals(this, other)) return true;
        return string.Equals(FirstName, other.FirstName)
            && string.Equals(LastName, other.LastName);
    }    
   ...
}
  • After
class FullName : IEquatable<FullName>
{
... 
    public bool Equals(FullName other)
    {
        if(ReferenceEquals(null, other)) return false;
        if(ReferenceEquals(this, other)) return true;
        return string.Equals(FirstName, other.FirstName)
            && string.Equals(LastName, other.LastName)
            && string.Equals(MiddleName, other.MiddleName);// 이곳의 조건식만 추가하면 됨
    }    
   ...
}
  • 즉, 다른 새로운 속성이 추가되더라도
    • 수정할 곳은 Equals메서드 내부로 제한됨
    • 비교뿐만 아니라 값의 속성을 다루는 처리 역시 값객체에서 제공하게 하면 수정할 곳을 줄일 수 있음

5. 값 객체가 되기 위한 기준

  • FullName 클래스를 구성하는 firstName이나 lastName등의 속성은 값 객체가 아니라 원시타입인 문자열로 정의돼 있음
  • 가능한 모든 속성을 값 객체로 만든 FullName클래스
class FullName : IEquatable<FullName>
{
    private readonly FirstName firstName;
    private readonly LastName lastName;
    
    public FullName(FirstName firstName, LastName lastName){
        this.firstName = firstName;
        this.lastName = lastName;
    }
    //(생략)
}
  • 생성자 메서드에 전달되는 인자는 값 객체
    • 이름을 나타내는 값 객체
    • class FullName : IEquatable<FullName>
      {
      ... 
          public bool Equals(FullName other)
          {
              if(ReferenceEquals(null, other)) return false;
              if(ReferenceEquals(this, other)) return true;
              return string.Equals(FirstName, other.FirstName)
                  && string.Equals(LastName, other.LastName);
          }    
         ...
      }
    • 성을 나타내는 값 객체
    • class LastName
      {
      	private readonly string value;
          
          public LastName(string value)
          {
              if(stirng.IsNullOrEmpty(value)) throw new ArgumentException("최소 1글자 이상 이어야함", nameof(value));
              
              this.value = value;
          }
      }
  • 이책에서 개인적인 기준
    • 도메인 모델로 선정되지 못한 개념을 값 객체로 정의해야 할 지에 대한 기준으로
      • 규칙이 존재하는가?
      • 낱개로 다루어야 하는가?
        • 두개를 중점으로 생각
    • 성명을 예로 들면
      • 규칙
        • 성과 이름으로 구성됨
      • 낱개
        • 앞서 본문에서 봤듯이 낱개로 다뤄지는 정보
      • 즉, 위의 상황으로 봐서 성명은 값 객체로 정의 해야 할 개념이 됨
    • 성 혹은 이름
      • 현재라면 값 객체로 만들지 않을것
      • 단, 전제를 바꿔 성과 이름에서 사용가능한 문자에 제약이 있으면?
        • 결론적으로 값 객체로 정의하지 않고도 규칙을 제 할 수 있음
    class FullName : IEquatable<FullName>
    {
        private readonly FirstName firstName;
        private readonly LastName lastName;
        
        public FullName(string firstName, string lastName)
        {
            if(firstName == null) throw new ArgumentNullException(nameof(firstName));
            if(lastName == null) throw new ArgumentNullException(nameof(lastName));
            if(!ValidateName(firstName)) throw new ArgumentException("허가되지 않은 문자가 사용됨", nameof(firstName));
            if(!ValidateName(lastName)) throw new ArgumentException("허가되지 않은 문자가 사용됨", nameof(lastName));
            
            this.firstName = firstName;
            this.lastName = lastName;
    	}
        
        private bool ValidateName(string value)
        {
            //사용가능한 문자를 알파벳으로 제한
            return Regex.IsMatch(value, @"^[a-ZA-Z]+$");
    	}
        //(생략)
    }
    • 위에 처럼FullName 클래스속성이 원시타입이어도 인자를 전달받은 시점에 검사를 하면 규칙을 강제할 수 있음
    • 값객체로 정의해도 문제는 없음
      • 만약 값객체로 만들기로 했다면, 성과 이름을 별도의 타입으로 나눌지 말지 선택
      • 따로 다룰 필요가 없는 경우 하나의 타입으로 다룰 수 있음
    • 이름을 나타내는 클래스
    • class Name
      {
          private readonly string value;
          
          public Name(string value)
          {
              if(value == null) throw new ArgumentNullException(nameof(value));
              if(!Regex.IsMatch(value, @"^[a-zA-Z]+$")) throw new ArgumentException("허가 되지 않은 문자가 사용됨", nameof(value));
              
              this.value = value;
          }
      }
    • Name클래스를 이용해 구현한 FullName 클래스
    • class FullName
      {
          private readonly Name firstName;
          private readonly Name lastName;
          
          public FullName(Name firstName, Name lastName)
          {
              if(firstName == null) throw new ArgumentNullException(nameof(firstName));
              if(lastName == null) throw new ArgumentNullException(nameof(lastName));
              
              this.firstName = firstName;
              this.lastName = lastName;
              //(생략)
          }
      }

6. 행동이 정의된 값 객체

  • 값 객체에서 중요한 점 중 하나는 독자적인 행위를 정의할 수 있다는 점
  • 돈에는 액수와 화폐 단위 2가지 속성
    • 값 객체는 데이터만을 저장하는 컨테이너가 아니라 행동을 가질 수도 있는 객체
  • class Money
    {
        private readonly decimal amount;
        private readonly string currency;
        
        this.amount = amount;
        this.currency = currency;
    }

6.1 금액을 더하는 처리를 구현하기

class Money
{
  private readonly decimal amount;
  private readonly string currency;
  
  //(생략)
  
  public Money Add(Money arg){
    if(arg == null) throw new ArgumentNullException(nameof(arg));
    if(current != arg.currency) throw new ArgumentException($"화폐 단위가 다름(this:{currency}, arg:{arg.currency})");
    
    return new Money(amount + arg.amount, currency);
  }
}
  • 돈을 더하려면 화폐 단위가 일치해야함
    • 그러므로 화폐 단위가 같은지 확인 해야함
    • 또 값 객체는 불편이므로 계산된 결과는 새로운 인스턴스로 반환

6.2 계산 결과 받기

var myMoney = new Money(1000, "KRW");
var allowance = new Money(3000, "KRW");
var result = myMoney.Add(allowance);

6.2.1 원시 타입끼리의 덧셈

var myMoney = 1000m;
var allowance = 3000m;
var result = myMoney + allowance;

6.2.2 화폐 단위가 일치하지 않으면 예외를 발생

var krw = new Money(1000, "KRW");
var usd = new Money(10, "USD":);
var result = jpy.Add(usd);// 예외 발생
  • 버그는 착각에서 발생
  • 값 객체는 결코 데이터를 담는 것만이 목적인 구조체가 아님
    • 값 객체는 데이터와 더불어 그 데이터에 대한 행동을 한곳에 모아둠으로써 자신만의 규칙을 갖는 도메인 객체가 됨

6.2.3 정의되지 않았기 때문에 알 수 있는 것

  • 즉, 객체에 정의된 행위를 통해 이 객체가 어떤 일을 할 수 있는지 알 수 있음
  • 반대로, 객체는 자신에게 정의 되지 않은 행위는 할 수 없다는 말도 됨
    • 돈을 나타내는 값 객체끼리 덧셈을 할 수 있지만, 곱셈은 불가능
  • 돈에 곱셈이 필요한 경우라면 금리를 계산할 때 정도?
    • 금리를 계산하기 위한 시그니처라면 아래와 같은 코드를 생각해 볼 수 있음
class Money
{
  //(생각)
    
    public Money Multiply(Rate rate);
    // public Money Multiply(Money money)은 정의하지 않음
}
  • 돈 객체끼리의 곱셈은 정의되지 않았으므로 묵시적으로 곱셈이 가능하지 않음을 알 수 있음
  • 의미랑 존재가 명확하다는 뜻

7.값 객체를 도입했을 때의 장점

  • 당연하지만 시스템 고유의 값을 객체로 나타내면 그만큼 정의하는 클래스의 수도 늘어남
  • 값 객체를 도입하려면 여기에 따르는 심리적 장애물을 넘어야함

7.1 값 객체의 장점

  • 표현력이 증가
  • 무결성이 유지
  • 잘못된 대입을 방지
  • 로직이 코드 이곳저곳에 흩어지는 것을 방지
    • 위 네가지는 모두 간단하지만 시스템을 보호하는 데느 크게 도움이 됨

7.2 표현력의 증가

  • 식별을 위한 다양한 번호인 로트번호나 일련번호, 제품번호 등 숫자만으로 구성되기도 하고, 알파벳이 섞인 문자열 형태도 있음
  • 제품 번호를 원시 타입으로 나타낸 프로그램은 어떨까?
var modelNumber = "a20421-100-1";
  • 위의 modelNumber는 원시타입인 문자열 타입의 변수임
  • 근데 그냥 보게 되면 제품번호의 내용이 어떤 것인지 예측이 어려움
void Method(string modelNumber)
{
...
}
  • 위에 처럼 타입이 문자열이라는 것만 알 수 있음
  • 제품번호의 내용을 알려면 modelNumber변수가 어디서 만들어져 어떻게 전달됐는지 따라가야함

7.2.1 제품번호를 나타내는 값 객체

class ModelNumber
{
  private readonly string productCode;
  private readonly string branch;
  private readonly string lot;
  
  public ModelNumber(string productCode, string branch, string lot)
  {
    if(productCode == null) throw new ArgumentNullException(nameof(productCode));
    if(branch == null) throw new ArgumentNullException(nameof(branch));
    if(lot == null) throw new ArgumentNullException(nameof(lot));
    
    this.productCode = productCode;
    this.branch = branch;
    this.lot = lot;  
  }
  
  public override string ToString()
  {
    return productCode + "-" + branch + "-" + lot;
  }
}
  • 위와 같이 클래스를 정의하면 제품 번호가 제품코드, 지점번호, 로트번호로 구성됨 알 수 있음
  • 그냥 문자열만 제공한것 보다는 큰 진보임
  • 값 객체는 자기 정의를 통해 자신이 무엇인지에 대한 정보를 제공하는 자기 문서화를 도움

7.3 무결성의 유지

  • 시스템에는 각 값이 준수해야 할 규칙이 있음
  • 사용자명을 예로 들어보자.
    • 간단히 말하면 문자열임
    • 그러나, 시스템에 따라서 N글자 이상 M글자 이하와 같은 제한이나 알파벳 문자만 포함할 것 같은 규칙이 있을 수 있음

7.3.1 유효하지 않은 값

  • 효과가 늦게 나타나는 독과 같이 다루기 까다로운 상태
  • 유효하지 않은 값을 사용할 때 항상 값이 유효한지 확인을 거쳐야함
if(userName.Length >=3)
{
    // 유효한 값이므로 처리를 계속
}
else
{
    throw new Exception("유효하지 않은 값");
}
  • 값의 유효성을 매번 확인하면 급한 불은 끌 수 있지만 유효성 검사 코드가 반복됨
  • 값 객체를 잘 이용하며 유효하지 않은 값을 처음부터 방지 할 수 있음

7.3.2 사용자명을 나타내는 값 객체

class UserName
{
    private readonly string value;
    
    public UserName(string userName)
    {
        if (value == null) throw new ArgumentNullException(nameof(value));
        if (value.Length < 3) throw new ArgumentNullException("사용자명은 3글자 이상이어야함");
       this.value = value;       
    }
}
  • 위와 같은 방법으로 시스템상 유효하지 않은 값은 이런 방식의 확인을 거쳐 허용하지 않는 것
  • 결과적으로 규칙을 위반하는 유효하지 않은 값을 걱정할 필요가 없음

8. 잘못된 대입 방지하기

User CreateUser(string name)
{
    var user = new User();
    user.Id = name;
    return user;
}
  • 위의 경우 실행에는 문제가 없겠지만, 올바른 코드라고 할 수 없음
  • 사용자명이 그대로 ID인 경우는 올바른 코드라고 볼 수 있음
  • 하지만 , 이메일 주소처럼 별도의 값을 ID로 삼는 경우는 바르지 못한 코드가됨
  • 코드가 올바른지 아닌지를 판단하기 위해 관계자의 기억이나 문서를 뒤지는 것보다는
    • 자기 문서화의 힘을 빌리는 것이 바람직
    • 값객체는 이를 실현할 수 있음

8.1 값 객체로 코드 판별

  • 사용자 ID를 나타내는 값 객체
class UserId
{
    private readonly string value;
    
    public UserId(string value)
    {
        if (value == null) throw new ArgumentNullException(nameof(value));
        this.value = value;
    }
}
  • 사용자명을 나타내는 값 객체
class UserName
{
    private readonly string value;
    
    public UserName(string value)
    {
        if(value == null) throw new ArgumentNullException(nameof(value));
        this.value = value;
	}
}
  • 현재 위의 두 클래스는 각각의 원시 타입인 문자열을 래핑한 단순한 객체

8.1.1 값객체로 수정

class User
{
    public UserId Id {get; set;}
    public UserName Name {get; set;}
}
  • 위와 같이 값객체를 사용하도록 User클래스 수정하고
  • 함수의 인자로 문자열이 아닌 UserName객체를 전달받게하자
User CreateUser(UserName name)
{
    var user = new User();
    user.Id = name; // 컴파일 에러
    return user;
}
  • User 클래스의 Id 속성은 UserId타입의 객체
    • 여기에 대입을 시도한 name은 UserName타입의 변수
    • 컴파일러가 타입 불일치를 발견하고 에러 발생함
  • 즉, 위와 같이 값 객체를 정의해 타입 시스템에 의존하면 예측하기 어려운 에러가 숨은 곳을 줄일 수 있음
    • 정적 타이핑 프로그래밍 언어에 사용하면 좋음
    • 타입 헌팅 기능을 통해 IDE가 에러를 잡을 수 있음

9. 로직을 한곳에 모아두기

  • DRY원칙처럼 코드 중복을 방지하는 일이 매우 중요
    • 중복 코드 많아지면 코드를 수정하는 것이 어려움
  • 단순하다면 상관 없지만 이외 사용자 정보를 수정하는 처리가 있다면?
    • 사용자며의 최소 길이가 변경이 되는 상황
      • 우선 사용자를 신규 생성하는 리스트 부분이 있는 곳을 찾아서 고치게된다. 그것이 2개라면 2개를 찾아가서 고치게되는 경우가 된다.
  • void UpdateUser(string id, string name)
    {
        if(name == null) throw new ArgumentNullException(nameof(name));
        if(name.Length < 3) throw new ArgumentException("사용자명은 3글자 이상이어야 함", nameof(name));
        //생략
    }

9.1 값 객체를 적용하여 한곳만 수정

class UserName
{
    private readonly string value;
    public UserName (string value)
    {
        if(value == null) throw new ArgumentNullException(nameof(value));
        if(name.Length < 3)throw new ArgumentNullException("사용자명은 3글자 이상이어야 함", nameof(name));
        this.value = value;
    }
}
  • 위를 이용해서 개선해보면
void Create(stirng name)
{
    var userName = new UserName(name);
    var user = new User(userName);
    //생략
}
void UpdateUser(string id, string name)
{
    var userName = new UserName(name);
    //생략
}
  • 저렇게 따로 값객체를 만들어서 코드의 중복을 줄일 수 있고, 수정시 용이하다.

10.정리

  • 값객체 개념은
    • 시스템 고유의 값을 만드는 단순한 것
    • 원시타입으로 만들수 있지만 지나치게 범용적이기 때문에 아무래도 표현력이 부족
  • 도메인에는 다양한 규칙이 포함
    • 값객체를 정의하면 이러한 규칙을 값 객체 안에 기술해 코드 자체가 문서의 역할함
  • 값객체는 도메인 지식을 코드로 녹여내는 도메인 주도 설계의 기본 패턴
    • 도메인 개념을 정의할 때는 우선 값 객체에 개념인지 검토해 보기

https://github.com/3DPIT/study/blob/master/02.studyData/07.DDD/02%EA%B0%92%EA%B0%9D%EC%B2%B4/22.01.14_%EA%B0%92%EA%B0%9D%EC%B2%B4%EB%9E%80.md

 

GitHub - 3DPIT/study

Contribute to 3DPIT/study development by creating an account on GitHub.

github.com

 

728x90
반응형

댓글