3 minute read

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

  • 공통점
    • 인스턴스 메서드를 구현 형태로 지원이 가능하다.
  • 차이점
    • 추상 클래스가 정의한 타입을 구현하는 클래스는 반드시 추상 클래스의 하위 클래스가 되어야한다. (다중 상속 미지원)
    • 인터페이스는 구현을 강제해 같은 동작을 하도록 보장하며, 각 구현 객체를 같은 타입으로 취급할 수 있다. 한 클래스에서 여러 인터페이스를 상속받을 수 있다.
    • 추상 클래스의 경우 기존 클래스에 확장이 어려우나, 인터페이스의 경우 기존 클래스에서의 확장이 간편하다.
public interface Singer {
	AudioClip sing(Song s);
}

public interface Songwriter {    
	Song compose(int chartPosition);
}

public interface SingerSongwriter extends Singer, Songwriter {
	AudioClip strum();
    void actSensitive();
}

믹스인

  • 믹스인을 구현한 클래스는 원래의 주된 타입 외에도 특정 선택적 행위를 혼합하여 제공할 수 있다.
  • 예: Comparable
class Person implements Comparable<Person> {
	private String name;
	private int age;
	
	public Person(String name, int age) {
	this.name = name;
	this.age = age;
	}

	// Comparable 인터페이스의 compareTo 메서드를 구현
	@Override
	public int compareTo(Person otherPerson) {
	// 나이를 기준으로 비교
		return Integer.compare(this.age, otherPerson.age);
	}

	@Override
	public String toString() {
		return "Person{" +
		"name='" + name + '\'' +
		", age=" + age +
		'}';
		}
}

템플릿 메서드 패턴

  • 인터페이스와 추상 골격 구현(skeleton implementation) 클래스를 함께 제공하는 식으로 인터페이스와 추상 클래스의 장점을 모두 취할 수 있다.
    • 추상 골격 구현 클래스는 구현을 도와주는 동시에, 추상 클래스에서의 타입 제약에서는 자유롭다.
  • 인터페이스로는 타입을 정의하고 필요한 디폴트 메서드를 함께 제공하고, 골격 구현 클래스에서 나머지 메서드들까지 구현한다.
// 템플릿 메서드 패턴 예제  
  
// 1. 인터페이스 정의  
interface Task {  
    // 타입을 정의하는 메서드  
    void initialize();  
  
    // 추상 메서드 - 하위 클래스에서 구현해야 함  
    void performTask();  
  
    // 디폴트 메서드 - 필요한 경우에 하위 클래스에서 오버라이드 가능  
    default void wrapUp() {  
        System.out.println("Task completed!");  
    }  
  
    // 템플릿 메서드 - 구현이 완료된 메서드  
    default void execute() {  
        initialize();  
        performTask();  
        wrapUp();  
    }  
}  
  
// 2. 골격 구현 클래스 정의  
abstract class AbstractTask implements Task {  
    // 인터페이스에서 정의한 메서드 중 일부를 구현  
    @Override  
    public void initialize() {  
        System.out.println("Initializing task...");  
    }  
  
    // performTask 메서드는 하위 클래스에서 구현해야 함  
    // 나머지 메서드들은 디폴트 또는 부분적인 구현을 가질 수 있음  
}  
  
// 3. 구체적인 작업 클래스 정의  
class ConcreteTask extends AbstractTask {  
    // performTask 메서드를 구체적으로 구현  
    @Override  
    public void performTask() {  
        System.out.println("Performing the task...");  
    }  
  
    // wrapUp 메서드를 필요에 맞게 오버라이드 가능  
    @Override  
    public void wrapUp() {  
        System.out.println("Custom wrap-up for the task!");  
    }  
}  
  
// 4. 메인 클래스  
public class TemplateMethodPatternExample {  
    public static void main(String[] args) {  
        // 구체적인 작업을 수행하는 객체 생성  
        Task task = new ConcreteTask();  
  
        // 템플릿 메서드 실행  
        task.execute();  
    }  
}

위 코드의 실행 결과는 다음과 같다.

  1. 인터페이스 정의: Task 인터페이스는 initialize, performTask라는 두 개의 메서드를 정의하고, wrapUpexecute라는 디폴트 메서드도 함께 제공한다.

  2. 골격 구현 클래스 정의: AbstractTask 클래스는 Task 인터페이스를 구현하면서 initialize 메서드를 일부 구현하고, 나머지는 추상 메서드로 남겨둔다.

  3. 구체적인 작업 클래스 정의: ConcreteTask 클래스는 AbstractTask를 상속하면서 performTask 메서드를 구체적으로 구현하고, wrapUp 메서드를 필요에 맞게 오버라이드한다.

  4. 메인 클래스: ConcreteTask 객체를 생성하고 execute 메서드를 호출하여 전체 작업을 실행한다.

default 메서드

  • 디폴트 메서드를 제공하여 반복되는 코드의 작성을 줄일 수 있다.
  • 디폴트 메서드 작성시에는 상속받는 메서드 작성을 위한 설명을 @implSpec 자바독 태그를 붙여 문서화해야 한다.
  • equals, hashcode는 디폴트 메소드로 사용해서는 안된다.

시뮬레이트한 다중 상속 (Simulated multiple inheritance)

  • 골격 구현 클래스의 우회적 이용이다.
  • 인터페이스를 구현한 클래스에서 해당 골격 구현을 확장한 private 내부 클래스를 정의하고, 각 메서드 호출을 내부 클래스의 인스턴스에 전달한다.
// 템플릿 메서드 패턴으로 시뮬레이트한 다중 상속 예제

// 1. 인터페이스 정의
interface Task {
    void initialize();

    void performTask();

    default void wrapUp() {
        System.out.println("Task completed!");
    }

    default void execute() {
        initialize();
        performTask();
        wrapUp();
    }
}

// 2. 골격 구현 클래스 정의
abstract class AbstractTask implements Task {
    @Override
    public void initialize() {
        System.out.println("Initializing task...");
    }
}

// 시뮬레이트한 다중 상속을 위한 private 내부 클래스 정의
class InnerAbstractTask extends AbstractTask {
    // InnerAbstractTask를 확장하여 필요한 메서드를 구현
    @Override
    public void performTask() {
        System.out.println("Performing the task...");
    }

    // InnerAbstractTask에 새로운 메서드 추가 가능
    public void customWrapUp() {
        System.out.println("Custom wrap-up for the task!");
    }
}

// 3. 구체적인 작업 클래스 정의
class ConcreteTask implements Task {
    // InnerAbstractTask를 private 필드로 선언하여 사용
    private InnerAbstractTask innerTask = new InnerAbstractTask();

    @Override
    public void initialize() {
        innerTask.initialize();
    }

    @Override
    public void performTask() {
        innerTask.performTask();
    }

    @Override
    public void wrapUp() {
        innerTask.customWrapUp();
    }
}

// 4. 메인 클래스
public class TemplateMethodPatternExample {
    public static void main(String[] args) {
        // 구체적인 작업을 수행하는 객체 생성
        Task task = new ConcreteTask();

        // 템플릿 메서드 실행
        task.execute();
    }
}

Leave a comment