Java는 보수적이야
10년 전에 자바를 공부하면서 이해가 안 되었던 지점들이 지금 와서는 이해가 되는 부분들이 있다. getter/setter를 쓰는 거나 생성자를 잘 안 쓰는 점들이다. 그 때는 괜히 타자를 많이 치게 만든다고 생각했었다. 비슷한 시기에 웹 개발을 공부하면서 루비를 봤었는데, 루비는 자바에 비해 간결하다고 느껴져서 좋아했었다.
이전 글에서도 이야기 했듯이, 자바에서는 한 번 노출한 인터페이스를 다음 버전에서도 잘 지키는 게 중요하다. 따라서 외부에 노출시킨 인터페이스를 수정하지 않는 상태에서 구현을 쉽게 바꿀 수 있는 방법이 발달한 거 같다. 이 기준을 바탕으로 생각하면 여러 디자인 선택들을 쉽게 이해할 수 있다.
생성자를 잘 안 쓰는 이유
Effective Java의 첫 번째 아이템이 “생성자 대신 정적 팩토리 메서드를 고려하다”이다. 생성자는 정적 팩토리 메서드에 비해 구현에 의존적이다. 생성자는 딱 그 타입만 만들 수 있지만, 정적 팩토리 메서드는 자식 타입을 대신 리턴할 수 있다. 다양한 구현을 만들고 쉽게 바꿔칠 수 있다.
내가 라이브러리를 만들어서 노출했을 때 생성자를 노출했다면 앞으로 해당 타입에 모든 기능을 집어넣어야 한다. 하지만 정적 팩토리 메서드는 여러 타입을 구현하고, 그 중 하나를 골라서 리턴하는 식으로 확장이 가능하다.
Dependency injection
디펜던시 인젝션이 필요한 이유도 같은 맥락에서 이해할 수 있다. 객체를 생성하는 건 기능에 비해 자주 바뀔 수 있다. 디펜던시 인젝션을 통해 객체의 생성을 외부로 빼내면 해당 디펜던시의 구현을 쉽게 바꿀 수 있다.
getter/setter
getter/setter를 안 쓰고 필드를 노출시키면 어떻게 될까. 클래스의 동작을 바꾸다 보면 내부 필드의 타입을 바꿀 일이 있다. 예를 들어 Person 클래스의 name 필드를 처음엔 String으로 관리하다가 나중엔 First name과 Last name으로 분리할 수도 있다. 이 때 name 필드를 노출했다면 이 라이브러리를 사용하던 코드가 깨지게 된다. 하지만 getter/setter를 쓴다면 이전 인터페이스를 깨지 않고 구현을 바꿀 수 있다.
상속에 대한 다른 관점
외부로 노출시킨 인터페이스를 꾸준히 잘 지키는 게 중요하다. 이걸 반대로 생각하면, 외부로 노출시킨 인터페이스만 그대로 지켜주면, 그 내부 구현이 어떻게 바뀌어도 상관없다고도 볼 수 있다. 자바에서 유명한 DB 라이브러리인 Hibernate나 Spring Data JPA를 쓰다 보면 내가 만든 타입이 나도 몰래 상속되어서 구현이 바뀌는 상황을 볼 수 있다.
이 재밌는 상황에 대해서는 다음 글에서 이어 이야기하겠다.