정적 팩터리 메서드
일반적인 객체지향 프로그래밍 언어들은 객체를 생성할 때 객체의 상태를 초기화할 수 있는 constructor(이하, 생성자)
인 특별한 메서드가 존재한다. 각 언어마다 의미하는 키워드는 다르지만, 그 의미는 모두 동일한다.
이 책에서 소개하는 정적 팩터리 메서드를 권장하는 이유를 요약하면 다음과 같다.
- 이름을 가질 수 있다.
- 생성할 인스턴스를 제어할 수 있다.
위 이유로 알 수 있듯 기본 생성자는 언어 레벨에서의 제한과 생성할 객체의 상태는 제어할 수 있지만, 인스턴스 자체를 제어할 수 없다. 때문에 상태에 따라 인스턴스를 생성하기 위해서는 constructor overloading
하거나, 클라이언트 측에서 생성된 객체에 대한 상태를 setter
를 통해 변경해야한다.
하지만 두 방법 모두 좋은 방법은 아니다. 전자는 복잡한 상태를 가진 객체의 경우 많은 생성자로 인해 어떤 생성자에 의해 생성될지 예측하기 어렵고, 또 상태는 다르지만 필요한 인자가 동일한 경우는 언어적 제한으로 인해 생성이 불가능하다.
따라서 이 책에서 제안하는 정적 팩터리 메서드
는 좋은 대안책이 될 수 있다.
정적 팩터리 메서드는 _이름을 지정할 수 있으며, 여러 개 만들 수 있고 매개변수의 순서가 동일하더라도 별개의 객체 생성 행위를 구현_할 수 있다.
반면, 책에서는 단점도 소개하는데 책에서도 설명했듯이 이는 큰 단점이 아니라, 이런 점도 존재하니 유념해~ 정도의 느낌이다.
개인적인 느낌으로 객체의 생성자는 private 상태로 존재하며, 객체의 상태를 검증하는 용도로 사용하면 어떨까 라는 생각이 든다. 언어적 차원에서 제공하는 생성자는 객체가 생성될 때 반드시 1회 생성되는 것을 보장하기 때문에 앞서 설명한 정적 팩터리 메서드와 함께, 공통적인 객체의 상태를 검증하는 로직은 생성자에서 구현하는 것도 좋은 선택인 것 같다.
빌더 패턴
앞서 객체의 생성에 초점이 되어있다면, 이번 장은 객체 생성 시 필요한 파라미터를 관리하는 방법을 제안한다.
객체를 생성할 때 상태값은 대부분 외부에 의해 전달된다. 만약 객체가 가진 상태가 많다면 생성자 또는 정적 팩터리 메서드는 각 상황에 맞는 overloading된 생성자, 팩터리 메서드를 각 상황에 맞게 만들어야 한다.
이런 요구사항을 만족하는 패턴으로 점층적 생성자 패턴
또는 자바빈즈 패턴
을 예시로 소개하는데, 두 방법 모두 좋은 해결책은 아니다.
먼저, 점층적 생성자 패턴은 객체의 상태가 복잡해질수록 생성자가 기하급수적으로 늘어나게 되고, 클라이언트는 생성자의 매개변수 순서를 기억해야 사용이 가능한 단점과, 또 순서가 틀려도 컴파일 타임에 파악이 불가능하다.
두 번째로 소개된 자바빈즈 패턴은 객체 생성 후 setter
를 통해 상태를 변경하는 것인데, 이 방법의 문제점은 인스턴스 자체가 불완전 상태로 존재하게 될 수 있기 때문이다.
불완전 상태는, 객체 생성 후 인스턴스화된 객체가 클라이언트의 실수 또는 도메인이 완전히 파악되지 않은 경우 객체의 상태가 적절하지 않은 상태로 존재할 수 있다는 의미이다.
이런 문제점을 해결하고나, 최근 나온 언어들은 named parameter
라는 지원하는데, 미지원하는 언어들은 이번 장에서 소개한 빌더 패턴을 통해 해결할 수 있다.
하지만, 이번 장에서 크게 공감할 수 없던 부분은 너무 많은 상태를 가진 경우 객체의 역할이 잘못된 경우일 것이고, 빌더 패턴 또한, 객체의 불완전 상태는 작업할 개발자가 관련 도메인을 이해하지 못하면 도메인적으로 불완전 상태가 발생할 수 있을 것 같다.
빌더 패턴을 잘 활용하기 위해선 정책 팩터리 메서드와 함께 도메인에 적합한 상태의 빌더 객체를 생성하는 방법이 떠오른다. 내가 생각하는 핵심은 빌더 패턴을 사용할 경우에 클라이언트가 직접 빌더를 제어할 수 없도록 하는 것이 좋아보인다.
합성
이번 장에서 소개하는 핵심 단어는 객체의 합성
이라 할 수 있다. 명시적인 것에 의존하게 되면 클래스는 의존된 클래스와 강하게 결합된다. 이는 Software(이하, 소프트웨어)
가 더 이상 유연한 무언가가 아니게 만드는 행위다. 우리는 hardware같이 설계된 회로가 만들어진 후 회로 자체를 변경할 수 없는 것과 반대로 소프트웨어는 코드를 수정하고 컴파일하면 된다는 의미로 유연함을 생각하면 안된다.
그런 의미에서 상속은 회로와 같은 단단함을 가지게 된다. 이 장에서 설명하는 합성은 추상화에 의존해 외부에서 주입하는, 우리가 너무나도 잘 아는 _DI(Dependency injection)_을 달성하는 것이 소프트웨어에서 어떤 강점이 있는지 소개한다.
자원 관리
우리 만드는 객체의 인스턴스는 Heap memory에 올라가며, memory는 우리가 지불할 수 있는 속도와 크기를 따졌을 때 가장 가성비가 좋은 영역이지만, 공짜가 아니다.
이번 장에서는 Java라는 언어적 시스템과 개발자의 실수로 발생하는 자원 낭비에 대해서 소개해준다.
특정 상황에 Java는 값을 객체로 다루기 위해 자동으로 Primitive type을 Objective type로 변경해준다. 이를 Auto boxing이라하며, 과정이 코드로 들어나지 않지만, Boxing/Unboxing 과정에 의해 예상하지 못한 부하 또는 메모리 누수가 발생한다.
이런 과정 외에도 책에서는 동일한 객체에 대해서 불필요한 객체 생성에 대한 예를 소개해준다.
좋은 예시로 앞서 학습한 정적 팩터리 메소드인 Boolean.valueOf
를 들 수 있다. 우리는 앞서 정적 팩터리 메서드를 통해 객체 생성에 대한 제어를 가져올 수 있었다. 따라서 Boolean.valueOf는 true/false에 대한 값에 대해 매번 새로운 인스턴스가 아니라, 동일한 인스턴스를 제공함으로서 성능을 확보할 수 있는 좋은 예시라 할 수 있다.
같은 맥락으로 다음 장은 할당된 자원에 대한 소유권을 관리하라는 것이다. 책에서는 다 쓴 객체 참조를 해제하라
라는 목차로 이 장을 더 잘 이해하기 위해서는 JVM에 존재하는 GC를 이해해보자.
'공부' 카테고리의 다른 글
클래스와 인터페이스 (0) | 2023.11.10 |
---|---|
모든 객체의 공통 메서드 (0) | 2023.11.10 |
Github 계정 GPG 등록하기 (0) | 2022.10.29 |
수강 하면 기록하는 명령어 (0) | 2022.09.18 |
CI/CD 공부 (0) | 2022.09.17 |