본문 바로가기

서론

 

  자바로 애플리케이션을 개발할때, 인터페이스를 사용한다. 일반적으로 인터페이스에 대해서 설명을 하라고 하면 `기능에 대한 명세 집합` 이라고 표현하는 경우가 많다. 기능에 대한 명세를 모으기 위해서만 인터페이스를 사용하는가? 에 대해서 의문이 들었다. 인터페이스가 기능에 대한 명세 집합이라는 목적만 가지면 오히려 코드의 복잡성만 올라가는게 아닐까 생각이 든다. 인터페이스를 왜 사용해야하고 어떤 효과가 있는지 좀 더 깊이 알아보기 위해서 이 글을 작성하였다.

 

 

인터페이스 (Interface) ?

 

인터페이스의 본질을 알아보기전에 간단하게 OOP에 대해서 다시 정리하고 넘어가자.  OOP는 변경에 유연한 설계기법이다. OOP에서 자주 나오는 용어에는 다형성이 있다. `다형성`은 상속을 통해 기능을 확장하거나 변경하는 것을 가능하게 해주는 것을 의미한다. 다형성이 가능하도록 OOP세계에서는 상속,추상화,인터페이스라는 개념이 등장한다.

 

 

상속

정의는 부모 클래스의 모든 멤버를 하위 클래스가 물려 받는 것이다. 단, IS A 관계가 성립해야 한다. 아무런 규약 없이 상속을 하게 되면 객체간에 강한 결합을 가지게 된다.

추상화

데이터나 프로세스 등을 의미가 비슷한 개념이나 표현으로 정의해 나가는 과정을 의미한다. 각 개별 개체의 구체적인 구현에 대한 상세함은 갖추는 것을 의미한다.

 

인터페이스

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

 

 

상속과 인터페이스로 구현하는 다형성

 

상속과, 인터페이스는 어떤 방식으로 OOP에서 말하는 다형성을 가능하게 할까? 정답은 오버라이딩 이다. 상속을 받은 하위 클래스에서는 부모클래스의 메서드를 오버라이딩시켜서 다형성을 가능하게 한다. 인터페이스는 구현체클래스에서 인터페이스의 메서드들을 재정의하면서 다형성을 가능하게 한다.

 

 

상속보다는 인터페이스 구현을 사용하자

 

다형성을 위해서 2개의 선택권이 있는것이다. 둘중에 어떤걸 사용하는게 좋을까? 상속을 통해 다형성을 제공하면 자식클래스와 부모클래스는 강하게 결합되게 된다. 결합도는 변경에 유연하지 않게 하는 큰 장애물 이다. 왜 결합도가 올라갈까? 이유는 단순한다. 부모클래스를 상속받은 자식클래스에서는 부모클래스의 모든 메서드를 그대로 사용할 수 있다. 원치않은 메서드들도 상속되기 때문에 의도치 않은 버그도 발생시킬 수 있다.

 

인터페이스를 사용해서 프로그래밍을 하면 구체 클래스를 사용하지 않기 때문에 유연성이 올라간다. 예제를 통해서 확인해보자. 결제방식을 설계단계에서 추상화 시켜서 Pay라는 인터페이스를 생성했다. 결제 방식은 여러가지가 존재하는데 현재 서비스에서는 카드결제와 현금결제만 가능하다.

 

이제 Pay 인터페이스를 구현하는 구현체로 CardPay와 CashPay 구현클래스를 작성하면 된다. 만약, 새로운 결제방식으로 QR결제가 비즈니스 모델에서 추가가 되면 Pay 인터페이스를 구현하는 QrPay 구현클래스를 작성하면서 유연성을 올리게 된다.

 

public interface Pay {
	public void payment();
}


public class CardPay implements Pay {

	@Overriding
	public void payment() {

	}
    
}

public class CashPay implements Pay {

	@Overriding
	public void payment() {

	}
    
}


public class PaymentService {
	
    Pay pay;
    private final Stirng CREDIT_CARD = "카드결제";
    private final String CASH = "현금결제";
    
    public void process(String option) {
    	
        if(option.equals(CREDIT_CARD) {
        	pay = new CardPay();
        }
        else if(option.equals(CASH) {
        	pay = new CashPay();
        }
    	pay.payment();
    }
}

 

 

다른 예제를 확인해보자. 아래와 같이 구현된 코드가 있다. 갑자기 요구사항으로 조회를 더 빠르게 할 필요가 있음을 알게 되었다. 어떻게 해야할까 LinkedList를 HasMap으로 교체하면 된다. 하지만, f()메서드에서 modify()메서드를 호출하면서 매개변수로 list를 그대로 넘겨주고 이때 구체적인 클레스인 LinkedList를 그대로 받아온다. modify()도 수정해야하고 modify()의 doSomethingWith()메서드도 수정을 해야한다. 즉, 전혀 유연하지 않은 상태이다.

 

void f() {

	LinkedList list = new LinkedList();



	modify(list);

}



void modify(LinkedList list) {

	list.add();

	doSomthingWith(list);

}

 

 

LinkedList는 결국 Collection 인터페이스의 구현체이다. 인터페이스 기반 프로그래밍을 하면 아래오 같이 수정이 되고 f메서드에서 LinkedList부분만 HashMap() 구현체로 교체해주면 된다. 즉, 유연성이 올라간 프로그램이 된것이다.

void f() {

	Collection list = new LinkedList();



	modify(list);

}



void modify(Collection list) {

	list.add();

	doSomthingWith(list);

}

 

 

댓글