책 읽고 정리하기/2022

Effective Java 3/E - 4장 클래스와 인터페이스 - 3

TwinParadox 2022. 2. 1. 20:44
728x90

23. 태그 달린 클래스보다는 클래스 계층구조를 활용하라

태그 달린 클래스의 단점

  • 열거 타입 선언, 태그 필드, switch 문 등의 관련 코드가 필요
  • 필드들을 final로 선언하는 경우, 쓰이지 않는 필드까지 생성자에서 초기화
  • 인스턴스의 타입만으로는 현재 의미 파악 불가

의미 없는 코드로 가독성이 떨어지면서 필요 없는 코드 작성 과정에서 오류 발생 가능성과 비효율적인 작업이 급격하게 증가하게 된다. 이러한 문제를 해결하기 위해서는 서브타이핑(Subtyping)을 사용하는 것이 좋다.

 

태그 달린 클래스의 예시

public class Figure {
	enum Shape { RECTANGLE, CIRCLE };

	final Shape shape;

	// 관련 필드가 한 곳에다 모여 있다.
	double length;
	double width;
	double radius;

	// 생성자에서 태그 필드도 초기화해야 한다.
	Figure(double radius) {
		this.shape = Shape.CIRCLE;
		this.radius = radius;
	}
	Figure(double length, double width) {
		this.shape = Shape.RECTANGLE;
		this.length = length;
		this.width = width;
	}

	// 동일한 기능이지만, 태그 필드 기준의 switch 구문이 필요
	public double area() {
		switch(shape) {
			case RECTANGLE:
				return length * width;
			case CIRCLE:
				return Math.PI * radius * radius;
			default:
				throw new AssertionError(shape);
		}
	}
}

 

클래스 계층구조 적용하기

  • 계층 구조의 루트를 추상 클래스로 정의하고, 동작이 달라지는 메서드를 루트 클래스의 추상 메서드로 선언
  • 태그 값에 무관하게 일정한 동작의 메서드는 루트 클래스의 일반 메서드
  • 모든 하위 클래스가 공통으로 사용하는 필드들도 루트 클래스의 필드로 지정
  • 루트 클래스를 확장한 구체 클래스를 의미에 따라 하나씩 정의
    • 각각의 클래스에서 의미에 해당하는 데이터 필드들을 넣어서 사용
    • 추상 메서드는 각 의미에 맞게 구현

루트 클래스의 코드를 건드리지 않고, 다른 사용자가 이 루트 클래스를 확장하고 사용이 가능해진다. 타입이 의미별로 따로 존재하기 때문에 변수 의미 부여와 제한이 가능해진다. 유연성 뿐 아니라, 컴파일타임 타입 검사 능력도 높여줄 수 있다.

 

 

 

24. 멤버 클래스는 되도록 static으로 만들라

중첩 클래스는 바깥의 클래스에서만 쓰여야하고, 그 외의 쓰임이 필요하면 톱레벨로 가져가야 한다.

 

중첩 클래스

  • 중첩 클래스의 종류는 네 가지
  • 정적 멤버 클래스
  • 비정적 멤버 클래스, 익명 클래스, 지역 클래스
  • 정적 멤버 클래스를 제외하고 나머지가 내부 클래스에 해당

 

정적 멤버 클래스

  • 다른 클래스 안에 선언하여, 외부 클래스의 private 멤버에도 접근이 가능한 클래스
  • 정적 멤버와 동일한 접근 규칙을 적용
  • 바깥 인스턴스에 접근할 일이 없을 때는 무조건 정적 멤버 클래스로 사용
  • 바깥 클래스와 함께 사용될 때만 유용한 도우미 클래스에 주로 사용

 

비정적 멤버 클래스

  • 비정적 멤버 클래스의 인스턴스는 바깥 클래스의 인스턴스와 암묵적 연결
  • 클래스명.this를 사용해 바깥 인스턴스의 메서드 및 참조 활용 가능
  • 중첩 클래스의 인스턴스가 바깥 인스턴스와 독립적 존재으로 존재할 수 없음
  • 멤버 클래스 인스턴스화 시점에 바깥 인스턴스와의 관계 확립되며 변경 불가
  • 어댑터 정의할 때 주로 사용

 

익명 클래스

  • 바깥 클래스의 멤버가 아니며, 쓰이는 시점에 선언과 동시에 인스턴스 생성
  • 코드의 어느 지점에서든 생성 가능
  • 비정적인 문맥에서 사용될 때, 바깥 클래스의 인스턴스 참조 가능
  • 선언 지점에서만 사용 가능하고 instanceof 검사 및 클래스 이름을 활용하는 작업은 불가능
  • 여러 인터페이스 구현 불가, 인터페이스 구현하면서 클래스 상속 불가
  • 람다가 없던 시절의 대체품이었으며, 정적 팩토리 메서드 구현에서 사용

 

지역 클래스

  • 지역 변수 선언이 가능한 곳에서는 어디서든 선언 가능하며 유효 범위가 지역 변수와 동일
  • 멤버 클래스처럼 이름이 있으며 반복 사용 가능
  • 익명 클래스처럼 비정적 문맥에서 사용될 때만 바깥 인스턴스를 참조 가능
  • 정적 멤버는 가질 수 없음

 

25. 톱레벨 클래스는 한 파일에 하나만 담으라

톱레벨 클래스(또는 인터페이스)를 하나의 파일에 두면, 컴파일러에 어떤 소스파일이 먼저 처리되느냐에 따라 동작이 달라지기 때문에, 이런 문제를 개선하기 위해서는 톱레벨 클래스 하나를 무조건 하나의 파일에만 담는 방법으로 대응해야 한다.

만약, 여러 톱레벨 클래스가 하나의 파일에 들어가야 한다면, 정적 멤버 클래스를 고려하자.

 

 

728x90
728x90