본문 바로가기

프로그래밍 언어/Java

Interface가 왜 필요할까?

우리는 프로그래밍을 하면서 Interface를 선언하고, 그 Interface를 구현하면서 프로그래밍을 진행한다.

 

그렇다면 왜 interface가 필요할까?

인터페이스의 정의는 다음과 같다.

인터페이스

모든 기능을 추상화로 정의만 하고 구현은 하지 않은것을 의미한다.

모든 기능을 추상화하였다는 의미이다. 그렇다면 추상화란 왜 필요할까?

 

예를 들어서, Payment System을 개발한다고 생각해보자.

Payment System에는 cashPay, cardPay가 존재한다.

class CashPayment {
    public void cashPay() {
        //TODO Payment logic...
    }
}

class CardPayment {
    public void cardPay() {
        //TODO Payment logic...
    }
}

두 가지 클래스가 존재한다. 각각 자신에게 맞는 Payment logic을 처리하는 class이다. 그렇다면 실제로 사용은 어떻게 될까?

 

class PaymentSystem {
    public void userPay(String type) {
        CardPayment cardPayment = new CardPayment();
        CashPayment cashPayment = new CashPayment();
        
        if (type.equals("card")) cardPayment.cardPay();
        else if (type.equals("cash")) cashPayment.cashPay();
    }
}

아마 다음과 같이 처리할것이다. 그렇다면 이러한 시스템에 QR을 이용한 Payment를 추가해야하 하는 상황이라면 어떨까 ?

 

class PaymentSystem {
    public void userPay(String type) {
        CardPayment cardPayment = new CardPayment();
        CashPayment cashPayment = new CashPayment();
        QrPayment qrPayment = new QrPayment();

        if (type.equals("card")) cardPayment.cardPay();
        else if (type.equals("cash")) cashPayment.cashPay();
        else if (type.equals("qr")) qrPayment.qrPay();
    }
}

위와 같이 하나의 Payment가 추가되는 현상이 발생할것이다. 이 경우 무수히 늘어날수 있으며 확장성에 있어서 취약한 포지션을 가지게 될것이다.

그렇기에 Interface가 졵재한다. 확장성과 유연성을 갖기 위해서

다음과 같이 공통적으로 사용하는 pay Method를 추출해보자.

interface Pay {
    public void pay();
}

그리고 이 Pay interface를 구현하는 3개의 Payment를 구축해보자.

class CardPayment implements Pay {
    @Override
    public void pay() {
        //TODO CardPayment logic
    }
}

class QrPay implements Pay {
    @Override
    public void pay() {
        //TODO QrPayment logic
    }
}

interface Pay {
    public void pay();
}

위와 같은 구조가 될것이다. 그렇다면 실제로 사용하는 곳에서는 어떻게 될까?

class PaymentSystem {
    Pay pay;
    
    public void userPay(String type) {
        if (type.equals("card")) {
            pay = new CardPayment();
        } else if (type.equals("cash")) {
            pay = new CashPayment();
        } else if (type.equals("qr")) {
            pay = new QrPayment();
        }
        
        pay.pay();
    }
}

위와 같은 구조로 변경이 된다.

이렇게 변경됨으로써 얻을수 있는 이득이 존재한다.

1. 메소드 내부에서 선언된 Payment 변수를 class 변수로 돌림으로써 재활용이 가능해지고 다른 메소드에서도 사용 가능하므로 확장성이 늘어난다.

2. 개발자는 기존에 생성과 각각의 상황에 맞는 Payment logic을 신경써야 했던 반면에 이제는 생성만 신경 쓰면 된다.

 

이러한 것이 가능하게 된 원인에는 결국 Pay라는 interface에 각각의 구현체들을 담을수 있는 자바의 다형성의 원리에 있다.

 

그렇다면 궁극적으로 우리가 왜 Interface를 사용하는거에 대한 해답을 찾을수 있다.

외부 세계는 변화한다. 그 말은 도메인 혹은 비즈니스를 그대로 투영시킨 시스템에도 변화가 언제든지 야기할수 있다는 의미이다. 

그렇기에 나온 개발방법론이 Agile이고 이 개발방법론은 주기적으로 변화를 시스템에 반영한다. 그리고 이러한 변화에 의해 시스템을 변경이 가능하게 만드는 것이 OOP의 핵심인 다형성의 원리이다.

 

다형성은 Interface나 혹은 class를 구현 상속함으로써 가능케 되는 것이고 이는 변화하는 환경을 시스템에 비교적 수월하게 반영할수 있게 해준다.

'프로그래밍 언어 > Java' 카테고리의 다른 글

GC(Garbage Collector)  (0) 2022.07.10
자바 Transactional  (0) 2022.07.10
동기화 처리 Synchronized  (0) 2022.06.12
Stream 확실히 알기  (0) 2022.06.12
Stream vs List  (0) 2022.06.12