치즈 돈카츠에 담긴 Interface의 개념

Interface와 OOP with Java — 김성렬, Backend Developer

딜리버리히어로코리아

식당에는 메뉴(Object)을 바라보는 세가지 시선이 존재한다.

간단한 내용이지만 정리를 해볼까 한다.

0. Interface란 무엇인가

객체지향 프로그래밍의 꽃은 추상화(Abstraction)다.

추상화를 함으로써 중복된 소스코드를 없애고

느슨한 결합도 (Loose Coupling)을 유지 할 수도 있다.

나아가 소스코드의 품질을 결정하는, 기본기인 동시에 끝이 되는 개념이다.

그리고 인터페이스는 여러 프로그래밍 언어들이 추상화라는 OOP 개념을 더 적절하게 사용하기 위해 문법적으로 지원해주는 논리적 도구이다.

그러나 Interface는 프로그래밍에 입문하는 사람들에게는 그 사용방식은 감을 잡기 힘든 내용이다.

책에서 중요하다고 해서 일단 만들어서 사용해 보기는 하지만 자바와 C++ 같은 언어에서 인터페이스가 주는 문법적 제약에 걸리고 자신이 의도한 설계와 다르게 적용되서 오히려 기능 구현에 고통받기도 한다.

이 글에서 인터페이스를 사용하는 두 가지 방법을 소개하려 한다.

대부분의 전산학 지식이 그렇듯 인터페이스 또한

현실 세계에서 자연스럽게 쓰이고 있는 개념이라는 것을 알아야 한다.

식당의 메뉴판과 요리책이 대표적인 예시이다

1. Interface는 Object를 바라보는 다양한 시각이다.

식당을 찾는 손님들은 메뉴판을 보고 음식을 주문한다. 메뉴판을 보는 손님들이 원하는 정보는 “어떤 음식을 먹을까?”이지 그 이상의 복잡한 정보를 원하지 않는다.

손님들에게 치즈 돈카츠는 메뉴일 뿐이다.

그러나

셰프들은 요리를 만들기 위해서 치즈돈까스를 불에 얼마나 굽고 어떤 소스를 만들고 숙성을 얼마나 하는지,? 스파게티는 얼마나 오래 삶고 토마토를 어떻게 조리해야 하는지와 같은 복잡한 정보를 원한다.

셰프들에게 치즈 돈카츠는 요리다.

그러나

식당 주인 입장은 또 다르다. 그들에게 치즈 돈카츠는 판매가 8900원이면서 재료비(3900)+인건비(2500)+임대료(1500) + 순이익(1000)인 수익을 내기 위한 상품이다.

식당 주인은 음식은 매출과 수익을 내는 상품이다

식당에는 치즈돈카츠(Object)을 바라보는 세가지 시선이 존재한다.

치즈 돈카츠는 메뉴다(손님)

치즈 돈카츠는 요리다 (셰프)

치즈 돈카츠는 매출과 수익을 내는 상품이다(식당주인)

이것을 자바로 아래와 같이 표현할수 있다.

public class 음식 implements 메뉴, 상품, 요리 { private String 음식이름; private List<재료> 재료목록; private int 판매가; .... }

public interface 메뉴 { String 메뉴소개멘트_보기(); int 판매가격은_얼마죠(); String 이메뉴와_잘어울리는_메뉴들보기(); }

public interface 상품 { int 마진률_보기(); int 상품의_재료비는_얼마(); int 상품의_총매출대비_비율은_얼마(); }

public interface 요리 { String 요리법을_알려주세요(); String 계절메뉴라서_지금_조리_불가능한가요(); String 쎈불에짧게익혀야하는음식인가요(); }

그리고 각 서로 바라보는 시선들은 이렇게 이해할수 있다.

public static void main(String[] args) { 음식 치즈돈카츠 = new 음식(); 메뉴 치돈_메뉴판 = 치즈돈카츠; 요리 치돈_조리법 = 치즈돈카츠; 상품 치돈_상품 = 치즈돈카츠; // 손님 치돈_메뉴.이메뉴와_잘어울리는메뉴들보기(); 치돈_메뉴.판매가격은_얼마죠(); // 셰프 치돈_조리법.계절메뉴인가요(); 치돈_조리법.쎈불에짧게익혀야하는음식인가요(); // 식당 주인 치돈_상품.마진률_보기(); 치돈_상품.상품의_재료비는얼마(); }

현실세계에서도 같은 사물을 서로 다른 시각으로 볼 수 있듯이 (치즈 돈카츠는 상품이고 요리인 동시에 메뉴이다)

프로그래밍의 세계에서도 같은 객체에 대해 서로 다른 시각으로 볼 수 있어야 한다.

똑같은 객체이지만 서로 바라보는 시선이 다르다는 것을 프로그래밍의 세계에서 Interface로 이렇게 표현할수 있다.

2. Interface는 여러구현체(Impl)를 묶는 개념이다.

앞에 내용을 다 잊어버리고 처음으로 돌아와서 생각하자

음식의 조리법에는 여러 가지 방식이 존재한다.

치즈 돈카츠 라는 음식은 불에 얼마나 굽는지 어떤 조미료를 사용 하는지에 따라 다양한 치즈 돈카츠를 만들 수 있다.

식당마다 치즈 돈카츠를 구현하는 방식은 다양하다.

그러나 메뉴판에서 우리는 그것을 치즈돈카츠라고 부른다.

어떤 식당에서는

칠레산 조미료를 사용하고 센 불에 3분 30초 동안 조리한 치즈 돈카츠 주세요 라고 주문하고

또 다른 식당에서는 국내산 조미료를 사용하고 중간 불에 4분 27초간 조리한 치즈 돈카츠 주세요 라고 주문하지 않는다

그냥 치즈 돈카츠 하나요~ 이렇게 주문한다

Class 가 두껍고 복잡한 내용이 담긴 조리법 이라면 Interface는 1~2장으로 이루어진 가벼운 메뉴판이다.

각 식당마다 다양한 조리법이 존재하지만

우리는 그것을 호출할때 “치즈돈카츠” 라고 호출한다.

손님들은 치즈돈카츠가 불 위에서 어떤 방식으로 익혀지는지 몇 분 익히는지 같은 세세한 정보는 알고 싶지 않다.

그저 맛있는 치즈 돈카츠가 먹고싶어서 간단히 적힌 메뉴판을 보고

“치즈 돈카츠 1인분 주세요~” 라고 주문할 뿐이다.

이것을 자바로 표현하자면 아래와 같다.

public interface 요리 { String 요리법_보기(); }

public class B식당요리법 implements 요리법 { @Override public String 요리법_보기() { return "칠레산 조미료를 사용하고 센불에 3분30초동안 조리한 치즈돈카츠"; } }

public class A식당요리법 implements 요리법 { @Override public String 요리법_보기() { return "국내산 조미료를 사용하고 중간불에 4분27초간 조리한 치즈돈카츠"; } } .... Class를 더 만들어서 더 많은 조리법을 구현할수 있다

소프트웨어도 마찬가지다.

어떠한 라이브러리, API를 사용하는 사용자는 그 API가 얼마나 멋진 추상화가 적용되었고 다양한 기법들이 사용 되었는지 알고싶지 않다.

그저 그 API 또는 라이브러리를 통해 내가 구현하고자하는 기능에 편하게 쓰고싶을 뿐이다.

그렇기 때문에 우리는 메뉴판을 제공하듯이 인터페이스를 제공한다.

치즈돈까스의 요리법이 수정 되었다고 메뉴판에서 무엇인가 수정되지 않듯

구현체가 바뀌었다고 인터페이스가 수정되지 않는다.

JDK로 알아보는 Interface 사용 예시

List list=new ArrayList(); list.add(“HI”); list.add(“yogiyo”); list.add(“KSR~!”);

다음 예제를 보자 위 예제는 ArrayList에 3개의 문자열을 넣는 소스코드이다

주목할 점은 인스턴스 list 의 선언을 왜 ArrayList 로 하지 않고

List 으로 선언했는가? 이다.

ArrayList list=new ArrayList(); list.add(“HI”); list.add(“assignment”); list.add(“yea~!”);

이렇게 ArrayList로 선언 해도 돌아가는데 아무런 문제가 없지만

좋은 프로그래머라면 전자방식을 선호하며 반드시 그래야만 한다.

자바언어를 창시한 제임스 고슬링은 리스트라는 자료구조를 설계할 때

리스트의 구현 방식이 리스트를 선언하는데 영향을 주고 싶지 않았을 것이다. (진짜 설계는 다른 사람이 했겠지만 편의상..)

자료구조 리스트는 두가지 구현방식을 가진다 (Array & Linked) 하지만 두가지 구현방식 모두 동작결과는 같다.

마치 A식당과 B식당에서 치즈 돈카츠를 호출하면 똑같은 치즈 돈카츠가 나오는 것처럼

그래서 List를 interface로 선언했다.

List를 Interface 선언 함으로서 우리는 아래와 같은 소스코드 변경이 가능하다

Interface List로 선언하면 위와같이 자유로운 구현체 변경이 가능하다(Array & Linked)

구현체 자체로 선언하면 위와 같이 다른 구현체로 변경이 불가능하다

참고로 이것은 Strategy Pattern (전략패턴) 이라 부르는 디자인 패턴이다.

하나의 기능을 구현하는 여러가지 전략(알고리즘)이 있으면 이것을

인터페이스로 선언부를 작성하고 여러가지 구현부를 클래스로 작성하여

교체하기 편리하게 하는 것이다.()

인터페이스를 사용함으로써 위와 같은 설계상 이점을 가질수 있다.

1, 2 번 둘다 똑같이 인터페이스를 설명하는 예제이지만

무엇을 객체로 잡았는지, 어떤 개념을 중점적으로 소스코드를 짰는지 등등 두가지 사용방식에서 “Interface를 사용했다” 말고는 공통된 것이 없다.

그 이유는 Interface는 추상화를 하는데 사용되는 도구이기 때문이다.

숫가락이라는 도구는 밥을 뜨면 밥먹는 도구지만 그것을 탈옥도구로도 사용할수 있는 것처럼

추상화를 하는데 Interface라는 개념적 도구를 서로 다른 방법으로 사용했기 때문이다.

그리고 개발 당사자에 따라, 개발 요구사항에 따라 설계되는 방식은 다양하다.

“아니 1번에서는 요리가 Class인데 왜 2번에서는 Interface야?”

라고 혼란스러워하지말자.

개발 당사자의 의도, 설계, 요구사항 분석에 따라 언제든지 다양하게 해석될 수 있다는 것을 알려주고 싶어서 일부로 같은 도메인을 예제로 사용했다.

실제로 위 내용들은 Java의 유명 프레임워크들에 자연스럽게 녹아있다.

SpringSecurity의 UserDetails 와 UserDetailService가 1번 방식으로 interface가 사용되었고 (사용자라는 Obj에서 인증에 필요한 사용자의 시점을 인터페이스로 분리)

Mybatis @Mapper 또는 DaoImpl 같은 키워드들이 2번 방식으로 사용되며

JPA Repository + Impl , 하이버네이트의 각 DB Dialect들도 2번의 방식이 사용되었다.

1,2,3 에 적어 놓은 키워드들로 구글링을 하면 정확히 어떻게 사용되었는지에 대한 세세한 정보를 얻을수 있을 것이다.

비록 난해하고 서투른 글쓰기 실력때문에 보기 어렵지만 나의 쓸모없는 지식이 누군가에게 도움이 되었으면 좋겠다.

김성렬, Backend Developer @Delivery Hero Korea

기업문화 엿볼 때, 더팀스

로그인

/