Study/Effective-Java

[item#15] 클래스와 멤버의 접근 권한을 최소화하라

hongeeii 2023. 11. 29. 14:06
728x90
반응형

구현과 API를 분리하는 정보 은닉의 장점

  • 시스템 개발 속도를 높인다. (여러 컴포넌트를 병렬로 개발할 수 있기 때문에) => 인터페이스를 만들고 병렬로 구현체 개발
  • 시스템 관리 비용을 낮춘다. (컴퍼넌트를 더 빨리 파악할 수 있기 때문에) => 인터페이스 위주로 코드를 살펴보면 파악이 빠르고, 문제가 생겼을 때 디버깅 빠르게 할 수 있다.
  • 성능 최적화에 도움을 준다. (프로파일링을 통해 최적화할 컴포넌트를 찾고 다른 컴포넌트에 영향을 주지 않고 해당 컴포너트만 개선할 수 있기 때문에) => 성능의 병목지점을 찾아내는게 용의하는 뜻이다.
  • 소프트웨어 재사용성을 높인다. (독자적인 컴포넌트라면) => 어떤 컴포넌트가 다른 곳에서도 사용할수 있는 컴포넌트라면
  • 시스템 개발 난이도를 낮춘다. (전체를 만들기 전에 개별 컴포넌트를 검증할 수 있기 때문에) => divide and conquer (분할 정복)

클래스와 인터페이스의 접근 제한자 사용 원칙

  • 모든 클래스와 멤버의 접근성을 가능한 한 좁혀야 한다.
  • 톱레벨 클래스와 인터페이스에 package-private 또는 public을 쓸 수 있다.
    • public으로 선언하면 API가 되므로 하위 호환성을 유지하려면 영원히 관리해야한다.
    • 패키지 외부에서 쓰지 않을 클래스나 인터페이스라면 package-private으로 선언한다.
  • 한 클래스에서만 사용하는 package-private 클래스나 인터페이스는 해당 클래스에 private static으로 중첩 시키자 (item 24)

image
top level이기 때문에 public, package-private으로밖에 사용 불가

Spring IOC Container, Service Loader등, interface만으로 사용하게 하고싶을때 구현체는 package-private으로 만든다.

public interface MemberService {

}

public class Member {

}

class DefaultMemberService implements MemberService {
    // item은 MemberService의 어떤 구현체인지를 알필요가 없다.
    // 인터페이스만으로 코딩을 하길 바란다.

}

한 클래스에서만 사용하는 package-private 클래스나 인터페이스는 해당 클래스에 private static으로 중첩 시키자

interface MemberRepository {

}

class DefaultMemberService implements MemberService {
    // item은 MemberService의 어떤 구현체인지를 알필요가 없다.
    // 인터페이스만으로 코딩을 하길 바란다.

    MemberRepository memberRepository;
    
    public Member getMember(){
        return memberRepository.findById();
    }
    
}

위와 같은 MemberRepository를 DefaultMemberServices 내에서만 사용한다고 했을 때, 밑에와 같이 사용 가능.

class DefaultMemberService implements MemberService {
    // item은 MemberService의 어떤 구현체인지를 알필요가 없다.
    // 인터페이스만으로 코딩을 하길 바란다.

    private String name;

    private static class PrivateStaticClass {
        // 왜 static일까?
        // inner class지만 외부 클래스의 레퍼런스가 없다.
        void doPrint() {
            System.out.println(name); // compile error
        }
        
    }

    private class PrivateClass {
        // 외부 인스턴스를 참조한다.
        // 항상 자동으로 인스턴스를 참조하는 필드가 생김
        void doPrint() {
            System.out.println(name);
        }
    }
}

멤버(필드, 메서드, 중첩 클래스/인터페이스)의 접근 제한자 원칙

  • private과 package-private은 내부 구현
  • public 클래스의 protected와 public은 공개 API -> 접근제한을 풀어주는게 많다면 컴포넌트를 잘못 설계한 것은 아닌가 고민..
  • 코드를 테스트 하는 목적으로 private package-private으로 풀어주는 것은 허용할 수 있다. 하지만 테스트만을 위해서 멤버를 공개 API로 만들어서는 안된다. (테스트를 같은 패키지에 만든다면 그럴 필요도 없다.)
  • public 클래스의 인스턴스 필드는 되도록 public이 아니어야 한다.(item 16)
  • 클래스에서 public static final 배열 필드를 두거나, 이 필드를 반환하는 접근자 메서드를 제공해서는 안된다.

상수를 제외한 인스턴스 필드는 되도록 public이 아니어야한다.

public class ItemService {

    public static final String NAME = "whiteship";
    private MemberService memberService; // 인스턴스 필드는 되도록 public이 아니어야한다.

    boolean onSale;

    protected int saleRate;

    public ItemService(MemberService memberService) {
        this.memberService = memberService;
    }
}

class DefaultMemberService implements MemberService {
    private static class PrivateStaticClass {
    }
}

test

public class ItemServiceTest {

    MemberService memberService;

    @Test
    void itemService(){
        ItemService service  = new ItemService(memberService);

    }
}

위 코드에서 MemberService의 구현체인 DefaultMemberService를 만들수가 없다.(package-default class이기 때문)
이럴경우, Mocking을 사용한다.

@ExtendWith(MockitoExtension.class)
public class ItemServiceTest {

    @Mock
    MemberService memberService;

    @Test
    void itemService(){
        ItemService service  = new ItemService(memberService);
        assertNotNull(service);
        assertNotNull(service.getMemberService()); //     private MemberService memberService; 

    }
}

위 처럼 test목적으로 getter라는 공개 API를 만드는 것 보다는 차라리 MemberService를 package-private으로 만들수 있다.
image
test 폴더라도 package명이 같다면 접근 가능하다.!!!!

위와 같은 방법으로 필드는 private으로 getter는 package-private으로 만들어서 접근 가능하다~!!.

클래스에서 public static final 배열 필드를 두거나, 이 필드를 반환하는 접근자 메서드를 제공해서는 안된다.

 public static final String[] NAME = new String[10];

외부에서 NAME상수를 변경 가능하다.

필드들은 전부 private으로 만들고, test를 하다가 private을 package-private으로 변경할 순 있다.  
하지만 test를 위해 private을 public이나 protected로 만들진 말자
728x90
반응형