아이템 15 - 클래스와 멤버의 접근 권한을 최소화하라 - 핵심 정리
이 글은 백기선 님의 이펙티브 자바 강의와 이펙티브 자바 3 / E 편을 참고하여 작성하였습니다.
정보 은닉의 장점
정보 은닉의 장점은 다음과 같다.
- 시스템 개발 속도를 높인다.
정보 은닉을 하려다보면 자연스럽게 인터페이스 를 설계하게 된다.
어떤 인터페이스의 설계를 마치고 나면 그 인터페이스를 사용하는 쪽은 그 인터페이스에 맞게 개발을 하면 된다.
그 인터페이스를 구현하는 쪽 역시 해당 인터페이스에 정의되어 있는 대로 동작할 수 있는 클래스를 개발하면 된다.
이렇게 인터페이스를 사용하는 쪽과 제공하는 쪽이 동시에 개발 진행이 가능하다.
동시에 여러 모듈을 개발하는 것도 가능하다.
- 시스템 관리 비용을 낮춘다.
인터페이스를 통해 각 컴포넌트를 더 빨리 파악할 수 있기 때문이다.
캡슐화가 잘 되어있다는 가정하에 다른 컴포넌트로 변경하기도 쉽고, 문제가 생겼을 때 디버깅하기도 수월하다.
- 성능 최적화에 도움을 준다.
직접적인 도움은 아니지만 정보 은닉이 잘되어있을 때 성능의 병목지점 을 찾기 용이하다.
- 소프트웨어의 재사용성을 높일 수 있다.
어떤 컴포넌트가 다른 프로젝트에도 사용될 수 있는 모듈이라면 해당 모듈을 그대로 사용할 수 있다.
- 시스템 개발 난이도를 낮춘다.
큰 시스템을 만들기위해 모듈 단위로 나누어 개발하여 큰 문제를 해결할 수 있다.
클래스와 인터페이스의 접근 제한자 사용 원칙
다음의 원칙대로 클래스와 인터페이스의 접근 제한자를 사용한다면
조금 더 쓰기 편하고 견고하고, 유연하게 만들 수 있다.
top level 클래스와 인터페이스에 package-private 또는 public 을 쓸 수 있다.
톱 레벨 클래스나 인터페이스란 어떤 파일의 최상단 에 선언하는 클래스나 인터페이스이다.
톱 레벨에 붙일 수 있는 접근 제한자는 package - private 이나 public 두개 중 하나이다.
package - private 은 default 접근 지시자라고도 한다.
package - private 을 사용하면 내부 구현체가 되고 public 을 사용하면 API 가 된다.
숨길 것인지 공개할 것인지에 따라 결정한다.
패키지 내부에서 사용하거나 외부에서 사용하지 않는 클래스나 인터페이스라면 package - private 을 사용한다.
public 을 사용하는 순간부터 해당하는 API 는 하위 호완성을 유지하려면 영원히 관리해야 한다.
public 으로 공개한 클래스나 인터페이스를 변경하게되면 클라이언트 코드에서도 바뀐 코드에 맞게 변경해야한다.
하위 호환성에 너무 억매이다보면 급진적인 변화를 주기가 어렵다는 단점이 있다.
public interface MemberService {
// 탑레벨에는 package - private 아니면 public 만 가능하다.
}
공개되어도 괜찮고, 영원히 유지보수해도 괜챃다면 public 으로 선언한다.
class DefaultMemberService implements MemberService {
}
구현체같은 경우는 내부 클래스에서만 알면 되기 때문에 package - private 이 적절하다.
클라이언트 코드에서는 공개된 인터페이스만 알면되고, 구체적인 구현체는 굳이 알 필요가 없기 때문이다.
구현체 같은 경우는 의존성 주입이나 서비스 로더를 통해 제공받으면 되기 때문에 공개된 인터페이스만 알아도 된다.
한 클래스에서만 사용하는 package - private 클래스나 인터페이스는 해당 클래 스에 private static 으로 중첩 시키자.
interface MemberRepository {
}
패키지 내부에서만 사용하는 MemberRepository 라는 인터페이스가 있다.
class DefaultMemberService implements MemberService {
MemberRepository memberRepository;
public Member getMemeber() {
return memberRepository.findById();
}
}
package - private
으로 선언했지만 해당 패키지 내에서 한 클래스 에서만 사용이 된다면
class DefaultMemberService implements MemberService {
private static class MemberRepository {
}
}
private static
으로 해당 클래스에 중첩시킨다.
그렇다면 왜 private static
을 사용해야할까?
class DefaultMemberService implements MemberService {
private String name;
private static class PrivateStaticClass {
}
private class PrivateClass {
}
public static void main(String[] args) {
Arrays.stream(PrivateClass.class.getDeclaredFields()).forEach(System.out::println);
}
}
private class 는 자신을 감싸고 있는 외부 인스턴스를 참조 한다.
private static class 는 외부 인스턴스를 참조하지 않는다.
class DefaultMemberService implements MemberService {
private String name;
private class PrivateClass {
void doPrint() {
System.out.println(name);
}
}
}
private class 는 자신을 감싸고 있는 바깥 클래스의 멤버들에 대한 접근이 수월하다.
자기 자신을 감싸고 있는 바깥 클래스의 인스턴스를 가지고 있기 때문이다.
class DefaultMemberService implements MemberService {
private String name;
private static class PrivateStaticClass {
void doPrint() {
System.out.println(name); // 불가능하다.
}
}
}
private static class 같은 경우는 바깥 클래스의 멤버들에 대한 접근이 불가능하다.
원래 독립적인 클래스나 인터페이스를 inner class 로 만드는 것이기 때문에
한 클래스에서만 사용하는 package - private 클래스나 인터페이스를
해당 클래스에 중첩할 때는 private static class 이 더 어울리다.
만약 내부 클래스에서 외부 클래스의 필드들을 참조하고 싶다면 private class 로 만들면 된다.
멤버(필드, 메서드, 중첩 클래스/인터페이스)의 접근 제한자 원칙
클래스에서의 맴버란 필드, 메서드, 중첩 클래스 / 인터페이스 를 의미한다.
공개 API 를 만든 이후에는 다른 모든 멤버들의 접근 제한자를 private 으로 만들어야한다.
필요할 시에는 package - private
으로 접근 제한자를 풀어주어도 된다.
package - private
으로 풀어주는 멤버들이 많아진다면 컴포넌트의 구성이 잘못되지는 않았는지, 컴포넌트를 나누어야할지 고민해야한다.
private
, package - private
은 내부 구현이다.
숨길 정보에 해당하는 것은 private
, package - private
으로 감추어야한다.
밖으로 노출해야하는 정보라면 public
으로 해야한다.
필드에 대한 접급은 상수 일 경우에는 public static final
을 사용해 공개해야 한다.
public 클래스에 있는 인스턴스 필드는 되도록이면 public
이 아니어야한다.
공개할 API 에는 public
, protected
를 사용하여 공개한다.
public class ItemService {
private MemberService memberService;
boolean onSale;
protected int saleRate;
public ItemService(MemberService memberService) {
this.memberService = memberService;
}
}
ItemService 를 작성하자.
class ItemServiceTest {
@Test
void itemService() {
ItemService service = new ItemService();
}
}
테스트 코드를 작성해야하는데 ItemService 에서 사용하는 MemberService 에 대한 참조를 할 수가 없다고 가정하자.
@ExtendWith(MockitoExtension.class)
class ItemServiceTest {
@Mock // 가짜 객체 생성
MemberService memberService;
@Test
void itemService() {
ItemService service = new ItemService(memberService);
assertNotNull(service);
assertNotNull(service.getMemberService()); // 맴버 서비스에 대한 접근
}
}
이러한 경우에는 Mocking 을 통해 해결할 수 있다.
이 테스트에서는 ItemService 클래스에 있는 private 맴버 인 MemberService 에 대한 접근이 필요하다.
이럴때는 두가지 방법이 있다.
public MemberService getMemberService() {
return memberService;
}
getter
를 통해 접근을 허용하는 방법이다.
테스트 하는 대상이 이미 getter
를 제공하는 경우라면 getter
를 활용하는 것이 좋다.
하지만 getter
가 없는 경우에 테스트를 위해 공개 API 를 만드는 경우보다는
접근 권한을 변경하는 것을 권장한다.
public class ItemService {
MemberService memberService; // package - private 으로 접근 제한자 변경.
boolean onSale;
protected int saleRate;
public ItemService(MemberService memberService) {
this.memberService = memberService;
}
}
@ExtendWith(MockitoExtension.class)
class ItemServiceTest {
@Mock // 가짜 객체 생성
MemberService memberService;
@Test
void itemService() {
ItemService service = new ItemService(memberService);
assertNotNull(service);
assertNotNull(service.memberService()); // 맴버 서비스에 대한 접근
}
}
package - private
으로 접근 제한자를 변경해 테스트에서 접근이 가능하도록 한다.
만약 접근할 객체를 private
으로 유지하고 싶다면
MemberService getMemberService() {
return memberService;
}
getter
를 package - private
으로 제공하는 방법이 있다.
public class ItemService {
private MemberService memberService;
boolean onSale;
protected int saleRate;
public ItemService(MemberService memberService) {
if (memberService == null) {
throw new IllegalArgumentException("MemberService should not be null.");
}
this.memberService = memberService;
}
MemberService getMemberService() {
return memberService;
}
}
생성자 메서드에서 파라미터를 확인하는 방법이 이상적인 방법이다.
이처럼 private 으로 만들었지만 package - private
으로 확장하는 것은 괜찮으나
테스트 때문에 굳이 불필요한 public
한 맴버들을 만드는 걸 권장하지 않는다.
public static final String[] NAMES = new String[10]; // 권장하지 않는다.
public static final
을 배열 필드 에 사용하는 것은 권장하지 않는다.
베열 안의 값은 변경이 가능 하기 때문이다.
또한 이러한 필드를 반환하는 메서드를 제공하는 것 역시 권장하지 않는다.
'개발 공부 > Java' 카테고리의 다른 글
이펙티브 자바 아이템 16 - public 클래스에서는 public 필드가 아닌 접근자 메서드를 사용하라 - 핵심 정리 (0) | 2022.11.14 |
---|---|
이펙티브 자바 아이템 15 - 클래스와 멤버의 접근 권한을 최소화하라 - 완벽 공략 (0) | 2022.11.09 |
이펙티브 자바 아이템 14 - Comparable 을 구현할지 고민하라 - 완벽 공략 (0) | 2022.10.31 |
이펙티브 자바 아이템 14 - Comparable 을 구현할지 고민하라 - 핵심 정리 (0) | 2022.10.31 |
이펙티브 자바 아이템 13 - clone 재정의는 주의해서 진행하라 - 완벽 공략 (0) | 2022.10.28 |
댓글