-
Effective Java Study - 1개발 2022. 9. 8. 00:43더보기
Chapter 4 Class & Interface
Item 15. 클래스와 멤버의 접근 권한을 최소화 한다 (캡슐화 하자)
잘 설계된 컴포넌트 ?
외부에 내부 데이터와 구현 정보를 얼마나 잘 은닉화 했는지로 판단.
- API로 외부와 통신하며 서로 내부 동작 방식에 관여 하지 않게 한다. -> 캡슐화를 이야기 한다.
캡슐화의 장점
시스템을 구성하는 컴포넌트를 독립시켜 사이클을 개별적으로 수행한다.
- 병렬로 개발할 수 있어 생산성이 좋아진다.
- 개별로 개발 하기 때문에 각 컴포넌트별 최적화가 가능하다.
- 독립적으로 기능을 수행하기 때문에 재사용성이 높다.
- 큰 시스템을 작은 시스템으로 쪼개 조립할 수 있기 때문에 난이도를 낮춰준다.
자바의 접근 제어 메커니즘
보통 접근 제한자와 선언된 위치로 정해진다.
접근 제한자로는 Private, Protected, public 존재, 기본이니 넘어간다.
기본원칙은 모든 클래스와 멤버의 접근성을 가능한 낮은 접근수준을 유지한다
Top Level Class 와 Interface에 부여할수 있는 수준은 package-private(default), public.
public 으로 선언시 공개 API로 지속적인 관리가 필요하므로 필요할 경우만 사용.
public일 필요가 없는 클래스의 접근 수준은 package-private 레벨로 만들어야 한다.
public 클래스 경우 그 package의 api가 되지만, 위의 클래스는 내부 구현이기 때문.
왠만하면 모든 클래스 멤버는 private로 하자.. 같은 패키지 내부에서 접근해야 하는 경우만 default선언해서 쓰자.
접근성을 좁히지 못하는 제약이 하나 존재한다.
- 상위 클래스의 메서드를 오버라이드 할때는 접근 수준을 상위 클래스보다 좁게 할 수 없다. (리스코프 치환 원칙을 지키기 위함)
클래스가 인터페이스를 구현하는건 특별한 예이며, 이때 클래스는 인터페이스가 정의한 모든 메소드를 public으로 선언해야함.
public 클래스의 인스턴스 필드는 왠만하면 public을 지양한다. final로 선언된 인스턴트 필드가 아닌 경우 Public으로 선언시 필드에 값을 제한 할 수 없게 된다.
public한 가변 필드를 갖는 클래스는 Thread에서 안전하지 못한다. 여기서도 예외가 존재한다 추상적 개념을 완성하는데 필요한 상수일 경우 Public static final로 선언해서 쓰자. 단, 기본 타입값이나, 불변 객체일 경우만 사용한다 아닐경우 책임 안짐 (많은 문제 야기)
-> 굳이 접근해서 사용하고 싶다 할때
방법 1. public -> private 전환 후 public static final 하나 더 선언해서 사용
방법 2. private로 전환 후, 이를 복사해 반환 하는 Public method 사용 (방어적 복사)
이를 해결하기 위해 자바 9에서 모듈 시스템 개념이 도입됨.
Package -> class들의 묶음 / Modules - > Package들의 묶음
Module의 경우 자신에 속하는 Package 공개할 것들을 module-info.java 파일에 선언함.
결론
- 프로그램 접근성은 가능한 최소화, 꼭 필요한 것만 public한 api로 설계 한다.
- 그 외의 class, interface등이 노출되지 않아야함. public classsms Public static final 상수 말고는 public field x
-> Item 16으로 이어짐
Item. 16 Public Class에서는 Public field가 아닌 접근자 메서드를 사용해
public class에서 field는 private으로 선언하고, Public Getter를 사용해 접근하자. (사실 기본)
패키지 외부에서도 접근가능한 클래스라면 접근자를 통해 유연성 획득.
결론
- public class는 가변 필드를 직접 노출 하지 않아야함. 불변 필드라면 모르겠지만(이또한 안전하진 않음)
Item. 17 변경 가능성을 최소화 해
Immutable class - 인스턴스의 내부 값을 수정 할 수 없는 클래스. 불변 인스턴스는 객체가 파괴되는 순간까지 고정.
ex) String, BigInteger, Integer (Boxing)등
불변 클래스 만드는 규칙
- 객체의 상태를 변경하는 메서드 (setter)를 제공하지 않는다.
- 클래스를 확장할 수 없게 한다 (Final로 선언)
- 모든 Field를 final, Private로 선언한다.
- 자신 외에는 내부의 가변 컴포넌트에 접근 할 수 없도록 한다. -> Getter로 접근도 하게 해선 안된다.
불변 객체는 근본적으로 Thread 안전하여 따로 동기화 할 필요가 없다. 동시에 사용해도 훼손될 일이 없음
불변 클래스는 자주 사용하는 인스턴스를 캐싱하여 같은 인스턴스를 중복 생성하지 않도록 Static Factory를 제공할 수 있음.
ex) Integer, BigInteger
Static Factory 사용시, 여러 클라이언트가 인스턴스를 공유하여 메모리 사용량과 GC 비용이 감소 -> 새로운 클래스 생성시 Public 생성자 대신 팩터리를 만들면 클라이언트 수정없이 캐싱 등 기능을 덧 붙일 수 있음
불변 객체는 메서드에서 예외가 발생해도, 객체 자체는 유효하여 원자성을 제공함.
단, 불변 클래스에서 값이 다른 필드가 존재 할 경우 독립된 객체로 만들어야 한다는 단점 존재.
결론
- Getter있다고 Setter 무조건 만들지말자.
- 클래스는 꼭 필요한 경우가 아니라면 불변인게 좋다, 모든 클래스를 불변으로 만들 수는 없지만 변경할 수 있는 영역을 최소화 시키는게 무조건 좋다.
- 변경이 필요한 필드를 제외하고는 final로 선언하자.
- 생성자는 불변식 설정이 모두 완료되고 초기화가 완벽히 끝난 상태의 객체를 생성하자.
Item. 18 상속보다는 컴포지션을 사용해
상속은 재사용하는 방법중 강력하지만, 최선은 아니다. 같은 패키지 내부에서라면 상속도 안전하지만, 패키지 경계를 넘나 들 경우 문제가 발생한다. 여기서 상속은 클래스를 확장 구현하는 상속이지 인터페이스를 구현하는 상속은 아님을 명심해야 한다. (엄연히 다름)
상속은 메서드 호출과 달리 캡슐화를 깨뜨린다.
상위 클래스의 구현에 따라 하위 클래스의 동작에 이상이 발생할 수 있다. 즉 상위 클래스 변경시 하위 클래스도 영향을 받는 다는 소리로 오동작을 야기시킬 수 있다.
이러한 문제를 해결하는 방법으로 기존 클래스를 확장하는 방법 대신, 새로운 클래스를 만들고, private Field로 기존 클래스의 인스턴스를 참조하게 하면, 기존 클래스가 새로운 클래스의 구성요소로 쓰인다. 이러한 방법을 컴포지션이라 한다.
새 클래스의 Method들은 기존 클래스에 대응하는 Method를 호출해 결과를 반환한다. 이를 Forwarding(전달)이라 하며 이러한 메서드들을 forwarding method라 한다.
결과적으로 새로운 클래스는 기존 클래스의 내부 구현 방식 영향에서 벗어나고, 새로운 작업이 추가되어도 영향 받지 않는다.
이러한 컴포지션 클래스를 wrapper class 라하며, Decorator Pattern이라고도 한다.
컴포지션과 전달의 조합은 넓은 의미로 위임이라고 한다 (래퍼 객체가 내부 객체에 자기 자신의 참조를 넘기는 케이스만)
Wrapper class는 콜백 프레임워크와는 어울리지 않는다. 자기 자신의 참조를 다른 객체에 넘겨서 다음 호출 때 사용하도록하는데, 내부 객체는 자신을 감싸고 있는 wrapper class의 정체를 몰라 문제가 발생한다.
결론
- 상속은 반드시 하위 클래스가 상위 클래스의 진짜 하위 타입인 케이스에서만 사용, A와 B가 Is-a관계 일 때만 상속하여 사용하자.
- 컴포지션과 전달을 사용하자. Wrapper class로 적당히 구현할 인터페이스 있으면 더더욱
'개발' 카테고리의 다른 글
Eureka - [Spring Cloud] (0) 2022.12.29 Gradle 기본 정리 (0) 2022.12.21 JPA 연관 관계 매핑 (0) 2022.08.02 Index (2) (0) 2022.07.05 Index (1) (0) 2022.06.29