자바 진영의 추운 겨울과 스프링의 탄생
EJB 시대
2000년대 초반에 자바 진영 기술 중에서 가장 지배적인 기술 중 하나로 EJB(Enterprise Java Beans)이 있었다. 지금으로 따지면 스프링이랑 JPA를 모두 합쳐놓은 자바 종합 세트였으므로, 금융권이나 아키텍트 같은 분들이 많이 사용했다. 그래서 수많은 IT회사에서 EJB를 자바 진영에서 표준적으로 정한 기술이라고 홍보하며, EJB로 구현한 애플리케이션을 판매했다. 당시의 EJB는 컨테이너, 트랜잭션 관리, 분산에 대한 지원 등 고급 기술이 잘 지원하고 있었고, 이론적인 부분이 훌륭했다. 또한 당시에 소위말하는 ORM 기술(자바 객체를 DB에 편하게 저장하고 꺼내는 기술)도 갖고 있었다.
그러나 EJB를 사용하는데 굉장히 많은 비용이 들었고, 이론은 좋은 데에 비해서 어렵고 복잡하고 느렸다. 그 당시 개발자들은 EJB가 제공하는 인터페이스를 모두 구현하고, EJB에 의존적으로 개발해야했다. 그야말로 EJB는 개발자들의 지옥과 같았다. EJB 인터페이스를 의존적으로 설계하는 점이 특히 치명적이었는데, 코드가 너무 지저분해서 차라리 POJO로 돌아가자는 말이 나올 정도였다. 또한 당시에 ORM 표준이라고 할 수 있는 EJB에서 제공되는 엔티티빈 기술은 기술 수준 자체가 낮았다.
이때 개빈 킹(Gavin King)이라는 개발자가 있는 Hibernate를 만들고, 로드 존슨(Rod Johnson)이라는 개발자가 스프링 프레임워크를 만들었다. 두 기술은 모두 오픈소스였다.
로드 존슨은 EJB를 사용하는 과정에서 느꼈던 EJB의 단점을 비판하는 책을 출간했고, 이 책을 통해서 EJB보다 훨씬 단순하면서 좋은 방법으로 개발할 수 있는 방법을 제안했다. 그리고 이것이 미래에 스프링이 되었다.
개빈 킹이 EJB의 엔티티빈 기술을 대체할 Hibernate라는 오픈소스 기술을 만든 이후로 개발자들이 점점 엔티티빈 대신 Hibernate를 사용하기 시작하자, 자바의 표준을 논의하는 곳(자바 커뮤니티 프로세스)에서 개빈 킹을 섭외하여 Hibernate를 거의 모두 사용하여 만든 자바 표준인 JPA를 만든다. 자바 표준 기술인 JPA는 인터페이스고 구현체로 Hibernate나 EclipseLink 등을 사용할 수 있지만, 대부분 Hibernate를 사용한다. JPA는 프로그램 내에 잘 안착하여 자바 진영의 ORM 시장은 대부분 JPA가 장악하고 있으며, 그리고 구현체 시장에서는 80퍼센트 이상 Hibernate가 사용되고 있다.
Hibernate는 실무 개발자들이 직접 만든 것이므로, 실무에서의 필요성을 중점적으로 설계되었으므로 정제가 덜 된 감이 있다. 그런데 이 기술에다가 JPA라는 표준이 개입되면서 필요없는 것들을 덜어내는 과정을 통해 깔끔하게 정제되었다. 실무에서 오는 실용성과 표준에서의 오는 안정성이 합쳐져서 JPA는 성공적인 기술이 되었다.
스프링의 역사
로드 존슨은 J2EE Design and Development라는 책을 출간하면서 스프링의 역사가 시작된다. 이 책을 통해 로드 존슨은 EJB의 문제점을 모두 지적하면서 EJB가 없어도 충분히 고품질의 확장 가능한 애플리케이션 개발 방법을 3만 라인 정도의 예제 코드의 형태로 공개했다. 이 코드 안의 스프링의 핵심 개념이 모두 포함되어 있다. 이 책이 나오자마자 히트를 치고, 많은 개발자들이 이 예제 코드를 자신의 실제 프로젝트에 사용하기 시작했다.
책이 출간되고 나서 유겐 휠러(Juergen Hoeller)와 얀 카로프(얀 카로프) 개발자가 로드 존슨에게 오픈소스 프로젝트를 제안했다. 지금까지도 유겐 휠러는 스프링의 핵심 코드에 상당수 기여를 하고 있다. 스프링이라는 이름은 얀 카로프가 지었으며, EJB라는 겨울을 넘어 새로운 시작이라는 뜻으로 스프링이라고 지었다. 우리는 그 3만 라인의 예제 코드에 왜 그렇게 열광했고, 지금까지도 사용되고 있는 지에 대해서부터 알아볼 것이다.
릴리즈
- 2003년 스프링 프레임워크 1.0 - XML
- 2006년 스프링 프레임워크 2.0 - XML 편의 기능 지원
- 2009년 스프링 프레임워크 3.0 - 자바 코드로 설정
- 2013년 스프링 프레임워크 4.0 - 자바 8
- 2014년 스프링 부트 1.0 출시
- 2017년 스프링 프레임워크 5.0 / 스프링 부트 2.0 - 리엑티브 프로그래밍 지원
- 2020년 9월 현재 스프링 프레임워크 5.2.x, 스프링 부트 2.3.x
스프링이란?
스프링 생태계
스프링은 특정한 기술 하나가 아니라 여러가지 기술들의 모음이라고 볼 수 있다.
필수 기술
- 스프링 프레임워크 : 스프링의 핵심 기술
- 스프링 부트 : 다양한 스프링 기술을 편리하게 사용할 수 있도록 돕는 기술
선택 기술
- 스프링 데이터 : 데이터베이스와 상관없이 기본적인 CRUD 기능을 편리하게 사용할 수 있도록 도와주는 기술로 대표적인 것은 스프링 데이터 JPA가 있다.
- 스프링 세션 : 세션 기능을 편리하게 사용할 수 있도록 도와주는 기술
- 스프링 시큐리티 : 보안과 관련된 기능을 편리하게 사용할 수 있도록 도와주는 기술
- 스프링 Rest Docs : API 문서화를 편리하게 해주는 기술
- 스프링 배치 : 배치 처리에 특화된 기술
- 스프링 클라우드 : 클라우드에 특화된 기술
이외에도 굉장히 많은 기술들이 있다. 다른 기술을 확인하고 싶다면 다음 페이지를 참고하면 된다. https://spring.io/projects
스프링 프레임워크
-
핵심 기술 : 스프링 DI 컨테이너, AOP, 이벤트, 기타
- 웹 기술 : 스프링 MVC, 스프링 WebFlux
- 데이터 접근 기술 : 트랜잭션, JDBC, ORM 지원, XML 지원
- 기술 통합 : 캐시, 이메일, 원격접근, 스케줄링
- 테스트 : 스프링 기반 테스트
- 언어 : 코틀린, 그루비
스프링 부트
- 스프링에서 제공되는 다양한 기술들을 편리하게 사용할 수 있도록 지원, 최근에는 기본으로 사용됨
- 단독으로 실행할 수 있는 스프링 애플리케이션을 쉽게 생성 : Tomcat 같은 웹 서버를 내장해서 별도의 웹 서버를 설치하지 않아도 됨
- 손쉬운 빌드 구성을 위한 starter 종속성 제공 : starter가 하나의 라이브러리를 가져오면, 그 라이브러리가 종속된 또 다른 라이브러리들을 모두 가져온다.
- 스프링과 3rd parth(외부) 라이브러리 자동 구성 : 스프링 버전에 맞게 외부 라이브러리들의 버전을 최적화하므로 외부 라이브러리의 버전 호환성을 확인할 필요 X
- 메트릭, 상태 확인, 외부 구성 같은 프로덕션 준비 기능 제공 : 운영 환경에서의 기본적인 모니터링 기능 제공
- 관례에 의한 간결한 설정
스프링 부트에 대한 오해가 있는데, 스프링 부트는 스프링 프레임워크와 별도로 사용할 수 있는 것이 아니다. 스프링 부트는 스프링 프레임워크를 비롯하여 다양한 기술들을 편리하게 사용할 수 있는 기능을 제공하는 껍데기인 셈이다. 따라서 스프링 부트만으로는 동작이 절대 되지 않는다.
스프링 단어?
스프링이라는 단어는 문맥에 따라 다르게 사용된다.
- 스프링 DI 컨테이너 기술
- 스프링 프레임워크
- 스프링 부트, 스프링 프레임워크 등을 모두 포함한 스프링 생태계
스프링의 핵심 개념
이 기술을 왜 만들었는가? 이 기술의 핵심 컨셉은?
- 웹 어플리케이션 만들고, DB 접근 편리하게 해주는 기술?
- 전자정부 프레임워크?
- 웹 서버도 자동으로 띄워주고?
- 클라우드, 마이크로서비스?
위의 것들은 핵심이 아니라, 결과물일 뿐이다.
스프링은 자바 언어 기반의 프레임워크
-> 자바 언어의 가장 큰 특징 - 객체 지향 언어
-> 스프링은 객체 지향 언어가 가진 강력한 특징을 살려내는 프레임워크
-> 스프링은 좋은 객체 지향 애플리케이션을 개발할 수 있게 도와주는 프레임워크
로드 존슨은 EJB에 의존하여 개발을 하면 객체 지향이 가진 장점들이 손실되는 것을 깨달았고, 이 장점들을 스프링이 가진 DI 컨테이너에서 효과적으로 활용이 가능하게 만들었다.
좋은 객체 지향 프로그래밍?
객체 지향 특징
- 추상화
- 캡슐화
- 상속
- 다형성
객체 지향 프로그램밍
- 객체 지향 프로그래밍은 컴퓨터 프로그램을 명령어의 목록으로 보는 시각에서 벗어나 여러 개의 독립된 단위, 즉 “객체”들의 모임으로 파악하고자 하는 것이다. 각각의 객체는 메시지를 주고 받고, 데이터를 처리할 수 있다.(협력)
- 객체 지향 프로그래밍은 프로그램을 유연하고 변경이 용이하게 만들기 떄문에 대규모 소프트웨어 개발에 많이 사용된다.
유연하고, 변경이 용이?
- 레고 블럭 조립하듯이
- 키보드, 마우스 갈아 끼우듯이
- 컴퓨터 부품 갈아 끼우듯이
- 컴포넌트를 쉽고 유연하게 변경하면서 개발할 수 있는 방법
이러한 것들을 다형성이라는 특징으로 대표할 수가 있다.
다형성(Polymorphism)의 실세계 비유
이 세상을 역할과 구현으로 구분한다고 가정해보자.
운전자와 자동차
운전자와 자동차의 역할이 있다고 해보자. 이 자동차의 역할을 K3, 아반떼, 테슬라 모델3 이렇게 세 개의 서로 다른 자동차가 구현한다. 그럼 운전자는 K3를 타다가 아반떼로 자동차를 바꿔도 충분히 운전이 가능하고, 운전자에게 영향을 주지 않는다. 운전자는 아반떼에 맞추어 새로운 운전 방법을 배울 필요가 없다.
유연하고 변경이 용이하다는 것은 K3 -> 아반떼로 자동차를 바꿔도 운전이 가능한 현상으로 비유될 수 있다. 이것이 가능한 이유는 자동차의 역할의 인터페이스를 모든 자동차가 구현했으며, 운전자는 자동차의 인터페이스에만 의존하고 있기 때문이다.
핵심은, 자동차 역할을 만들고 이를 구현과 분리한 것이 운전자를 위한 방법론이라는 점이다. 또한, 자동차의 역할 인터페이스만 따라준다면, 얼마든지 새로운 자동차가 구현될 수 있으며, 이러한 방식으로 무한히 확장이 가능하다. 기름 자동차에서 전기 자동차가 나왔다하더라도, 인터페이스만 따라준다면 얼마든지 구현이 가능하다.
프로그래밍 관점에서 이를 바꿔 말하면 클라이언트에 영향을 주지 않고, 새로운 기능을 제공할 수 있다. 이 모든 것이 역할과 구현을 분리했기 때문에 가능하며, 클라이언트는 새로운 것을 알거나 무언가를 바꿀 필요가 없다.
로미오와 줄리엣 공연
공연을 기획하는 입장이 되었다고 상상해보자. 로미오 역할과 줄리엣 역할이 있다. 장동건과 원빈이 로미오 역할을 할 수도 있고, 줄리엣 역할은 김태희나 송혜교, 또는 무명 배우가 수행할 수 있다. 중요한 점은 배우는 대체가 가능해야 된다는 점이다. 이 공연에서 역할과 구현을 분리했기 때문에 대체 가능성이 생긴다.
또한 로미오 배우의 입장에서 줄리엣 배우가 누가 됐든지 간에, 본인 대본만 잘 익힌다면 아무런 문제 없이 자신의 역할을 수행할 수 있다. 따라서 얼마든지 배우를 변경할 수 있으므로, 변경 용이성이 발생한다.
또 다른 예시
- 키보드, 마우스, 세상의 표준 인터페이스
- 정렬 알고리즘
- 할인 정책 로직
역할과 구현을 분리
역할과 구현으로 구분하면 세상이
- 단순해지고
- 유연해지며
- 변경도 편리해진다.
따라서 다음과 같은 장점이 따라온다.
- 클라이언트는 대상의 역할(인터페이스)만 알면 된다.
- 클라이언트는 구현 대상의 내부 구조를 몰라도 된다.
- 클라이언트는 구현 대상의 내부 구조가 변경되어도 영향을 받지 않는다.
- 클라이언트는 구현 대상 자체를 변경해도 영향을 받지 않는다.
자바의 다형성
- 역할 = 인터페이스
- 구현 = 인터페이스를 구현한 클래스, 구현 객체
객체를 설계할 때 역할과 구현을 명확히 분리한다. 즉, 인터페이스를 먼저 설계한 후에 이를 구현할 객체를 만드는 것이다. 핵심은 구현보다 인터페이스를 통해서 역할을 정하는 것이 우선이라는 점이다.
객체의 협력이라는 관계부터 생각
다형성을 공부할때 상속이나 구현에 관한 개념에 치중하여 클라이언트를 생각하지 않는 경우가 있는데, 다형성 개념에서는 클라이언트가 가장 중요하다. 클라이언트는 요청하는 주체이고, 서버는 요청을 받아 응답하는 주체로 서로가 협력관계를 가진다.
아래 그림처럼 클라이언트가 단순히 서버에 요청만 할 수도 있지만,
다음과 같이 동시에 클라이언트와 서버가 되어 다른 서버에게 요청할 수도 있다.
응답 과정에서 리턴하는 것이 없더라도 요청에 따라 어떤 행위를 한다면 그 자체로 응답이라고 할 수 있다.
자바의 다형성
자바의 다형성은 오버라이딩을 통해 실현된다. 곧바로 예를 통해서 알아보자.
위의 그림처럼 MemberService가 MemberRepository 구현체에 대하여 save()를 호출했다고 하자. 그렇다면 이 MemberRepository 인터페이스를 구현한 것이 MemoryMemberRepository냐, JdbcMemberRepsitory냐에 따라서 호출되는 메서드가 달라질 것이다. 이때, 클라이언트인 MemberService는 MemberRepository 인터페이스에 의존하고 있으며, 어떤 객체가MemberRepository 인터페이스를 구현하고 있다면 다형성에 의해서 이 인터페이스 변수에 할당이 가능하다. 따라서 이 변수에 MemoryMemberRepository와 JdbcMemberRepository 둘 중 무엇을 할당하든 클라이언트는 정상적으로 그 안에 오버라이딩된 save()를 호출할 수 있다.
다형성의 본질
- 인터페이스를 구현한 객체 인스턴스를 실행 시점에 유연하게 변경할 수 있다.
- 다형성의 본질을 이해하려면 협력이라는 객체 사이의 관계에서 시작해야한다.
- 클라이언트를 변경하지 않고, 서버의 구현 기능을 유연하게 변경할 수 있는 것이 다형성의 본질이다.
역할과 구현을 분리
- 실세계의 역할과 구현이라는 편리한 컨셉을 다형성을 통해 객체 세상으로 가져올 수 있음
- 유연하고 변경이 용이
- 확장 가능한 설계
- 클라이언트에 영향을 주지 않는 변경이 가능
다형성의 한계점은 역할(인터페이스) 자체가 변하면 클라이언트와 서버 모두에 큰 변경이 발생한다는 점이다. ex) 자동차 -> 비행기 / 대본 변경 / USB 인터페이스 변경 따라서 인터페이스를 안정적으로 잘 설계하는 것, 즉 가장 변화가 없는 방식으로 설계하는 것이 중요하다.
스프링과 객체 지향
객체 지향 언어에서 어찌보면 가장 중요한 특징을 뽑으라면 다형성이 가장 중요하다고 말할 수 있는데, 스프링은 다형성을 극대화하여 이용할 수 있도록 도와준다. 스프링에서 제공하는 기능인 제어의 역전(IoC), 의존관계 주입(DI)은 다형성을 활용해서 역할과 구현을 편리하게 다룰 수 있도록 지원하는 기능이다. 따라서 스프링을 사용하면 마치 레고 블럭을 조립하듯이, 혹은 공연 무대의 배우를 선택하듯이 구현을 편리하게 변경할 수가 있다.
좋은 객체 지향 설계의 5가지 원칙(SOLID)
SOLID
클린 코드로 유명한 로버트 마틴(Robert C. Martin)이 좋은 객체 지향 설계의 5가지 원칙을 다음과 같이 정리했다.
- SRP (Single Responsibility Principle) : 단일 책임 원칙
- OCP (Open/Closed Principle) : 개방-폐쇄 원칙
- LSP (Liskov Substitution Principle) : 리스코프 치환 원칙
- ISP (Interface Segregation Principle) : 인터페이스 분리 원칙
- DIP (Dependency Inversion Principle) : 의존관계 역전 원칙
SRP 단일 책임 원칙
한 클래스는 하나의 책임만 가져야 한다는 뜻이지만, 하나의 책임이라는 개념이 조금 모호하다. 문맥과 상황에 따라서 이 책임이라는 범위가 클 수도 있고, 작을 수도 있다. 이때 중요한 판단 기준이 변경이다. 변경이 있을때 파급 효과가 적다면 단일 책임 원칙을 잘 따른 것이다. 예를 들어, UI를 하나 변경할 때 모든 코드를 다 고쳐야 한다면 단일 책임 원칙을 지키지 못한 것이다. 책임이라는 범위를 너무 크게 하거나, 작게 해서는 좋은 설계라고 할 수 없기 때문에, 이 책임의 단위를 적절히 조절하는 것이 객체 지향 설계의 묘미이다.
OCP 개방-폐쇄 원칙
**소프트웨어 요소는 확장에는 열려 있으나, 변경에는 닫혀있어야 한다.** 즉, 기능을 확장하는 과정에서 코드 변경을 안해도 된다는 뜻이며, 다형성의 개념을 고려하면 충분히 설명이 가능하다.
인터페이스를 통해 역할과 구현을 분리해놓으면, 새로운 기능을 만들고자 할때에도 기존 코드의 변경 없이, 인터페이스를 구현한 새로운 클래스만 추가하기만 하면 된다. 따라서 이 다형성을 잘 활용하여 코드의 변경 없이 기능을 확장할 수 있도록 설계했을 때 개방-폐쇄 원칙을 준수하고 있다고 말할 수가 있다.
그런데 MemberService가 클라이언트고, MemberRepository 인터페이스에 의존하여 그 구현체의 save() 메서드를 호출하고 있다고 할 때, MemberService가 클라이언트로서 MembeRepsitory의 구현체 종류를 직접 변경한다면 다음과 같이 코드 변경이 불가피하다는 한계가 있다. 즉, 분명 다형성을 사용하여 기능을 만드는 것까지는 괜찮았으나, 이 새로운 기능을 적용하는 과정에서 OCP 원칙이 지켜지지 않는다는 한계가 발생한다.
MemberRepository m = new MemoryMemberRepository(); // 기존 코드
MemberRepository m = new JdbcMemberReopsitory(); // 변경 코드
이 문제를 해결하려면 객체를 생성하고 연관관계를 맺어주는 별도의 조립을 해주는 설정자가 필요한데, 이 역할을 스프링 컨테이너가 해주는 것이다.
LSP 리스코프 치환 원칙
프로그램의 객체는 프로그램의 정확성을 깨뜨리지 않으면서 하위 타입의 인스턴스로 바꿀 수 있어야 한다. 간단히 말하면, 구현체가 인터페이스에서 기대되는 바와 다르게 작동하도록 기능을 구현해서는 안된다. 인터페이스가 요구하는 모든 기능을 구현하기만 하면 컴파일은 가능하지만, 이 원칙은 컴파일의 성공을 넘어서서 이 인터페이스에 대한 신뢰를 유지하기 위해서는 구현체들이 인터페이스 규약을 모두 지켜야 함을 요구한다.
ISP 인터페이스 분리 원칙
특정 클라이언트를 위한 인터페이스 여러 개가 범용 인터페이스 하나보다 낫다. 위에 들었던 예시처럼 자동차라는 인터페이스가 있다면, 이 자동차 인터페이스를 운전에 대한 인터페이스, 정비에 대한 인터페이스 등으로 분리하는 것이 낫다. 이렇게 설계하면 사용자 클라이언트도 운전자 클라이언트와 정비사 클라이언트로 분리했을 때, 정비 인터페이스 자체가 변하더라도 정비사 클라이언트에만 영향을 줄 뿐, 운전자 클라이언트에 영향을 주지 않는다는 장점이 생긴다. 이처럼 인터페이스가 명확해지고, 대체 가능성도 높아질 수 있도록 인터페이스를 적당한 크기로 분리하는 것을 ISP 원칙이라고 한다.
DIP 의존관계 역전 원칙
프로그래머는 추상화에 의존해야지, 구체화에 의존해선 안된다. 쉽게 말하면, 클라이언트가 구현 클래스 대신 인터페이스에만 의존해야한다는 의미이다. 즉, 기능의 변경을 용이하게 하려면, 역할과 구현을 철저하게 분리해야 하며, 클라이언트는 무조건 역할에 의존해야 한다.
OCP 원칙은 인터페이스와 구현 클래스에 동시에 의존하므로, 구현체를 변경하려면 코드의 변경이 불가피하다. 따라서 구현체 변경을 위해 코드를 변경했다면, DIP 원칙을 위반하는 셈이다. 그렇다고 해서 다형성만으로 DIP를 준수할 순 없다. 클라이언트 측에서 코드를 통해, 구현체 명시를 하지 않고는 구현체를 생성할 수 조차 없기 때문이다.
정리
객체 지향의 핵심은 다형성이다. 그런데 다형성만으로는 구현 객체를 변경할 때 클라이언트 코드도 함께 변경되므로 부품을 갈아 끼우듯이 개발할 수가 없으므로, OCP, DIP 원칙을 지킬 수가 없게 되는 것이다. 이를 해결하기 위한 무언가가 더 필요하다.
객체 지향 설계와 스프링
다시 스프링으로
스프링 이야기에 왜 객체 지향 개념이 언급되는가? 스프링이 제공하는 기술이 다형성, OCP, DIP 원칙 준수가 가능하도록 도와주기 때문이다. 즉, 부품을 갈아끼우듯이 손쉽게 구현체 변경이 가능하다는 것이다.
한 개발자는 순수한 자바로, OCP, DIP 원칙을 지키면서 개발을 할 수 있도록 코드를 작성했고 이것이 DI 컨테이너라는 프레임워크로 발전했다. 이 DI라는 개념은 코드를 직접 짜보는 편이 그 필요성을 이해하기 쉬우므로 다음 강의부터는 코드를 통해 알아볼 것이다.
정리
모든 설계에서 역할과 구현을 분리해야 한다. 애플리케이션 설계도, 역할만 먼저 만들어두고, 구현은 언제든지 유연하게 변경할 수 있도록 만드는 것이 좋은 객체 지향 설계이다. 이상적으로는 모든 설계에 인터페이스를 부여하는 것이 좋다. 인터페이스를 모두 만들어놓은 뒤에 구현체를 구현하면 데이터베이스의 종류가 정해지지 않았다든지 등의 불확실한 상황에서 인터페이스만을 갖고 일단 설계를 시작하며, 구현에 대한 선택을 최대한 미룰 수 있다는 장점이 따라온다. 또한 나중에 구현체를 바꾸더라도 코드 변경의 범위가 작고 유연해진다는 장점도 동반된다.
그러나 이러한 설계 방법에서 실무적인 이슈가 발생하는데, 인터페이스를 도입했을 때 추상화라는 비용이 발생한다는 점이다. 단순하게는 인터페이스와 클래스 둘다 만들어야한다는 비용이 따르지만 이것은 큰 비용이라고 하기 힘들다. 그런데 추상화가 되어버리면 런타임에서 사용되고 있는 구현체가 무엇인지 확인하기 위해서 코드를 한번 더 열어봐야한다.
이렇게 추상화에 따르는 장점과 비용이 존재하므로 설계 과정에서 항상 장점이 비용을 초과할 때에만 추상화를 결정해야 한다. 혹은 구체 클래스를 먼저 사용하고, 향후 꼭 필요할 때 리팩토링을 통해서 인터페이스를 도입하는 것도 방법이다.
Comments