[Spring] Interface, AbstractClass의 차이점과 ServiceImpl 패턴을 쓰는 이유.

kshired·2022년 1월 2일
2

Spring

목록 보기
8/11
post-thumbnail

이 글은 저의 주관적인 생각이 지극히 많이 포함되어있습니다만.. 다른 사람의 생각을 한 번 살펴보는 것도 좋겠죠?

Interface ?

interface는 자바에서 사용되는 클래스의 "기본 틀"이라고 생각하면 됩니다.

다른 클래스를 작성할 때 기본이 되는 틀을 제공하고, 다른 클래스 사이의 중간 매개 역할을 하는 일종의 추상 클래스와 유사한 역할을합니다.

인터페이스는 오로지 추상 메소드와 상수만을 포함합니다.

Interface vs Abstract Class

인터페이스와 추상 클래스의 차이가 무엇일까요?

추상클래스도 살펴보면 상속을 통해서 구현되는 구체 클래스가 추상 메소드를 구현하는 것을 강제하는데, 인터페이스와 달리 생성자, 필드, 일반 메소드도 포함할 수 있습니다.

또한 추상 클래스이기 때문에, 다중 상속을 지원하지 않는 자바의 특성상 인터페이스는 몇 개든 상속을 할 수 있지만 추상 클래스는 다중 상속이 불가능합니다.

하지만, 여기까지 알아본 차이점은 그냥 정말 구조적인 특징이고.. 실제 의미론상(존재론상) 차이점을 알아봅시다.

일단 사람들이 많이하는 오해 중 하나가 "인터페이스는 다중 상속이 안되는 추상 클래스를 인터페이스의 다중 상속으로 보완한다." 라는 오해입니다.

일단 두 추상 클래스와 인터페이스는 존재 목적부터 다릅니다. ( 당연히 두 개가 존재하는 이유가 있겠죠? )

추상 클래스는 그 추상 클래스를 상속 받아서 기능을 이용하고 확장하는 것에 의의를 둡니다. 하지만 그에 반해 인터페이스는 껍데기만 있는 그 함수의 구현을 강제하는데 사용을하게 됩니다. 이러한 강제적인 구현은 구현된 객체가 같은 이름의 메서드를 통해 ( 구현은 다르지만 역할은 같은 ) 동일한 동작을 보장하게됩니다.

결국 추상 클래스는 그 추상 클래스를 기본 틀으로해서 확장을 할 때 쓰라는 것이고,
인터페이스는 이미 정의된 인터페이스에 맞추어서 만들게 한다는 것입니다.

그래서 이것을 정리해보면 아래와 같습니다.

  • 인터페이스
    • 인터페이스는 어떠한 설계도입니다.
    • 이러한 설계도에 우리는 맞추어서 규칙으로써 인터페이스에 맞추어 개발하고, 그 규칙에 맞추어 개발하게 되면 우리는 내부 구현을 알 필요도 없이 그냥 인터페이스의 함수가 잘 동작할 것이라 생각하고 호출하면 됩니다.
  • 추상 클래스
    • 부모 클래스에서 정의한 동작을 자식 클래스에서 구현하고 그것을 자식이 구현하고 확장합니다.
    • 인터페이스와 달리 다른 기능도 확장하고 추가할 수 있습니다.

Service Interface와 Service Implements 패턴

왜 interface를 사용하여 미리 함수의 원형을 정의하고 그것을 impl한 class를 구체클래스로 구현을 하는가?

우리는 스프링을 사용하다보면 아래와 같은 패턴을 자주 사용합니다.

  • UserService ( interface )
  • UserServiceImpl ( class )

이 패턴을 사용할 때 보통 여러 사람들은 이러한 얘기를 합니다. "인터페이스를 규약해놓고 그것에 맞추어 여러 구현체를 구현하고, 다른 클래스에서 사용할 때 실제 구현체에 상관없이 개발할 수 있다는 OOP적 관점에서 사용한다."

여기서 질문을 하나 던져본다.

근데.. 여러분은 Service, ServiceImpl 구조를 사용할 때 정말 한 Service Interface를 implements 하는 여러 Service 구현체를 만든는 경우가 많았나요?

대부분의 사람들은 "아니요" 라는 대답을 할 것입니다.

실제로 한 커뮤니티에서 위와 같은 Service, ServiceImpl 구조에 대해서 이야기를 했을 때, "관습 때문에", "그냥 이렇게 쓰니까" 라는 이유를 들며 개발을 진행했다는 의견이 다수였습다.

만약에 그러한 이유로 현재 Service, ServiceImpl 구조를 쓰고 있다면 그 구조를 갖다 버립시다.

그럼 이걸 어떻게 써야하는데요?

이러한 패턴을 사용할 때 우리가 얻을 수 있는 장점은 아까 위에서 설명하였듯이, 인터페이스라는 규약에 맞추어 개발되었기에 우리는 Impl 클래스의 실제적 구현을 상관하지 않고 구현할 수 있다는 것입니다.

실제로 저는 혼자서 프로젝트를 할 때는 한 domain에 관해 개발을 할 때, 미리 Service의 각 interface를 정의하고 Controller를 개발하고 실제 Impl한 클래스의 구현을 제일 마지막으로 미룹니다.

그럼 Impl 클래스를 실제로 구현할 때까지 실제로 동작하지 않는데 뭘 한다는거에요?

믿는겁니다. ( Amen.. )

뭘 믿느냐고 물어보면, 실제 제가 그 구체 클래스를 구현했을 때 인터페이스 규약에 맞추어 로직을 잘 구현했다고 믿는겁니다.

그렇게 되면, Controller 단에서는 ServiceImpl이 규약에 맞추어 잘 구현했을테니까(할꺼니까) 저는 단지 믿고 인터페이스의 함수를 가져다가 개발을 진행합니다.

그 후 ServieImpl의 실제 구현을 하는것이죠.

이렇게 되면, 우리는 정말 구체 클래스에 의존하지않고 개발을 할 수 있습니다.

Service 구체 클래스가 어떤 내부 로직으로 동작하는지 관심을 가질 필요도 없고, 그것을 사용하는 Controller 클래스는 그냥 함수를 가져다 쓰면 되는 겁니다.

이러한 이유로 저는 Service, ServiceImpl 패턴을 자주 사용하며 애용하고 있습니다. 물론 사람마다 각각의 이유가 있을 것이고, 제 생각도 틀렸다고 할 수 있습니다. ( 그러한 반박이 있다면 항상 환영이니 댓글로 달아주세요! )

마치면서

오늘은 Interface vs Abstract Class 그리고 ServiceImpl 패턴에 대해 알아보았습니다. 이러한 관점에 대한 글은 제가 쓴 글 외에도 인터넷에 만연해있으니 한 번 살펴보는 것을 추천드립니다.

그리고 간단하게라도 하나 더 강조 드리고 싶은 점은, Interface, Abstract Class는 단순히 다중상속이라는 차이에 의해서 사용되는 방식이 다른게 아니다라는 것을 한 번 더 언급드리고 싶으니.. 이것을 상기하시면 도움이 될 것이라고 생각합니다.

profile
글 쓰는 개발자

1개의 댓글

comment-user-thumbnail
2022년 2월 11일

interface는 어떠한 설계도라고 하셨는데,
그럼 class는 어떠한 설계도가 아닌가요 => 이렇게 되면 거의 비슷하게 설명이 가능하게 되버리네요.

제가 생각하는 interface는 역할이라고 생각합니다.
여기서는 어떠한 Service 에 대한 역할을 규정하거나 규약 하는 것이죠.
그리고 Service, ServiceImpl 을 사용하는 이유가 의존성을 떨어뜨리려고 하는 것이라고 생각합니다.
관심사를 분리하고, 구현체에 의존성을 없앰으로써 변경사항이 생겼을 때 클라이언트 코드를 변경하지 않도록 하는 것이죠.

OOP적인 관점에서는, 클라이언트와 상호작용하는 interface(접속부)는 최대한 감추고 코드의 재사용성을 극대화함이 목적입니다. => 여기서 말하는 interface는 자바 interface가 아닙니다.
재사용성을 극대화 하기 위해서는 클라이언트 측(이 글에서는 Controller가 되겠죠)에서는 구현체를 몰라야 합니다.
언제든 구현체를 갈아끼울 수 있으려면 당연히 추상화에 의존해야하구요. => SOLID 중 DIP
확장과 변경에 유연해지는 것도 가장 큰 장점이라고 생각합니다.

이 문제는 OOP적인 관점이 꼭 필수로 들어가야한다고 생각해요.
스프링을 사용 한다는 것 자체가 내가 OOP 개 굇수다라고 생각하면서 써야하는 프레임워크라 생각하기 때문입니다
(몰라도 어느정도는 OOP적이게 짰다고 할 수 있어요 왜냐하면 스프링이 그렇게 돼있거든요)

kshired님 글 잘봤습니다~

답글 달기