인터페이스는 객체지향 프로그래밍에서 매우 중요한 개념입니다. 이를 활용하면 코드의 유연성과 확장성을 높일 수 있습니다. 아래에서는 인터페이스를 어떻게 활용할 수 있는지에 대한 다양한 예제와 개념을 설명해 드리겠습니다.
1. 다형성 (Polymorphism)
인터페이스는 다형성을 구현하는 강력한 도구입니다. 다형성은 같은 인터페이스를 구현하는 여러 객체가 동일한 메서드를 다른 방식으로 구현할 수 있게 해줍니다.
예제: 동물 소리
interface Animal {
    void makeSound();
}
class Dog implements Animal {
    @Override
    public void makeSound() {
        System.out.println("Woof");
    }
}
class Cat implements Animal {
    @Override
    public void makeSound() {
        System.out.println("Meow");
    }
}
public class Main {
    public static void main(String[] args) {
        Animal myDog = new Dog();
        Animal myCat = new Cat();
        myDog.makeSound(); // Woof
        myCat.makeSound(); // Meow
    }
}
- 이 예제에서 Animal인터페이스를 통해Dog와Cat클래스가 각각의 방식으로makeSound()메서드를 구현했습니다. 인터페이스를 사용하여Dog와Cat을 동일한 타입인Animal로 다룰 수 있으며, 각 클래스의 구체적인 구현은 런타임에 결정됩니다.
2. 코드의 느슨한 결합 (Loose Coupling)
인터페이스를 사용하면 코드의 결합도를 낮출 수 있습니다. 이는 시스템의 특정 부분을 쉽게 변경하거나 확장할 수 있게 합니다.
예제: 결제 시스템
interface PaymentProcessor {
    void processPayment(double amount);
}
class CreditCardProcessor implements PaymentProcessor {
    @Override
    public void processPayment(double amount) {
        System.out.println("Processing credit card payment of $" + amount);
    }
}
class PayPalProcessor implements PaymentProcessor {
    @Override
    public void processPayment(double amount) {
        System.out.println("Processing PayPal payment of $" + amount);
    }
}
class CheckoutService {
    private PaymentProcessor processor;
    public CheckoutService(PaymentProcessor processor) {
        this.processor = processor;
    }
    public void checkout(double amount) {
        processor.processPayment(amount);
    }
}
public class Main {
    public static void main(String[] args) {
        PaymentProcessor creditCard = new CreditCardProcessor();
        PaymentProcessor paypal = new PayPalProcessor();
        CheckoutService service1 = new CheckoutService(creditCard);
        service1.checkout(100.0); // Processing credit card payment of $100.0
        CheckoutService service2 = new CheckoutService(paypal);
        service2.checkout(200.0); // Processing PayPal payment of $200.0
    }
}
- 이 예제에서 PaymentProcessor인터페이스를 사용하여CreditCardProcessor와PayPalProcessor클래스가 각각의 결제 방식을 구현했습니다.CheckoutService는 결제 방식을 알고 있을 필요 없이PaymentProcessor인터페이스만 의존합니다. 결제 방식이 추가되더라도CheckoutService는 변경할 필요가 없습니다.
3. 전략 패턴 (Strategy Pattern)
인터페이스는 전략 패턴을 구현하는 데 자주 사용됩니다. 전략 패턴은 알고리즘을 캡슐화하여 동적으로 교체할 수 있게 해줍니다.
예제: 정렬 전략
interface SortingStrategy {
    void sort(int[] numbers);
}
class BubbleSort implements SortingStrategy {
    @Override
    public void sort(int[] numbers) {
        System.out.println("Sorting array using bubble sort");
        // Bubble sort algorithm...
    }
}
class QuickSort implements SortingStrategy {
    @Override
    public void sort(int[] numbers) {
        System.out.println("Sorting array using quick sort");
        // Quick sort algorithm...
    }
}
class Sorter {
    private SortingStrategy strategy;
    public Sorter(SortingStrategy strategy) {
        this.strategy = strategy;
    }
    public void sort(int[] numbers) {
        strategy.sort(numbers);
    }
}
public class Main {
    public static void main(String[] args) {
        int[] numbers = {5, 3, 8, 1};
        Sorter bubbleSorter = new Sorter(new BubbleSort());
        bubbleSorter.sort(numbers); // Sorting array using bubble sort
        Sorter quickSorter = new Sorter(new QuickSort());
        quickSorter.sort(numbers); // Sorting array using quick sort
    }
}
- 여기서 SortingStrategy인터페이스는 정렬 알고리즘을 정의하고 있습니다.BubbleSort와QuickSort클래스는 각각의 정렬 방식을 구현합니다.Sorter클래스는SortingStrategy인터페이스에 의존하며, 정렬 알고리즘을 유연하게 교체할 수 있습니다.
4. 템플릿 메서드 패턴 (Template Method Pattern)과 함께 사용
인터페이스는 템플릿 메서드 패턴에서 훅 메서드를 정의하는 데 사용될 수 있습니다. 이 패턴은 알고리즘의 골격을 정의하고, 일부 단계를 서브클래스에서 재정의할 수 있게 합니다.
예제: 요리 템플릿
interface Cooking {
    void prepareIngredients();
    void cook();
    void serve();
}
abstract class CookingTemplate implements Cooking {
    // 템플릿 메서드
    public final void prepareMeal() {
        prepareIngredients();
        cook();
        serve();
    }
    @Override
    public abstract void prepareIngredients();
    @Override
    public abstract void cook();
    @Override
    public abstract void serve();
}
class PastaCooking extends CookingTemplate {
    @Override
    public void prepareIngredients() {
        System.out.println("Preparing pasta and sauce ingredients");
    }
    @Override
    public void cook() {
        System.out.println("Cooking pasta and heating sauce");
    }
    @Override
    public void serve() {
        System.out.println("Serving pasta with sauce");
    }
}
class SteakCooking extends CookingTemplate {
    @Override
    public void prepareIngredients() {
        System.out.println("Preparing steak and seasoning");
    }
    @Override
    public void cook() {
        System.out.println("Grilling steak");
    }
    @Override
    public void serve() {
        System.out.println("Serving steak with vegetables");
    }
}
public class Main {
    public static void main(String[] args) {
        CookingTemplate pastaCooking = new PastaCooking();
        pastaCooking.prepareMeal();
        CookingTemplate steakCooking = new SteakCooking();
        steakCooking.prepareMeal();
    }
}
- 이 예제에서 CookingTemplate추상 클래스는 요리의 골격을 정의하고, 각 요리 단계는Cooking인터페이스를 구현하여 서브클래스에서 구체적으로 구현됩니다. 이 방법은 템플릿 메서드 패턴과 인터페이스를 결합하여 유연성을 높이는 데 사용됩니다.
5. 디자인 패턴과의 결합
인터페이스는 다양한 디자인 패턴에서 중요한 역할을 합니다. 전략 패턴, 템플릿 메서드 패턴 외에도 팩토리 패턴, 데코레이터 패턴 등에서도 인터페이스는 핵심적으로 사용됩니다.
결론
인터페이스는 객체지향 프로그래밍에서 코드를 더 유연하고 확장 가능하게 만드는 중요한 도구입니다. 인터페이스를 활용하면 다형성, 느슨한 결합, 전략 패턴 등 다양한 설계 패턴을 구현할 수 있으며, 이는 결국 코드의 유지보수성과 재사용성을 높이는 데 크게 기여합니다.
이제 인터페이스를 어떻게 활용할 수 있는지, 그리고 왜 중요한지 더 명확하게 이해할 수 있을 것입니다. 추가적으로 궁금한 부분이나 다른 예제가 필요하면 언제든지 질문해 주세요!
Share article