회원가입을 하면 원하는 문장을
저장할 수 있어요!
다음
AWS 이용 중이라면 최대 700만 원 지원받으세요
이번 글을 시작으로 실제 자바 프로젝트에서 디자인 패턴을 어떻게 사용하는지 정리하고자 합니다. 첫 번째 주제로 자바 라이브러리와 프레임워크에서 자주 쓰이는 어댑터 패턴(Adapter Pattern)에 대해 알아볼 텐데요. 이후 몇 편의 자바 디자인 패턴 시리즈를 통해 각 디자인 패턴의 장단점과 구현 방법을 정리하고, 실제 프로젝트에서 어떻게 활용하는지 살펴보겠습니다.
회원가입을 하면 원하는 문장을
저장할 수 있어요!
다음
회원가입을 하면
성장에 도움이 되는 콘텐츠를
스크랩할 수 있어요!
확인
이번 글을 시작으로 실제 자바 프로젝트에서 디자인 패턴을 어떻게 사용하는지 정리하고자 합니다. 첫 번째 주제로 자바 라이브러리와 프레임워크에서 자주 쓰이는 어댑터 패턴(Adapter Pattern)에 대해 알아볼 텐데요. 이후 몇 편의 자바 디자인 패턴 시리즈를 통해 각 디자인 패턴의 장단점과 구현 방법을 정리하고, 실제 프로젝트에서 어떻게 활용하는지 살펴보겠습니다.
디자인 패턴이란 소프트웨어 개발에서 빈번히 발생하는 문제를 해결하기 위한 일종의 설계 방식을 말합니다. 개발자들은 디자인 패턴을 통해 코드 설계에 대한 방향성을 공유할 수 있으며, 이를 통해 코드의 유지 보수와 재사용성을 향상시킬 수 있습니다.
예를 들어, 어떤 프로젝트에서 '팩토리 패턴(Factory Pattern)'을 적용한다고 하면, 개발자들은 그 구조와 방향성을 이해하고 소스 코드 개발에 적용할 수 있어야 합니다. 한 가지 명심할 점은 디자인 패턴은 단순히 특정 문제를 해결하는 코드를 의미하는 것이 아니라 코드 설계 과정에서 사용할 수 있는 일종의 전략(strategy)이라는 것입니다.
자바에서 활용되는 디자인 패턴은 생성 패턴(Creational Patterns), 구조 패턴(Structural Pattern), 행동 패턴(Behavioral Patterns) 이렇게 세 가지 카테고리로 분류됩니다. 생성 패턴은 객체 생성에 대한 디자인 패턴이며, 구조 패턴은 클래스와 객체를 조합하는 방식을 정형화한 패턴을 말합니다. 마지막으로 행동 패턴은 클래스와 객체들의 상호 작용과 책임 분산을 정리한 패턴을 말합니다.
이번 자바 디자인 패턴 시리즈에서는 세 가지 분류의 디자인 패턴과 관련하여, 각각의 주요 패턴을 알아보려고 합니다. 그중에서도 오늘은 구조 패턴의 한 종류인 어댑터 패턴에 대해 다룹니다.
어댑터 패턴(Adapter Pattern)은 호환되지 않는 인터페이스들을 연결하는 디자인 패턴을 말합니다. 이 패턴은 기존의 클래스를 수정하지 않고도, 특정 인터페이스를 필요로 하는 코드에서 사용할 수 있게 해줍니다. 또한 클래스의 인터페이스를 다른 인터페이스로 변환할 수도 있는데요. 이를 통해 서로 다른 인터페이스를 가진 클래스들이 상호 작용할 수 있도록 해서 코드의 재사용성을 증대시키게 됩니다.
어댑터 패턴 주요 구성 요소로는 타겟(Target), 어댑티(Adaptee), 어댑터(Adapter), 클라이언트(Client) 이렇게 4가지가 있습니다. 타겟은 클라이언트가 직접적으로 호출하는 인터페이스를 말하며, 어댑티는 아직 호환되지 않은 기존 클래스(또는 인터페이스)를 의미합니다. 클라이언트는 특정 작업을 요청하는 클래스를 말하며, 어댑터는 타겟 인터페이스를 구현하여 클라이언트 요청을 애댑티로 전달하는 클래스입니다.
어댑터 패턴은 자바의 I/O 라이브러리에서 자주 볼 수 있습니다. 예를 들어, InputStreamReader는 바이트 스트림을 문자 스트림으로 변환하는 어댑터 역할을 하는데요. 이 클래스는 InputStream을 받아서 Reader를 제공함으로써 바이트를 문자로 변환하는 과정을 캡슐화하고 있습니다.
이 밖에도 어댑터 패턴의 활용은 스프링 프레임워크(Spring Framework)에서도 자주 볼 수 있는데요. 스프링 프레임워크의 MVC 아키텍처에서 HandlerAdapter 인터페이스는 어댑터 패턴의 일반적인 예라고 할 수 있습니다. 참고로 HandlerAdapter 인터페이스는 다양한 종류의 핸들러를 동일한 방식으로 처리하는데 활용됩니다.
실제 개발 과정에서 어댑터 패턴을 구현할 때는 ① 패턴을 적용하고자 하는 인터페이스 식별하기 ② 어댑터 클래스 작성 ③ 클라이언트 코드에서 호출하는 과정을 거치게 됩니다. 예를 들어, 아래와 같이 호환되지 않는 인터페이스와 클래스가 있는 경우를 살펴보겠습니다.
위 두 인터페이스와 클래스를 호환시키기 위해서는 아래 그림처럼 어댑터 클래스를 작성하여 어댑터 패턴을 적용해 볼 수 있습니다. 아래 예시 코드의 어댑터 클래스는 타겟 인터페이스를 구현했으며, 어댑티 클래스를 멤버 변수로 가지고 있습니다.
이제 클라이언트에서 아래 그림과 같이 코드를 작성하여 Adaptee 클래스의 performAction() 메소드를 호출할 수 있습니다. 즉, 클라이언트에서 Target 인터페이스의 doSomething() 메소드를 호출하여, Adaptee 클래스의 performAction() 메소드를 호출할 수 있게 된 것입니다. 이를 통해 클라이언트는 직접 Adaptee 클래스를 호출하지 않고도 자신이 원하는 인터페이스를 통해, Adaptee 클래스의 기능을 사용할 수 있는 것입니다.
앞선 예제에서 살펴봤듯이 어댑터 패턴을 이용하면 기존의 클래스를 수정하지 않고도 클라이언트에서 새로운 인터페이스를 사용할 수 있습니다. 이는 기존의 코드를 재사용하고 코드 중복을 줄여주는 데 도움이 되죠. 또한 클래스 간의 결합도를 줄여주어, 소스 코드 변경이 필요할 때 쉽게 수정할 수 있다는 장점도 있습니다.
단점으로는 어댑터 패턴을 사용하면 어댑터 클래스를 추가로 작성해야 하기 때문에 소스 코드가 늘어나게 됩니다. 이는 코드의 복잡성을 증가시키고, 유지 보수를 어렵게 만들 수도 있습니다. 또한 어댑터가 중간에 데이터를 변환하는 과정에서 추가적인 처리 시간과 오버 헤드가 발생할 수도 있습니다.
어댑터 패턴은 추가 코드를 작성해야 하고, 오버헤드가 발생할 수도 있기 때문에 무분별한 사용은 권장하지 않습니다. 따라서 호환되지 않는 인터페이스를 가진 클래스들이 함께 작동해야 하거나, 이미 존재하는 클래스의 인터페이스가 요구 사항과 맞지 않거나 또는 기존 클래스에 원하는 인터페이스가 없는 경우 어댑터 패턴을 고려하는 것이 좋습니다.
예를 들어, 서드파티 라이브러리나 API를 사용하는데 그 인터페이스가 애플리케이션 코드와 잘 맞지 않는 경우인데요. 이때 어댑터 패턴을 사용해 서드파티 라이브러리 및 API 내부 구현에 영향을 받지 않으면서, 프로젝트에 필요한 인터페이스를 생성할 수 있습니다.
지금까지 디자인 패턴 중 구조 패턴의 한 종류인 어댑터 패턴에 대해서 살펴봤습니다. 어댑터 패턴은 인터페이스 사이에 유연성이 필요한 상황에서 효율적으로 사용될 수 있는 디자인 패턴입니다. 다만 앞서 언급했듯이 코드 복잡성을 증가시키고, 오버 헤드를 발생시킬 수 있다는 단점도 있기 때문에 적절한 상황에서만 사용되어야 합니다.
이러한 어댑터 패턴 외에도 인터페이스와 클래스, 객체 간의 관계를 관리하는 디자인 패턴에는 퍼사드 패턴(Facade Pattern), 데코레이터 패턴(Decorator Pattern), 프록시 패턴(Proxy Pattern) 등이 있는데요. 다음 글을 통해 살펴보도록 하겠습니다.
요즘IT의 모든 콘텐츠는 저작권법의 보호를 받는 바, 무단 전재와 복사, 배포 등을 금합니다.