Effective Java 3 | Class, Interface

공부의 기록·2021년 12월 13일
0

Java (Effective Java)

목록 보기
4/12
post-thumbnail
post-custom-banner

클래스, 인터페이스

본 문서는 2021년 12월 22일 에 작성되었다.

Effective Java 1 | Object 에서는 객체의 생성과 관련한 이론 및 기법을 배웠다.
Effective Java 2 | Common Methd 에서는 사용자 정의 클래스를 만들면 Object 클래스에서 오버라이딩 해야하는 메서드 들에 대한 내용들을 배웠다.

그리고 이번 파트는 어떻게하면 유연한 클래스 및 인터페이스를 작성하는가? 의 포인트이다.


아이템 15 | 접근권한 최소화

원제목 | 클래스와 멤버의 접근 권한을 최소화하라

멤버(필드, 메서드, 중첩 클래스, 중첩 인터페이스) 에 부여할 수 있는 접근 수준은 네 가지이다.

  1. private | 멤버를 선언한 톱 레벨 클래스에서만 접근가능
  2. package-private | 멤버가 소속된 패키지 안의 모든 클래스에서 접근 가능
  3. protected | package-private 의 접근 범위를 포함하여, 이 멤버를 선언한 클래스의 하위 클래스에서도 접근할 수 있다.(JLS, 6.6.2 제약 존재)
  4. public | 모든 곳에서 접근할 수 있다.

특별한 이유가 아니라면 멤버들을 불변객체 로 관리해야한다.
특히 public static final Object[] values={ ... } 와 같은 정적 상수 배열도 보안 허점이 존재한다.
final 이라는 키워드에 속아서 해당 방식을 사용하지 않도록 주의하자!


아이템 16 | getter, setter

원제목 | public 클래스에서는 public 필드가 아닌 접근자 메서드를 사용하라

솔직히 자바를 어느 정도 공부를 하고 Effective Java 와 같은 책을 공부했다면 getter, setter 에 대해서 본 경험이 있을 것이다. 사용자 정의 클래스가 아니더라도 get() set() 관련 메서드는 매우 흔하다.

특히 Effective Java 1 | Object 를 읽었거나 GoF 디자인패턴 과 같은 책을 읽었거나 관련 내용을 구글링 만 했어도 들어봤을 것 같은데, 이러한 getter 와 setter 를 사용한 패턴 중에는 Java Beans 패턴 이 있다. 권장되는 패턴은 아니지만, 초기 모델에 한해서 꽤나 빠르게 구현하고 직관적이라는 장점이 있다.


아이템 17 | 불변 클래스(미완)

원제목 | 변경 가능성을 최소화하라

불변 클래스란 내부의 값을 변경할 수 없는 클래스이다.

클래스를 불변 클래스로 만들려면 다음과 같은 성질을 가져야 한다.

  1. 객체의 상태를 변경하는 메서드(변경자)를 제공하지 않는다.
  2. 여러 방법 등을 활용해 클래스를 확장할 수 없게 한다.
  3. 모든 필드를 final 로 선언한다.
  4. 모든 필드를 private 로 선언한다.
  5. 자신 외에서는 내부의 가변 컴포넌트에 접근할 수 없도록 한다.

이 아래 자세한 내용은 코드 예제만 남겨두고 중단하도록 하겠다.
불변 클래스와 관련된 주의사항은 꽤나 많은 편이고 그 중 다수는 아직 배우지 않은 내용을 다루고 있기 때문이다.

public class Complex {
  private final double re;
  private final double im;
  
  private Complex(double re, double im) {
    this.re=re;
    this.im=im;
  }
  
  // 생략
}

아이템 18 | 컴포지션 선택

원제목 | 상속보다는 컴포지션을 사용하라

상속은 전적으로 B is A 인 경우에만 사용하자.
B is A 인가 라는 질문에 명확하게 "그렇다"라고 답하지 못한다면 Composition 을 사용하자.
Composition 에 대해서는 다음의 포스트 Java - 상속과 구성 을 참고하자.

Collection Framework 에서 상속의 잘못된 사용은 다음과 같다.

  1. Stack 은 Vector 가 아니므로 Vector 를 상속하면 안됐다.
  2. Properties 도 Hashtable 이 아니므로 Hashtable 을 확장해서는 안됐다.

아이템 19 | 상속 고려

원제목 | 상속을 고려해 설계하고 문서화하라. 그러지 않았다면 상속을 금지하라.

상속을 고려한 클래스를 만들 때 다음과 같은 것들을 고려 해야 한다.

  1. 재정의 허락 메서드들의 자기사용 문서 작성
  2. 상속용 설계 클래스의 하위 클래스를 이용한 검증
  3. 상속용 설계 클래스의 생성자의 재정의 허락 메서드 호출 금지
  4. 상속용 설계 클래스에서 Cloneable, Serializable 사용 자제

# 재정의 허락 메서드들의 자기 사용 문서 작성

상속용 클래스는 재정의가 허락된 메서드 들의 자기사용 문서 를 만들어야 한다.
여기서 재정의가 허락된( public || protect ) && !final 인 메서드 이다.

더 구체적인 예를 들어보면 부모 클래스에 다음과 같은 메서드가 있다고 해보자.

  1. public boolean a(){ };
  2. public void b() { if(a()){ ... } else { ... } };

여기서 b 메서드에는 다음과 같은 주석이 달려야 할 것이다.

  • Implementation Requiredments : 이 메서드는 ~~~ 하고 클래스 내부의 {@code a()} 를 호출하므로 a() 를 먼저 재정의하지 않으면 ~~Exception 이 던져집니다.

아이템 20 | 인터페이스 우선

원제목 | 추상 클래스보다는 인터페이스를 우선하라

클래스는 상속 수에 제한이 있지만,
인터페이스는 상속(편의상) 수에 제한이 없기 때문에 설계상의 유연함을 가질 수 있다.

인터페이스로 쉽게 구현할 수 있는 것을 클래스로 구현하려면 2^n 의 경우로 Combinatorial Explosion 이 발생할 수 있다.

인터페이스의 메서드는 기본적으로 abstract method 이지만,
구현 방법이 확실한 경우에는 default method 로 선언해도 되며,
그러한 경우 @implSpec 자바독 태그를 붙여서 문서화를 해야 하며,
equals, hashCode 같은 메서드는 default mehotd 로 선언하면 안된다.

유연한 인터페이스에 대한 자세한 기법은
Skeleton Implements 혹은 Simple Implements 를 검색해서 참고해보자.


아이템 21 | 인터페이스 구현 시 고민사항

원제목 | 인터페이스는 구현하는 쪽을 생각해 설계하라

인터페이스에 default method 를 사용하는 경우 다음과 같은 상황들을 주의해야 한다.

  1. 범용성 있는 코드인가.
  2. Java 외부 패키지 와의 충돌 가능성은 없는가.
  3. 여러 종류의 비즈니스 로직 상에서 테스트해봤는가.
  4. 다른 인터페이스 구현체와의 충돌사항은 없는가?

따라서 default mehotd 를 통해서 interface 에 특정 기능을 넣어줄 수는 있으나
이는 매우 험난하고 위험한 행위이므로 심사숙고해야한다.

default boolean removeIf(Predicate<? super E> filter) {
  Objects.requireNonNull(filter);
  boolean removed = false;
  final Iterator<E> each = iterator();
  
  while (each.hasNext()) {
    if (filter.test(each.next())) {
      each.remove();
      removed = true;
    }
  }
  
  return removed;
}

아이템 22 | 인터페이스 타입 정의

원제목 | 인터페이스는 타입을 정의하는 용도로만 사용하라

인터페이스는 자신을 구현한 구현체(클래스) 에게
나를 구현함으로써 어떤 것을 할 수 있는지를 알려주는 용도 로만 사용해야 한다.

그리고 그 중 하나가 바로 인스턴스를 참조할 수 있는 타입 역할 이다.

그러나 상수들을 모아놓은 인터페이스를 만드는 것과 같은 최악의 사례도 엄연히 존재한다.
이런 패턴은 차라리 유틸리티 클래스를 만들어서 관리하는 것이 낫다.

# 상수 인터페이스

public interface NumbersImpl {
   static final double DOUBLE_NUMBER_ONE = 어떤 숫자;
   static final double DOUBLE_NUMBER_TWO = 어떤 숫자;
   static final double DOUBLE_NUMBER-THREE = 어떤 숫자;
}

# 상수 클래스 (유틸리티 클래스)

public class Numbers {
   private Numbers(){}
   
   public static final double DOUBLE_NUMBER_ONE = 어떤 숫자;
   public static final double DOUBLE_NUMBER_TWO = 어떤 숫자;
   public static final double DOUBLE_NUMBER-THREE = 어떤 숫자;
}

아이템 23 | 현명한 클래스 계층도

원제목 | 태그 달린 클래스보다는 클래스 계층구조를 활용하라

태그 달린 클래스는 다음과 같은 클래스를 의미한다.

public Shapes {
   enum Shape { CIRCLE, RECTANGLE, TRIANGLE };
   final Shape shape;
   
   // Shape 가 Circle 일 때 사용
   // 변수들...
   
   // Shape 가 Rectangle 일 때 사용
   // 변수들...
   
   // Shape 가 Triangle 일 때 사용 
   // 변수들...
   
   public double area() {
      switch(shape){
        case CIRCLE:
          return 넓이;
        case RECTANGLE:
          return 넓이;
        case TRIANGLE:
          return 넓이;
      }
   }
}

태그 달린 클래스는 다음과 같은 단점이 있다.

  1. 장황하다.
  2. 오류를 내기 쉽다.
  3. 비효율 적이다.

따라서 다음과 같이 인터페이스를 이용해 계층도를 만들자.

public interface Shapes {
   abstract double area();
}
class Circle implements Shapes {
   // 멤버 변수
   
   // 생성자
   
   @Override double area() {
      // 로직
   }
}
class Rectangle implements Shapes {
   // 동일...
}
class Triangle implements Shapes {
   // 동일...
}

당연히 이 클래스도 private 타입의 멤버변수들을 가지고 접근 메서드를 사용해야 한다.


아이템 24 | 중첩 클래스 + static

원제목 | 멤버 클래스는 되도록 static 으로 만들라

중첩 클래스는 클래스 속에 선언된 클래스입니다.
표현 상의 목적으로 임의로 외부 클래스와 내부 클래스로 부르겠습니다.

내부 클래스에 대한 일반적인 규칙은 다음과 같습니다.

  1. 내부 클래스는 외부 클래스 영역에서만 쓰여야 합니다.
  2. 내부 클래스가 외부 클래스 밖에서 쓰여야 한다면, 톱 레벨 클래스로 따로 만들어야 합니다.

해당 부분에 대한 이해를 하지 못하였습니다.
따라서 이 부분은 자바에 대한 이해도가 더 높아졌을때 다시 진행하겠습니다.


아이템 25 | 한 파일에 하나의 톱 레벨 클래스

원제목 | 톱레벨 클래스는 한 파일에 하나만 담으라

하나의 소스 파일에는 하나의 톱레벨 클레스 혹은 하나의 톱레벨 인터페이스 를 담아야 한다.

profile
2022년 12월 9일 부터 노션 페이지에서 작성을 이어가고 있습니다.
post-custom-banner

0개의 댓글