[아이템 20] 추상 클래스보다는 인터페이스를 우선하라

Jimin Lim·2022년 5월 15일
0

Effective Java

목록 보기
20/38
post-thumbnail

아이템 20

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

다중 구현 방식

자바가 제공하는 다중 구현 방식은 (1) 인터페이스, (2) 추상 클래스가 있다.

(1) 인터페이스는 선언한 메서드를 모두 정의하고 그 일반 규약을 잘 지킨 클래스라면 어떤 클래스를 상속했든 같은 타입으로 취급된다.
(2) 추상 클래스는 추상클래스에서 정의한 메서드를 구현하는 클래스는 반드시 추상 클래스의 하위 클래스가 되어야 한다.

인터페이스 경우 예를 들어, 다른 부모를 상속하더라도 같은 implEx를 구현한다면 아래 클래스들은 같은 타입으로 취급된다.

public class ImplementExample1 extends parent1 implements implEx{
	
}

public class ImplementExample2 extends parent2 implements implEx{
	
}

인터페이스 장점

(1) 기존 클래스에도 손쉽게 새로운 인터페이스를 구현해넣을 수 있다.

인터페이스를 확장할 경우, 그 인터페이스가 요구하는 메서드를 추가하고, 클래스 선언에 implements 구문만 추가하면 끝이다.
반면 추상 클래스의 경우, 추상 클래스는 확장하길 원하는 두 클래스의 공통 조상이어야 한다. 이 방식은 새로 추가된 추상 클래스의 모든 자손이 이를 상속하게 된다.

(2) 믹스인 정의에 안성맞춤이다.
Mixin: 대상 타입의 주된 기능에 선택적 기능을 혼합한 것, 다른 클래스에서 이용할 메소드를 포함한 클래스

기존 클래스에 덧씌울 수 없다는 점, 두 부모를 섬길 수 없다는 점에서 추상 클래스는 적합하지 않다.

(3) 계층 구조가 없는 타입 프레임워크를 만들 수 있다

public interface Singer{
	public void sing();
}

public interface SongWriter{
	public void compose();
}

public interface SingerSongWriter extends Singer, SongWriter{
	public void actSensitive();
}

위와 같이 가수, 작곡가를 확장해 싱어송라이터를 인터페이스로 정의하면 문제가 되지 않으며 새로운 메서드까지 추가한 제3의 인터페이스를 정의할 수도 있다.

만약 추상 클래스로 만들게 된다면, 두 개의 부모를 상속할 수 없기에 가능한 조합 전부를 각각 클래스로 정의한 고도비만 계층구조가 만들어질 수 있다.

(4) 디폴트 메소드
디폴트 메서드를 제공해 프로그래머들의 일감을 덜어줄 수 있다. 하지만 이 방식에도 다음과 같은 제약이 존재한다.

  1. equals, hashcode와 같은 메서드를 디폴트로 제공해서는 안된다.
  2. 인스턴스 필드를 가질 수 없다. 또한 public이 아닌 멤버 (private 정적 메서드 제외)도 가질 수 없다.
  3. 본인이 만들지 않은 인터페이스에는 디폴트 메서드를 추가할 수 없다.

추상 골격 구현 클래스

위에서 다뤘던 디폴트 메소드의 제약으로 인한 방식이다. 이 방식은 인터페이스와 추상 클래스의 장점을 모두 취하는 방법이다.
인터페이스로는 타입을 정의하고, 골격 구현 클래스는 나머지 메서드까지 구현한다. (템플릿 메서드 방식)

public interface Character {
  public void move();
  public void seat();
  public void attack();
  public void process();
}

//공통되는 부분은 부모 클래스에 캡슐화 시키고 달라지는 부분만 자식 클래스에서 구현
public abstract class AbstractCharacter implements Character{
  @Override
  public void move() {
    System.out.println("걷다");
  }

  @Override
  public void seat() {
    System.out.println("앉다");
  }

  @Override
  public void process() {
    move();
    seat();
    attack();
  }
}

public class Thief extends AbstractCharacter implements Character{
    @Override
    public void attack() {
        System.out.println("표창을 던진다");
    }
}

public class Wizard extends AbstractCharacter implements Character{
    @Override
    public void attack() {
        System.out.println("마법봉을 휘두르다");
    }
}

만약 추상 클래스 AbstractCharacter를 생성하지 않았다면, implements로 구현했을 것이며 이 방식은 공통되는 부분이 생길 수 있다.

//각각 구현하는 경우
public class Thief implements Character{
  @Override
  public void move() {
    System.out.println("걷다"); //공통
  }

  @Override
  public void seat() { //공통
    System.out.println("앉다");
  }

  @Override
  public void attack() {
    System.out.println("표창을 던진다");
  }   
  
  @Override
  public void process() { //공통
    move();
    seat();
    attack();
  }
  
}

public class Wizard implements Character{
  @Override
  public void move() {
    System.out.println("걷다");
  }

  @Override
  public void seat() {
    System.out.println("앉다");
  }

  @Override
  public void attack() {
    System.out.println("마법봉을 휘두르다");
  }
  
  @Override
  public void process() {
    move();
    seat();
    attack();
  }
}

핵심 정리

  • 일반적으로 다중 구현용 타입으로는 인터페이스가 적합
  • 복잡한 인터페이스라면 구현하는 수고를 덜어주는 추상 골격 구현 클래스 고려해보자

참고

https://it-mesung.tistory.com/192

profile
💻 ☕️ 🏝 🍑 🍹 🏊‍♀️

0개의 댓글