java 생성자와 객체의 완전한 상태

디자인패턴 2020. 11. 27. 00:58

 lombok에 빌더 어노테이션에 대해 생각해 보다가, 문득 builder를 왜 쓰는지가 궁금했습니다. 디자인 패턴에 대해서 하나도 모르는 저는 빌더 패턴을 3편에서 4편 정도를 쓸 듯 싶습니다. 먼저, '완전한 상태'에 대해서 생각해 보겠습니다.

 


 age와 name으로 이루어진 모델이 하나 있습니다. 그리고, 이 오브젝트는 생성이 되면 값이 변경되지 않습니다.

 

 

 getter와 setter가 있습니다. 그리고 Main 함수를 보겠습니다.

 

 

 모델의 나이를 set하고, name을 set 합니다. Model 클래스에서 이름과 age가 필수 항목이라고 할 때, 모델 객체만 만든 상태는 완전한 상태인지 생각해 보겠습니다.

 

 

 그렇지 않습니다. 이 상태에서 뭔가 작업을 한다고 하면 문제가 생길 듯 해 보입니다. 그래서 4번째 줄에서 나이를 20으로 설정했습니다. 그러면 나이에는 20이라는 값이 들어갈 겁니다.

 

 

 그런데, 이름이 없습니다. 여전히 불완전한 상태입니다.

 

 

 5번째 줄의 setName 메서드를 수행하면 완전한 상태가 됩니다. 여기서 보셔야 할 부분은 '불완전한 상태'입니다. age나 name이 필수로 채워져야 했습니다. 그럼에도 불구하고 처음에 생성될 때 채워지지 않았다는 것이 핵심입니다.

 


 바뀐 모델 클래스입니다.

 

 age와 name 앞에 final을 붙였습니다. 이것은, 상수화를 시켜줍니다. 생성자에서 초기화 될 때만 필드값을 설정할 수 있습니다. 그리고, 생성자는 age와 name을 받게 되어 있습니다. 이 상황을 그림으로 그려보면 아래와 같습니다.

 

 

 한 번 객체가 생성될 때, age와 name을 넣어버립니다. 최소한 객체가 생성되었을 때, 나이가 없거나 이름이 없는 불완전한 상태는 나타나지 않습니다. 이펙티브 자바에서 말하는 '일관성'이란, 이 부분을 의미합니다. 그리고, 이름을 String으로 받았습니다. 불변 객체 중 하나입니다. 굳이 StringBuffer나, StringBuilder를 쓸 필요가 없습니다. name이 바뀔 일이 없다면요.

 

 

 Model이 이런 식으로 되어 있다고 해 보겠습니다. name을 String이 아닌 Builder로 받았는데요.

 

 

 이 코드를 실행하면 어떤 결과가 나올까요? Model은 20과 chogahui라는 결과를 뱉을까요?

 

 

 chogahui05를 뱉었습니다. 이는 StringBuilder가 불변 객체가 아니기 때문입니다.

 

 

 단지, 참조값이 바뀌지 않았을 뿐입니다. 그 안에 있는 내용은 바뀔 수 있습니다.

 

 

 String은 그렇지 않습니다. 그 점을 주의깊게 보시면 됩니다. 글에서 심심찮게 언급이 되는 것이었고, 이 글에도 언급이 되었으니, 중요하다고 해도 과언이 아닌 듯 싶습니다. name과 age를 생성자로 받게 하면, 생성할 때 필수 항목인 이름과 나이 2개를 채우게 되므로, 생성이 끝난 후에는 더 이상 불완전한 상태가 아니게 됩니다.

 

 


 그런데 여전히 문제가 있습니다.

 

 나이를 -2147483648로, name을 null로 설정하면 어떨까요? age와 name 둘 다 set이 되었기 때문에 불완전하지는 않습니다.

 

 

 그런데, 이렇게 되면 있을 수 없는 상태입니다. 어떻게 나이가 음수가 될 수가 있고, 이름이 정해지지 않을 수 있나요? age와 name을 생성자에서 set 했을 뿐이지 유효성에 대한 검사는 하지 않았기 때문입니다. 어떻게 하면 좋을까요? 그러한 처리를 하는 예를 String 클래스에서 어렵지 않게 찾아볼 수 있습니다.

 

 

 codepoint와, offset, count를 넘기는 String 생성자는 코드 포인트 배열을 가지고, String을 생성합니다. 그런데, 99999900번의 코드 포인트를 가지는 문자는 없습니다. 이것은 invalid한 매개 변수입니다. 생성자에서 이러한 상태의 코드 포인트가 들어오는 것은, 정상적인 상황이 아닌 듯 합니다.

 

 

 그러니, 이런 상황에서는 비정상적인 Argument가 들어왔다고 예외를 날려버립니다.

 

 

 실제로, 정상적이지 않은 코드 포인트를 날리면 위와 같은 예외를 뱉어냅니다. 똑같이 구현해 봅시다. 이제 null이 들어오지 말아야 할 필드에 null이 들어오는 게 문제인데요. not null이여야 하는 필드에 null이 들어오는 경우에 어떻게 처리하면 좋을까요?

 

 

 String의 또 다른 생성자를 보면 null이 되면 안 되는 필드에 대해서,  null 값이 들어오면 어떻게 처리하는지가 424번째 줄에 나와 있습니다. Model 생성자에서는 어떻게 처리하면 될까요? if문으로 name이 null이면 예외를 던져주고, 나이가 유효한 값이 아니라면 예외를 던져주는 식으로 처리하면 됩니다. 굳이 catch를 할 필요는 없어 보입니다.

 

 age에 부적절한 인수가 들어왔을 때 IllegalArgument 예외를 날려주고, name 필드에 null 값이 들어왔을 때 NullPointer 예외를 걸어주면 되겠네요.

 

 

 잘못된 인자가 들어오면, 이에 대해서도 처리를 해 주네요. 문제는, 생성이 될 때 불완전한 상태는 어느 정도 해결이 된 듯 해 보였습니다만, 직관적이지 않다는 것입니다. 여기서 인자가 나이 말고도, 키, 몸무게 같은 것이 들어간다면 어떨까요? 키를 넣어야 하는데, 나이를 넣을 수도 있는 헷갈리는 상황이 되어 버립니다.

 

 이런 문제들은 어떻게 해결하면 좋을까요? 인텔리 제이 다음 시간에 알아보도록 하겠습니다.