Java

파라미터 타입이 다른 공통 로직 모듈화 - 어댑터 패턴

99duuk 2024. 12. 11. 13:59

그리고 다시 이어서 

2024.12.11 - [Java] - 파라미터 타입이 다른 공통 로직 모듈화 - 인터페이스

 

파라미터 타입이 다른 공통 로직 모듈화 - 인터페이스

파라미터 타입이 달라 코드 중복이 발생하는 문제"를 인터페이스를 통해 해결하는 것을 간단한 예로 다시 정리해둔 것임...Animal 인터페이스를 활용하여 공통 로직을 모듈화하고,파라미터 타입

99duuk.tistory.com

 


기존 코드를 수정하기 두렵거나.. 다양한 타입의 객체를 통합된 방식으로 처리해야 하는 상황이라면 어댑터 패턴 또한 고려해볼만할지도? 

(맞는지모름)

 


1. 어댑터 패턴의 기본 개념

어댑터 패턴은 두 가지 시스템(또는 클래스)이 호환되지 않을 때,

중간에 어댑터 클래스를 만들어 서로를 연결하는 디자인 패턴임


이 패턴은 특정 인터페이스를 구현하면서, 기존 객체를 해당 인터페이스에 맞춰주는 방식으로 작동함..


문제 상황

  • 전제 조건:
    기존 코드에서 Dog, Cat, Bird 클래스가 이미 존재하지만, 이 클래스들을 직접 수정할 수 없음 
    (Dog, Cat, Bird 클래스에 getName()과 getAge() 같은 공통 메서드를 추가할 수 없는 경우)
  • 목적:
    printAnimalInfo(Animal animal)와 같은 공통 메서드를 작성하여 이름과 나이를 출력하고 싶음.

2. 어댑터 패턴을 활용한 설계

(1) Animal 인터페이스 정의

마찬가지로 모든 동물을 통합된 방식으로 다루기 위해 Animal 인터페이스를 정의

public interface Animal {
    String getName();
    int getAge();
}

(2) 어댑터 클래스 작성

각 동물 클래스에 대응하는 어댑터 클래스를 작성하여, 기존 클래스와 Animal 인터페이스를 연결

public class DogAdapter implements Animal {
    private final Dog dog;

    public DogAdapter(Dog dog) {
        this.dog = dog;
    }

    @Override
    public String getName() {
        return dog.getName();
    }

    @Override
    public int getAge() {
        return dog.getAge();
    }
}

public class CatAdapter implements Animal {
    private final Cat cat;

    public CatAdapter(Cat cat) {
        this.cat = cat;
    }

    @Override
    public String getName() {
        return cat.getName();
    }

    @Override
    public int getAge() {
        return cat.getAge();
    }
}

public class BirdAdapter implements Animal {
    private final Bird bird;

    public BirdAdapter(Bird bird) {
        this.bird = bird;
    }

    @Override
    public String getName() {
        return bird.getName();
    }

    @Override
    public int getAge() {
        return bird.getAge();
    }
}

(3) 공통 메서드 작성

1. AnimalAuthProcessor와 같은 유틸리티 클래스

printAnimalInfo는 다양한 동물 객체의 이름과 나이를 출력하는 공통 로직을 담당하므로, 이를 유틸리티 클래스에 작성하는 것이 적합

public class AnimalAuthProcessor {
    public static void printAnimalInfo(Animal animal) {
        System.out.println("Name: " + animal.getName());
        System.out.println("Age: " + animal.getAge());
    }
}
// 호출
        AnimalAuthProcessor.printAnimalInfo(dogAdapter);
        AnimalAuthProcessor.printAnimalInfo(catAdapter);

 

 

  • 유틸리티 클래스:
    • 프로젝트 전반에서 공통으로 호출할 메서드라면, AnimalAuthProcessor 같은 유틸리티 클래스에 작성.
    • 동물 객체의 처리와 관련된 작업이 많을 경우 적합.

2. Animal 인터페이스나 관련 클래스의 구현체

printAnimalInfo 메서드는 특정 동물 객체의 이름과 나이를 출력하는 기능이므로,

이를 Animal 인터페이스의 디폴트 메서드로 포함하거나, 관련 클래스의 구현체에 작성할 수도 있음

Animal 인터페이스에 디폴트 메서드로 작성

public interface Animal {
    String getName();
    int getAge();

    default void printAnimalInfo() {
        System.out.println("Name: " + getName());
        System.out.println("Age: " + getAge());
    }
}
    // 호출
    	dogAdapter.printAnimalInfo();
        catAdapter.printAnimalInfo();
  • Animal 인터페이스:
    • 호출이 더 간단해지고, Animal 인터페이스의 역할이 명확해짐
    • printAnimalInfo가 모든 Animal 구현체에서 필수적으로 제공되어야 하는 공통 동작이라면, 인터페이스 디폴트 메서드로 작성.
    • 어댑터 클래스에서 바로 호출 가능.

 

 


3. 각 어댑터 클래스에서 메서드로 작성

printAnimalInfo 메서드를 각 어댑터 클래스에 작성하여, 어댑터마다 독립적으로 공통 로직을 제공할 수도

public class DogAdapter implements Animal {
    private final Dog dog;

    public DogAdapter(Dog dog) {
        this.dog = dog;
    }

    @Override
    public String getName() {
        return dog.getName();
    }

    @Override
    public int getAge() {
        return dog.getAge();
    }

    public void printAnimalInfo() {
        System.out.println("Name: " + getName());
        System.out.println("Age: " + getAge());
    }
}
	// 호출
	dogAdapter.printAnimalInfo();
  • 각 어댑터 클래스:
    • printAnimalInfo가 특정 어댑터에서만 사용되는 기능이라면, 해당 어댑터 클래스에 작성. (더 명확한 책임 분리)
    • 어댑터마다 별도의 추가 로직이 필요한 경우 적합.

  • 전역적으로 공통 작업을 처리하려면, 유틸리티 클래스에 
  • 모든 Animal 구현체에서 반드시 공통으로 필요한 동작이라면, 인터페이스 디폴트 메서드에 
  • 특정 어댑터에만 관련된 작업이라면, 해당 어댑터 클래스에 작성

 


3. 어댑터 패턴의 장점

  1. 기존 코드 수정 없이 통합
    • Dog, Cat, Bird 클래스는 수정하지 않고도 Animal 인터페이스와 호환되도록 만들 수 있
    • 외부 라이브러리나 레거시 코드와 통합할 때 특히 유용
  2. 확장성
    • 새로운 동물 클래스(Fish, Rabbit)가 추가되더라도, 새로운 어댑터만 작성하면 기존 코드와 호환
  3. 유연성
    • 어댑터를 통해 인터페이스와 구현체 간의 결합도를 낮출 수 있

 



5. 결론

  • 인터페이스 방식이 더 나은 경우:
    • 새롭게 설계한 코드라면, Animal 인터페이스를 구현하여 공통 로직을 처리하는 방식이 간결하고 자연스럽
    • 다형성을 적극 활용할 수 있으며, 공통 동작 강제가 필요할 때 적합
  • 어댑터 패턴이 더 나은 경우:
    • 기존 클래스(Dog, Cat, Bird)를 수정할 수 없는 경우.
    • 외부 라이브러리 코드나 레거시 코드를 통합해야 하는 경우.

'공통 로직 처리'만이 목적이라면 인터페이스 방식이 더 간단하고 적합하지만,

코드 수정 제약이나 외부 시스템과의 통합이 필요하다면 어댑터 패턴이 더 좋은 선택


 

 

정적 팩토리 메서드보단 어댑터쪽이 더 그럴싸해보임...