본문 바로가기
Develop/Java

[Java] 접근 권한을 최소화해야 하는 이유

by 연로그 2022. 3. 6.
반응형

이 포스팅에 들어가기 앞서 잘 설계된 컴포넌트란 무엇인가?에 대해서 생각해보도록 하자.

우리가 컴포넌트를 설계할 때 중요한 것이 뭘까?

어떤 컴포넌트를 잘 설계되었다고 표현할까?

 


✨ 잘 설계된 컴포넌트

 

  • 클래스 내부 데이터와 구현 정보를 외부로부터 잘 숨긴 컴포넌트
  • 서로의 내부 동작 방식을 모른채 API를 통해 다른 컴포넌트와 소통

 

👉 결국은 정보 은닉캡슐화가 중요한 포인트라고 볼 수 있다.

 

🔻 정보 은닉이란?

더보기

: 다른 객체에게 자신의 정보를 숨기고 자신의 연산만을 통해 접근을 허용하는 것

 

정보 은닉으로 인한 장점

  • 여러 컴포넌트를 병렬로 개발 가능
    👉 시스템 개발 속도↑
  • 각 컴포넌트를 더 빨리 파악할 수 있고 교체 부담도 적음
    👉 시스템 관리 비용 ↓
  • 성능 최적화에 도움
  • 소프트웨어의 재사용성 ↑

 

캡슐화 vs 정보은닉
- 캡슐화: 객체의 상태와 메소드들을 함께 묶는 것
- 정보 은닉: 객체의 정보를 숨기기 위해 접근을 제한하는 것

 

Java는 이 정보 은닉을 위해 다양한 장치를 제공하고 있다.

이 포스팅에서는 그 중 하나인 접근 제어자에 대해 설명해보려 한다.

들어가기 앞서 다음 문장 하나를 기억해두자.

 

모든 클래스와 멤버의 접근성은 가능한 한 좁혀라.

 


📚 접근 제어자

 

📙 톱 레벨 클래스, 인터페이스

톱 레벨: 가장 바깥이라는 뜻

 

  • package-private: 해당 패키지 안에서만 이용 (= default)
  • public: 클라이언트에 영향을 주는 공개 API

 

📑 접근성 가능한 한 좁히기

public은 클라이언트에 영향을 줄 수 있다.

public인 클래스에서 내부 로직이 변경된다면 클라이언트까지 영향이 갈 수 있으므로 조심스러워야 한다.

내부 구현용인, public일 필요가 없는 톱 레벨 클래스, 인터페이스라면 반드시 package-private으로 범위를 좁혀라.

 

📑 한 클래스에서만 사용하는 package-private

톱 레벨에 위치한다는 건 같은 패키지의 모든 클래스가 접근할 수 있다는 의미이다.
이 때 private static을 중첩시키면 바깥 클래스 하나에서만 접근할 수 있다.

// AS-IS
public class A{
    private int a;
}
public class B{ // B가 A에서만 쓰이는 클래스라면?
    private int b;
}
// TO-DO
public class A{
    private int a;
    
    private static class B{
        private int b;
    }
}

👉 한 클래스에서만 사용하는 package-private 톱레벨 클래스/인터페이스에서 고려해보자. (Effective Java - item 24)

👉 자세한 설명: https://yeonyeon.tistory.com/205

 

[Java] 중첩 클래스의 종류 (feat. 멤버 클래스는 static으로!)

😏 서론 지난 번 클래스와 멤버의 접근 권한을 최소화해야 하는 이유에 대해 공부해보았다. 한 클래스에서만 사용하는 클래스는 private static으로 중첩시키라는 말이 있었는데 이 이유에 대해 자

yeonyeon.tistory.com

 

📗 멤버

  • private: 해당 클래스에서만 접근 가능
  • package-private: 클래스와 같은 패키지에서 접근 가능
  • protected: 같은 패키지 + 하위 클래스의 패키지에서 접근 가능
  • public: 모든 곳에서 접근 가능

 

📑 권한 풀어주기

코드를 작성하다보면 멤버의 권한을 풀어주는 일이 생길 수 있다.

권한을 풀어주는 일이 지나치게 많아지면 컴포넌트를 더 분리해야하는 것이 아닌지 고민해보자.

 

📑 멤버와 공개 API

protected와 public은 외부에 영향을 줄수도, 받을수도 있다.

예를 들어 외부에서 public 필드의 값을 마음대로 변경하면 내부 동작에 영향을 줄 수 있다.
이로 인해 내부 동작 방식을 API 문서에 적어 사용자에게 공개하는 경우도 생긴다. (Effective Java - item 19)


private와 package-private는 내부 구현용이지만 Serializable을 직접 구현한 클래스에서는 예외가 될 수 있다.

직렬화한 데이터가 일종의 공개 API로 취급하기도 한다. (Effective Java - item 86, 87)

Serialize; 직렬화
👉 Java 시스템 내부에서 사용되는 Object나 Data를 외부에서도 사용할 수 있도록 byte 형태로 변환하는 기술
👉 Serializable 인터페이스를 상속받음으로써 객체는 직렬화의 조건을 충족함.

 

📑 멤버 접근성의 제약

SOLID에서 리스코프 치환 원칙라는 원칙이 있다.

이 원칙에 의해 상위 클래스의 메소드를 재정의하는 경우 그 접근 수준을 상위 클래스보다 좁게 설정할 수 없다.

이를 어기면 컴파일 오류가 발생한다.

리스코프 치환 원칙이란?
👉 상위 타입의 객체를 하위 타입의 객체로 치환해도 동작에 문제가 없어야하는 규칙

 

class가 interface를 구현하는 건 특별한 예외다.

이의 경우 class의 메소드는 모두 public으로 선언해야 한다.

 

예제 1

  • A class를 상속받은 B class
public class A {
    public void methodA() {
        System.out.println("A!!");
    }
}

public class B extends A {
    @Override
    private void methodA() {
        System.out.println("B!!");
    }
}

컴파일 오류

 

예제 2

  • A interface를 구현하는 AImpl class
interface A{
    void test1();
    public void test2();
}

class AImpl implements A {
    @Override
    void test1() {
        System.out.println("test1");
    }

    @Override
    public void test2() {
        System.out.println("test2");
    }
}

컴파일 오류

 


🤔 의문점

 

❓ public 클래스의 public final 필드

public 클래스의 인스턴스 필드가 public이면 필드가 언제든지 가변될 수 있다.


그렇다면 public final으로 바꾸면 되지 않을까? 라고 생각할 수 있다.
이 경우에는 배열이나 List의 경우를 값의 변경 여지가 있다는 점을 생각하면 될 것 같다.


애초에 public으로 설정한다는 것은 우리의 가장 큰 목적이었던 캡슐화에서 벗어나있기도 하다.

 

예제)

  • 배열의 index 1 값을 3으로 변경 후 값이 변경되었는지 확인
public class A {
  public int[] arr = {1, 2, 3};
}

@Test
public void test() {
  A a = new A();
  a.arr[1] = 3;
  assertThat(a.arr[1]).isNotEqualTo(2);
}

 

다만 꼭 필요한 경우에는 static final 상수를 public으로 지정할 수 있다.
(배열이나 List의 경우 값이 가변될 수 있으니 주의할 것.)

 

🔻 Array와 List 불변으로 return하는 법

더보기
// 배열
Collections.unmodifiableList(Arrays.asList(배열));
배열.clone();
// 리스트
Collections.unmodifiableList(리스트);

 

❓ 테스트를 위한 접근성 확장

테스트 목적으로 접근 범위를 넓히는 경우가 있다.
private 멤버를 default로 풀어주는 정도까지는 괜찮지만 이를 protected, public까지 푸는 것은 문제가 있다.
이러한 경우 테스트 목적은 무엇인지, 클래스 구조나 설계가 잘못되어있지 않은지 의심해보아야 한다.

 

❓ Java 9와 모듈 시스템

모듈: 패키지의 모음

Java 9에서는 모듈 시스템이라는 개념이 도입되었다.

모듈에 속하는 패키지 중 공개(export)할 것을 선언하는 파일이 생겨났다. (info.java)
이 파일에 존재하지 않는 패키지라면 public, protected 멤버라도 모듈 외부에서 접근이 불가능하다.

 

다른 모듈에서는 접근할 수 없으나 같은 모듈이라면 패키지가 달라도 접근할 수 있는 접근 제어자가 생긴 것이다.

 

이를 활용한 예제로는 JDK가 있다.
자바 라이브러리에서 공개하지 않은 패지키들은 해당 모듈 밖에서 접근이 불가능하다.

 

❓ 모듈 시스템이 없던 시절의 코드

그렇다면 이전에 작성되었던 Java 코드와의 호환성은 어떻게 유지될까?
JAR가 모듈 경로가 아닌 classpath에 로드된 것을 이름 없는 모듈(Unnamed Module)이라고 한다.
이는 모듈 공개 여부와 상관 없이 public, protected가 모듈 밖에서도 접근이 가능하다.
모듈이 없는 것처럼 동작함으로써 이전 코드와도 하위 호환성 유지가 가능하다.

 

🌟 결론

접근성은 가능한 한 최소화 하자!


참고

  1. Effective Java - item 15
  2. https://www.geeksforgeeks.org/unnamed-module-in-java/
  3. https://stackoverflow.com/questions/13913174/what-are-the-differences-between-information-hiding-and-encapsulation
반응형