3.5.2 - 좋은 의도, 나쁜 결과
- getter와 setter는 캡슐화 원칙을 위반하기 위해 설계되었다.
- Java에서 getter와 setter는 클래스를 자료구조로 바꾸기 위해 도입되었다.
- 자바는 public 프로퍼티를 추가해야하는 어색한 상황을 피하기 위해 프로퍼티를 private 으로 변경하고 모든 프로퍼티에 getter와 setter를 추가하는 기능을 제공한다.
- IDE 설계자들은 getter와 setter 이용해서 private 프로퍼티를 감싸는 방식을 권장한다.
- 하지만 getter와 setter를 사용하면 OOP의 캡슐화 원칙을 손쉽게 위반하게 된다.
- 겉으로는 메서드처럼 보이지만 실제로는 우리가 데이터에 직접 접근하고 있다는 불쾌한 현실을 가리고 있을 뿐이다..
그래서 record가 나오지 않았을까 ! ?
3.5.3 - 접두사에 관한 모든 것
- getter/setter 안티 패턴에서 유해한 부분은 두 접두사인 get과 set이라는 사실이 중요하다.
- 이러한 접두사는 객체가 어떤 존중도 받을 가치가 없는 자료구조라는 사실을 전달하게 된다.
- 이러한 접두사를 붙이면 객체가 데이터를 노출하는 바이트 집합으로 다뤄질 것이라고 기대를 하게 된다.
- 객체는 대화를 원하지 않는다. 그저 우리가 어떤 데이터를 객체 안에 넣어주거나 다시 꺼내주기를 원할 뿐이다.
- 대화를 원하지 않는다….?
- 어떤 데이터를 반환하는 메서드를 포함하는 것은 괜찮다
- 하지만 이 메서드의 이름을 다음과 같이 짓는 것은 올바르지 않다
- 둘의 차이는 getDollars()는 데이터 중 dollars를 찾은 후 반환하세요 라고 이야기 하는 것이고 dollars()는 얼마나 많은 달러가 필요한가요? 라고 묻는 것이다.
- dollars는 객체를 데이터의 저장소로 취급하지 않고 객체를 존중한다. 사용자는 이 메서드를 통해 얼마나 많은 달러가 포함되어 있는지 알 수 없지만 이 값이 private 프로퍼티로 저장되어 있다고 가정하지는 않는다.
- 즉 내부 구조에 관해 어떤 것도 가정하지 않으며 결코 이 객체를 자료구조라고 생각하지 않는다.
결론은 getter와 setter가 OOP에서 안티패턴이라고 많이 하지만 과연 이런 규칙을 잘 지키면서 개발을 하는 곳이 있을까 하는 의문이 든다. getter와 setter를 편리하게 만들어주는 IDE나 롬복 같은 편리한 도구 덕분에 이 규칙은 계속 이어지지 않을까..? 생각이 드는 것 같습니다.
3.6 부 생성자 밖에서는 new를 사용하지 마세요
- 작고 구현한지 얼마 안된 애플리케이션에서는 현재 다루는 문제점이 명확하게 드러나지는 않지만 큰 규모의 시스템에서는 매우 중요하고 종종 치명적인 결과를 초래하기도 한다.
- euro() 메서드 안에서는 new 연산자를 이용해 Exchange 인스턴스를 생성하고 있다.
- 이 경우 클래스가 작고, 단순하거나, 값비싼 자원을 사용하지 않는다면 전혀 문제가 되지 않는다.
- 문제는 바로 “하드코딩된 의존성"이다. Cash 클래스는 Exchange 클래스에 직접 연결되어 있기 때문에 의존성을 끊기 위해서는 Cash 클래스 내부코드를 변경하는 방법밖에 없다.
- 클래스자 작다면 큰 문제는 아니지만 큰 규모에서 하드코딩된 의존성이 소프트웨어를 테스트하고 유지보수하기 어렵게 만든다.
- 문제의 근본은 new 연산자이다.
- 만약 메서드 내부에서 new 연산자를 사용하지 못하도록 만든다면?
- 수정된 코드는 생성자의 두번째 인자로 exchange 인스턴스를 전달해야 한다. Cash 클래스는 더이상 exchange 인스턴스를 직접 생성할 수 없게 되며 오직 생성자를 통해서 제공된 Exchange만 협력할 수 있게된다.
- Cash 클래스는 더이상 Exchange 클래스에 의존하지 않게 된다.
- … ? 의존은 하지만 의존성을 제어하는 주체가 Cash가 아니라 우리 자신이라는 점에서 차이가 있다.
- 즉 우리의 결정에 의지하고 우리가 제공하는 객체와 협력하게 된다.
- 객체가 필요한 의존성을 직접 생성하는 대신 우리가 생성자를 통해 협력할 객체를 주입시켜야 한다.
- 타입 인트로스펙션과 캐스팅을 사용하고 싶은 유혹에 빠지더라도 절대로 사용하지 말자.
- 기술적으로 Java의 Instanceof 연산자와 Class.cast() 메서드, 다른 언어에서 동일한 기능을 수행하는 연산자들이 모두 포함된다.
- 타입 인트로스펙션은 리플렉션 이라는 포괄적인 용어로 불리는 여러가지 기법 중 하나다.
- 리플렉션을 사용하면 메서드, 명령어, 구문, 클래스, 객체, 타입등을 변경할 수 있다.
- CPU가 이 요소들에 접근하기 전에 쉽고 간단하게 코드를 수정할 수 있게 된다.
- 리플렉션은 매우 강력한 기법이지만 동시에 코드를 유지보수하기 어렵게 만드는 기법이다.
- 코드가 런타임에 다른 코드에 의해 수정된다는 사실을 항상 기억해야 한다면 코드를 읽기 매우 어려울 것이다.
- 이 접근방법은 타입에 따라 객체를 차별하기 때문에 OOP의 기본 사상을 심각하게 훼손시킨다.
- 차별하지 말고 객체가 누구건 상관없이 자신의 일을 할 수 있도록 해야한다.
- 런타임에 객체의 타입을 조사하는 것은 클래스 사이의 결합도가 높아지기 때문에 기술적인 관점에서도 좋지 않다.
- 위의 예제에서 Iterable, Collection 이라는 두개의 인터페이스에 의존하고 있고 의존하는 대상이 늘어날수록 결합도는 높아지기 때문에 유지보스 측면에서 중요하지 않다.
부 생성자를 제외한 어떤 곳에서도 new를 사용하지 말자 이러한 규칙만 지킨다면 객체들은 상호간에 분리되고 테스트 용이성과 유지보수성을 크게 향상시킬 수 있다.
읽었던 내용중 잘 이해되고 의존성 주입을 다른 방식으로 이해할 수 있었던 부분이었던 것 같아서 괜찮았다. 부 생성자 부분도 괜찮게 읽었던 내용이였는데 두 방법은 충분히 적용해볼만 하다고 생각이 들었다.
3.7 인트로스펙션과 캐스팅을 피하세요
instanceof 연산자를 사용하거나 클래스를 캐스팅하는 일은 안티패턴이기 때문에 사용해서는 안된다.
내용이 조금 어렵고 생소해서 이해가 명확하게 되지는 않았던 부분인 것 같습니다…