-
15. 클래스와 멤버의 접근 권한을 최소화하라
-
16. public 클래스에서는 접근자 메서드(=getter)를 사용하자
-
17. 객체를 최대한 immutable로 두자 private final!
-
18. 상속보다는 구성을 사용하자
-
19. 상속을 고려해 설계/문서화하고, 그렇지 않았다면 상속 금지
-
20. 추상 클래스 < 인터페이스
-
21. 인터페이스는 구현할 쪽을 생각하며 설계
-
22. 인터페이스는 타입을 정의하는 용도로만 사용. 상수 버킷 X
-
23. 태그달린 클래스 대신 계층구조를 택하자
-
24. 멤버 클래스는 되도록 static으로 만들자
-
25. .java파일 하나에는 하나의 public class만 담아 컴파일 오류를 없애자
-
독후감
객체지향과 관련된 자바 기능들과 주의해야 할 점을 모아놓은 챕터입니다. [오브젝트]를 미리 잃었기 때문에(ㅎㅎ~) 아는 얘기가 대부분이었지만, 이펙티브 자바책 자체에서 정리하는 것과 추가로 알게된 내용이 있어 정리합니다!
15. 클래스와 멤버의 접근 권한을 최소화하라
- SRP에 따른 클래스의 목적 이유인 퍼블릭 API를 제외하고는 대부분의 메소드와 필드값(필드값은 상수를 제외하고 전부)들의 접근 권한을 최소화해야한다. 대부분 private으로 두는 것이 상책이다
- 내부 구현의 구체성을 숨김으로써 API 중심 개발, 단순성 향상, 변화에 유연한 클래스 관계를 달성할 수 있다
- public 가변 필드를 가질 경우 thread safe하지 않으며, 사이드 이펙트 때문에 예상치 못한 프로그램 동작이 일어날 수 있다
16. public 클래스에서는 접근자 메서드(=getter)를 사용하자
17. 객체를 최대한 immutable로 두자 private final!
- 특별한 이유가 없다면 모든 클래스는 확장을 막고(final class), setter를 없애고, 모든 필드를 private final로 두어 필드 값을 변경할 수 없는 "불변 클래스"로 두는 것이 좋다. 불변으로 만들 수 없어도 변경할 수 있는 부분을 최소화하자
- 생성자는 불변식의 모든 조건을 만족한 "완전한"상태의 객체를 반환하는 것이 좋다
- 프로그램의 단순성을 높이고, thread-safe하여 스레드간 공유 가능한 객체를 만들어 객체의 추가 생성 없이 이용할 수 있고, 객체의 일관성을 유지한 상태로 둘 수 있다는 이점이 있다.
- 사이드 이펙트를 최소화한 채로 연산 결과를 반환하는 함수형 프로그래밍을 활용해 클래스의 불변 부분을 늘릴 수 있도록 한다
18. 상속보다는 구성을 사용하자
- 상속은 상위 클래스를 상속받는 과정에서 상위 클래스의 캡슐화를 깨뜨리고(상위 클래스의 구현 내용에 하위 클래스가 영향), 코드의 복잡도를 올린다 (기능 확인을 위해 상위 클래스 코드를 살펴봐야함)
- 상위 클래스의 캡슐화가 깨지는 예시로, 하위 클래스에서 호출한 super 객체의 메소드가 내부에서 재정의한 메소드를 호출하는 예시가 있다
- 정말 정확한 is kind of 관계일 때만 상속을 써야하며, 그마저도 상위 클래스가 확장을 염두하지 않은채 설계되었다면 상속은 사용하지 말자
- 상위 클래스의 메소드를 사용하기 위해, private final 필드로 상위 클래스를 두어 클래스에서 필요한 기능을 이용할 수 있다 (Wrapper class와 비슷)
19. 상속을 고려해 설계/문서화하고, 그렇지 않았다면 상속 금지
- 상속을 고려한 클래스 설계라면 꼭! 내부 구현 내용과 주의점을 문서화시키자. 메소드에서 또다른 내부 메소드를 호출하는 경우, 그 순서, 호출 결과로 발생하는 이펙트, 세부 구현 내용 등이 그 대상이다 -> 클래스의 추상화 원칙에 위배된다
- 상속용 클래스는 배포 전에 하위 클래스를 만들어 점검이 필요하고, 생성자는 재정의 가능 메소드(final이 아닌 메소드)를 호출하지 않아야 오동작을 막을 수 있다
- 많은 것을 고려했을 때, 상속용 클래스를 최소화하고, 상속용 클래스가 아닌 경우에 구체 클래스의 상속을 막는 것이 가장 현명한 프로그래밍 방식
20. 추상 클래스 < 인터페이스
- 인터페이스에 정의된 메소드를 구현하는 구현체를 implements에 추가하여 쉽게 추가 가능 (조합 폭발 X) -> 다중 인터페이스 구현이 가능하기 때문
- 복잡한 인터페이스라면 골격 구현을 제공하는 AbstractInterface를 먼저 구현하고 이를 상속하는 방식을 고려
21. 인터페이스는 구현할 쪽을 생각하며 설계
- 자바 9부터 추가된 인터페이스의 default method : 구현체들의 깨뜨림을 최소화하면서 인터페이스 메소드 추가가 가능해졌지만, 완벽하게 동작하는 것을 보장하긴 힘듦
- 인터페이스의 목적은 구현체들의 구현임을 생각하며 인터페이스를 범용적으로 설계하고, 릴리즈 전 여러 구현체들로 테스트하자
22. 인터페이스는 타입을 정의하는 용도로만 사용. 상수 버킷 X
- 객체지향의 이점 -> 그것의 구현 클래스를 인터페이스 타입 객체 자체로 사용할 수 있음을 알리기 위해 인터페이스가 존재. 오직 이 용도로만 인터페이스를 써야함
- "상수 인터페이스"는 인터페이스의 안티패턴 : 해당 상수를 사용하고 있는 클라이언트 코드들이 상수에 종속되게 만듦
- 원래의 목적을 가진 특정 클래스/인터페이스 자체에 상수를 추가하거나, enum을 이용하거나, 싱글톤 객체를 사용하는 것이 합당
23. 태그달린 클래스 대신 계층구조를 택하자
- 태그달린 클래스란, 클래스 내부의 조건에 따라 동작 방법이 달라지는 클래스 구조 : shape이라는 필드가 triangle이냐 rectangle이냐에 따라 넓이 계산이 달라지는 것을 커버하기 위해 switch문을 추가하는 것이 그 예시
- 쓰이지 않는 필드들까지 초기화해야하고, 쓸데없는 코드가 많아 가독성을 해치고, 비효율적인 코드 그.자.체.
- 대신 구현체에 따라 상태 패턴을 쓰거나 클래스 계층 구조를 만들어 다형적인 동작을 할 수 있도록 만들자
24. 멤버 클래스는 되도록 static으로 만들자
- 내부 클래스는 외부 클래스와 함께 사용되는 맥락에서, 외부 클래스의 도우미 역할을 하는 경우가 많다
- 내부 클래스가 바깥 클래스의 인스턴스를 참조할 필요가 없다면 static으로 선언하여 객체 생성의 비용과 내부 클래스의 외부 참조를 최소화할 수 있도록 하자!
25. .java파일 하나에는 하나의 public class만 담아 컴파일 오류를 없애자
독후감
OOP에서 객체는 추상적이어야 합니다. 이 말에는 많은 것이 담겨있는데, 객체가 하는 일의 세부 구현을 감춰야 하고, 객체가 하는 최소한의 기능을 public API로 드러내며 나머지 필드값은 전부 private으로 감춰야 한다는 것을 뜻합니다. 이렇게 되면 내부 필드가 해당 객체를 사용하는 클라이언트 코드에 의해 변화하여 예상치 못한 결과를 초래하는 것을 막을 수 있고, 객체의 세부 구현사항을 감춰 변화와 확장에 유리하게 됩니다.
immutable 객체에 대한 장점은 여러가지가 나오는 것 같습니다. 그 중 가장 중요한 점이 thread safe하여 공유 가능한 객체가 된다는 점인데, 이것을 읽고 바로 Spring의 Bean들이 생각났습니다. @Controller나 @Service 같은 spring mvc 빈들은 톰캣 등의 서블릿 구현체들에 의해 실행되는 컨트롤러와 서비스 메소드들을 담고 있습니다. 싱글톤으로 관리되는 빈들에게 상태, 즉 가변 필드가 존재한다면 무려 200개의 스레드를 띄워두고 사용하는 톰캣의 특성상 필연적인 race condition이 발생하겠죠. 그렇기 때문에 기본적으로 빈들에 선언되는 의존성은 final로 두고, 구성으로 두는 의존성 객체를 제외하고는 어떤 상태 필드값도 두지 않는 것이 일반적입니다.
그리고 static final로 주로 표현되는 상수를 프로그램에 나타내기 위해 interface를 사용한 적이 있습니다.. 사실 꽤 많습니다? 하지만 이것이 안티패턴임을 확실히 알았고, 오로지 상수를 정의하기 위해 인터페이스를 추가하는 대신 해당 상수가 필요한 인터페이스나 클래스에 static fianl 변수를 추가하는 방향으로 고려하도록 해야겠습니다.
<오브젝트>를 읽을 때, 상속을 사용하면 안되는 가장 중요한 이유가 상위 클래스의 캡슐화를 깨뜨리기 때문이라고 배웠는데, 동일한 내용을 다시 한 번 만나니 반가웠습니다! 상위 클래스의 구현 내용에 따라 super 메소드의 호출에 예상치 못한 결과가 일어날 수도 있고, 또한 하위 클래스에서 사용되는 숨겨진 상위 메소드의 구현 내용과 문서를 찾기 위해 상위 클래스를 뒤져야하는 정말 클래스를 상위에 두고 그것을 확장하려는 목적으로, 그러니까 "is kind of" 관계에 있는 특수한 목적으로만 사용해야겠습니다. 그마저도.. 어떤 메소드의 구현체로서 특정 메소드를 사용할 수 있는 클래스를 만들고자 하면 인터페이스를 사용하는 것이 더 현명한 방법이라고 하니..
구성(Composite)은 레이어드 아키텍처로 자바 웹 어플리케이션을 만들다보면 필연적으로 만나게되는 디자인 패턴입니다. 어떤 클래스의 기능을 사용하기 위해서 괜히 상속해서 super메소드를 사용하는 것보단 구성으로 둬서 원 객체가 작성하고자 하는 메소드에 도움을 주는 식으로 사용하는 것이 좋다는 것을 다시 한 번 느낍니다.
객체지향과 관련된 자바 기능들과 주의해야 할 점을 모아놓은 챕터입니다. [오브젝트]를 미리 잃었기 때문에(ㅎㅎ~) 아는 얘기가 대부분이었지만, 이펙티브 자바책 자체에서 정리하는 것과 추가로 알게된 내용이 있어 정리합니다!
15. 클래스와 멤버의 접근 권한을 최소화하라
- SRP에 따른 클래스의 목적 이유인 퍼블릭 API를 제외하고는 대부분의 메소드와 필드값(필드값은 상수를 제외하고 전부)들의 접근 권한을 최소화해야한다. 대부분 private으로 두는 것이 상책이다
- 내부 구현의 구체성을 숨김으로써 API 중심 개발, 단순성 향상, 변화에 유연한 클래스 관계를 달성할 수 있다
- public 가변 필드를 가질 경우 thread safe하지 않으며, 사이드 이펙트 때문에 예상치 못한 프로그램 동작이 일어날 수 있다
16. public 클래스에서는 접근자 메서드(=getter)를 사용하자
17. 객체를 최대한 immutable로 두자 private final!
- 특별한 이유가 없다면 모든 클래스는 확장을 막고(final class), setter를 없애고, 모든 필드를 private final로 두어 필드 값을 변경할 수 없는 "불변 클래스"로 두는 것이 좋다. 불변으로 만들 수 없어도 변경할 수 있는 부분을 최소화하자
- 생성자는 불변식의 모든 조건을 만족한 "완전한"상태의 객체를 반환하는 것이 좋다
- 프로그램의 단순성을 높이고, thread-safe하여 스레드간 공유 가능한 객체를 만들어 객체의 추가 생성 없이 이용할 수 있고, 객체의 일관성을 유지한 상태로 둘 수 있다는 이점이 있다.
- 사이드 이펙트를 최소화한 채로 연산 결과를 반환하는 함수형 프로그래밍을 활용해 클래스의 불변 부분을 늘릴 수 있도록 한다
18. 상속보다는 구성을 사용하자
- 상속은 상위 클래스를 상속받는 과정에서 상위 클래스의 캡슐화를 깨뜨리고(상위 클래스의 구현 내용에 하위 클래스가 영향), 코드의 복잡도를 올린다 (기능 확인을 위해 상위 클래스 코드를 살펴봐야함)
- 상위 클래스의 캡슐화가 깨지는 예시로, 하위 클래스에서 호출한 super 객체의 메소드가 내부에서 재정의한 메소드를 호출하는 예시가 있다
- 정말 정확한 is kind of 관계일 때만 상속을 써야하며, 그마저도 상위 클래스가 확장을 염두하지 않은채 설계되었다면 상속은 사용하지 말자
- 상위 클래스의 메소드를 사용하기 위해, private final 필드로 상위 클래스를 두어 클래스에서 필요한 기능을 이용할 수 있다 (Wrapper class와 비슷)
19. 상속을 고려해 설계/문서화하고, 그렇지 않았다면 상속 금지
- 상속을 고려한 클래스 설계라면 꼭! 내부 구현 내용과 주의점을 문서화시키자. 메소드에서 또다른 내부 메소드를 호출하는 경우, 그 순서, 호출 결과로 발생하는 이펙트, 세부 구현 내용 등이 그 대상이다 -> 클래스의 추상화 원칙에 위배된다
- 상속용 클래스는 배포 전에 하위 클래스를 만들어 점검이 필요하고, 생성자는 재정의 가능 메소드(final이 아닌 메소드)를 호출하지 않아야 오동작을 막을 수 있다
- 많은 것을 고려했을 때, 상속용 클래스를 최소화하고, 상속용 클래스가 아닌 경우에 구체 클래스의 상속을 막는 것이 가장 현명한 프로그래밍 방식
20. 추상 클래스 < 인터페이스
- 인터페이스에 정의된 메소드를 구현하는 구현체를 implements에 추가하여 쉽게 추가 가능 (조합 폭발 X) -> 다중 인터페이스 구현이 가능하기 때문
- 복잡한 인터페이스라면 골격 구현을 제공하는 AbstractInterface를 먼저 구현하고 이를 상속하는 방식을 고려
21. 인터페이스는 구현할 쪽을 생각하며 설계
- 자바 9부터 추가된 인터페이스의 default method : 구현체들의 깨뜨림을 최소화하면서 인터페이스 메소드 추가가 가능해졌지만, 완벽하게 동작하는 것을 보장하긴 힘듦
- 인터페이스의 목적은 구현체들의 구현임을 생각하며 인터페이스를 범용적으로 설계하고, 릴리즈 전 여러 구현체들로 테스트하자
22. 인터페이스는 타입을 정의하는 용도로만 사용. 상수 버킷 X
- 객체지향의 이점 -> 그것의 구현 클래스를 인터페이스 타입 객체 자체로 사용할 수 있음을 알리기 위해 인터페이스가 존재. 오직 이 용도로만 인터페이스를 써야함
- "상수 인터페이스"는 인터페이스의 안티패턴 : 해당 상수를 사용하고 있는 클라이언트 코드들이 상수에 종속되게 만듦
- 원래의 목적을 가진 특정 클래스/인터페이스 자체에 상수를 추가하거나, enum을 이용하거나, 싱글톤 객체를 사용하는 것이 합당
23. 태그달린 클래스 대신 계층구조를 택하자
- 태그달린 클래스란, 클래스 내부의 조건에 따라 동작 방법이 달라지는 클래스 구조 : shape이라는 필드가 triangle이냐 rectangle이냐에 따라 넓이 계산이 달라지는 것을 커버하기 위해 switch문을 추가하는 것이 그 예시
- 쓰이지 않는 필드들까지 초기화해야하고, 쓸데없는 코드가 많아 가독성을 해치고, 비효율적인 코드 그.자.체.
- 대신 구현체에 따라 상태 패턴을 쓰거나 클래스 계층 구조를 만들어 다형적인 동작을 할 수 있도록 만들자
24. 멤버 클래스는 되도록 static으로 만들자
- 내부 클래스는 외부 클래스와 함께 사용되는 맥락에서, 외부 클래스의 도우미 역할을 하는 경우가 많다
- 내부 클래스가 바깥 클래스의 인스턴스를 참조할 필요가 없다면 static으로 선언하여 객체 생성의 비용과 내부 클래스의 외부 참조를 최소화할 수 있도록 하자!
25. .java파일 하나에는 하나의 public class만 담아 컴파일 오류를 없애자
독후감
OOP에서 객체는 추상적이어야 합니다. 이 말에는 많은 것이 담겨있는데, 객체가 하는 일의 세부 구현을 감춰야 하고, 객체가 하는 최소한의 기능을 public API로 드러내며 나머지 필드값은 전부 private으로 감춰야 한다는 것을 뜻합니다. 이렇게 되면 내부 필드가 해당 객체를 사용하는 클라이언트 코드에 의해 변화하여 예상치 못한 결과를 초래하는 것을 막을 수 있고, 객체의 세부 구현사항을 감춰 변화와 확장에 유리하게 됩니다.
immutable 객체에 대한 장점은 여러가지가 나오는 것 같습니다. 그 중 가장 중요한 점이 thread safe하여 공유 가능한 객체가 된다는 점인데, 이것을 읽고 바로 Spring의 Bean들이 생각났습니다. @Controller나 @Service 같은 spring mvc 빈들은 톰캣 등의 서블릿 구현체들에 의해 실행되는 컨트롤러와 서비스 메소드들을 담고 있습니다. 싱글톤으로 관리되는 빈들에게 상태, 즉 가변 필드가 존재한다면 무려 200개의 스레드를 띄워두고 사용하는 톰캣의 특성상 필연적인 race condition이 발생하겠죠. 그렇기 때문에 기본적으로 빈들에 선언되는 의존성은 final로 두고, 구성으로 두는 의존성 객체를 제외하고는 어떤 상태 필드값도 두지 않는 것이 일반적입니다.
그리고 static final로 주로 표현되는 상수를 프로그램에 나타내기 위해 interface를 사용한 적이 있습니다.. 사실 꽤 많습니다? 하지만 이것이 안티패턴임을 확실히 알았고, 오로지 상수를 정의하기 위해 인터페이스를 추가하는 대신 해당 상수가 필요한 인터페이스나 클래스에 static fianl 변수를 추가하는 방향으로 고려하도록 해야겠습니다.
<오브젝트>를 읽을 때, 상속을 사용하면 안되는 가장 중요한 이유가 상위 클래스의 캡슐화를 깨뜨리기 때문이라고 배웠는데, 동일한 내용을 다시 한 번 만나니 반가웠습니다! 상위 클래스의 구현 내용에 따라 super 메소드의 호출에 예상치 못한 결과가 일어날 수도 있고, 또한 하위 클래스에서 사용되는 숨겨진 상위 메소드의 구현 내용과 문서를 찾기 위해 상위 클래스를 뒤져야하는 정말 클래스를 상위에 두고 그것을 확장하려는 목적으로, 그러니까 "is kind of" 관계에 있는 특수한 목적으로만 사용해야겠습니다. 그마저도.. 어떤 메소드의 구현체로서 특정 메소드를 사용할 수 있는 클래스를 만들고자 하면 인터페이스를 사용하는 것이 더 현명한 방법이라고 하니..
구성(Composite)은 레이어드 아키텍처로 자바 웹 어플리케이션을 만들다보면 필연적으로 만나게되는 디자인 패턴입니다. 어떤 클래스의 기능을 사용하기 위해서 괜히 상속해서 super메소드를 사용하는 것보단 구성으로 둬서 원 객체가 작성하고자 하는 메소드에 도움을 주는 식으로 사용하는 것이 좋다는 것을 다시 한 번 느낍니다.