✔️ 이 글은 [스프링 입문을 위한 자바 객체 지향의 원리와 이해 - 김종민] 도서를 바탕으로 정리한 글입니다.
스프링 프레임워크를 설명하는 공식적인 정의는 "자바 엔터프라이즈 개발을 편하게 해주는 오픈소스 경량급 애플리케이션 프레임워크"이다.
각기 다른 소프트웨어 모듈이나 기능을 가진 다양한 응용 소프트웨어 시스템들을 개발할 때 서로 간에 공통되는 설계 문제가 존재하며 이를 처리하는 해결 사이에도 공통점이 있는데 이러한 유사점을 패턴이라 한다.
디자인 패턴은 23개로 나뉘는데 크게 생성(Creational), 구조(structural), 행위(Behavioral) 3가지로 분류된다. 이를 GoF(Gang of Four) 디자인 패턴이라 부른다.
각 분류 별 패턴
- 생성 패턴 : 객체 생성에 관련된 패턴으로, 객체의 생성과 참조 과정을 캡슐화해 특정 객체가 생성되거나 변경되어도 프로그램 구조에 영향을 크게 받지 않도록 유연성을 제공
- 추상 팩토리 메소드 (Abstract Factory Methods) : 구체적인 클래스를 지정하지 않고 인터페이스를 통해 서로 연관되는 객체들을 그룹으로 표현함
- 팩토리 메소드 (Factory Method) : 객체 생성을 서브클래스로 위임하여 캡슐화함
- 빌더 (Builder) : 복합 객체의 생성과 표현을 분리하여 동일한 생성 절차에서도 다른 표현 결과를 만들어낼 수 있음
- 프로토타입 (Prototype) : 원본 객체를 복사함으로써 객체를 생성함
- 싱글톤 (Singleton) : 어떤 클래스의 인스턴스는 하나임을 보장하고 어디서든 참조할 수 있도록 함
- 구조 패턴 : 클래스나 객체를 조합해 더 큰 구조를 만드는 패턴
- 어댑터 (Adapter) : 특정 클래스 인터페이스를 클라이언트에서 요구하는 다른 인터페이스로 변환하여 클라이언트가 이용할 수 있게 해줌
- 브리지 (Bridge) : 구현부에서 추상층을 분리하여 각자 독립적으로 확장할 수 있게 함
- 컴퍼지트 (Composite) : 객체들의 관계를 트리 구조로 구성하여 복합 객체와 단일 객체를 구분없이 다룸
- 데코레이터 (Decorator) : 주어진 상황 및 용도에 따라 어떤 객체에 다른 객체를 덧붙이는 방식
- 퍼사드 (Facade) : 서브 시스템에 있는 인터페이스 집합에 대해 하나의 통합된 인터페이스(Wrapper) 제공
- 플라이웨이트 (Flyweight) : 크기가 작은 여러 개의 객체를 매번 생성하지 않고 가능한 한 공유할 수 있도록 하여 메모리를 절약함
- 프록시 (Proxy) : 접근이 어려운 객체로의 접근을 제어하기 위해 객체의 Surrogate나 Placeholder를 제공
- 행위 패턴 : 객체나 클래스 사이의 알고리즘이나 책임 분배에 관련된 패턴으로, 한 객체가 혼자 수행할 수 없는 작업 여러개의 객체로 분배하면서도 객체 사이의 결합도를 최소화하는 것에 중점을 준다.
- 책임 연쇄 (Chain of Responsibility) : 요청을 받는 객체를 연쇄적으로 묶어 요청을 처리하는 객체를 만날 때까지 객체 Chain을 따라 요청을 전달
- 커맨드 (Command) : 요청을 객체의 형태로 캡슐화하여 재사용하거나 취소할 수 있도록 저장함
- 인터프리터 (Interpreter) : 특정 언어의 문법 표현을 정의함
- 반복자 (Iterator) : 내부를 노출하지 않고 접근이 잦은 어떤 객체의 원소를 순차적으로 접근할 수 있는 동일한 인터페이스 제공
- 중재자 (Mediator) : 한 집합에 속해 있는 객체들의 상호작용을 캡슐화하여 새로운 객체로 정의
- 메멘토 (Memento) : 객체가 특정 상태로 다시 돌아올 수 있도록 내부 상태를 실체화
- 옵저버 (Observer) : 객체 상태가 변할 때 관련 객체들이 그 변화를 통지받고 자동으로 갱신될 수 있게 함
- 상태 (State) : 객체의 상태에 따라 동일한 동작을 다르게 처리해야할 때 사용
- 전략 (Strategy) : 동일 계열의 알고리즘군을 정의하고 캡슐화하여 상호교환이 가능하도록 함
- 템플릿 메소드 (Template Methods) : 상위클래스는 알고리즘의 골격만을 작성하고, 구체적인 처리는 서브클래스로 위임함
- 방문자 (Visitor) : 객체의 원소에 대해 수행할 연산을 분리하여 별도의 클래스로 구성함
어댑터 패턴(Adapter Pattern)
어댑터(변환기)는 서로 다른 두 인터페이스 사이에 통신이 가능하도록 해주는 연결 역할을 해준다. 어댑터 패턴은 OCP(개발 폐쇄 원칙)을 활용한 설계 패턴이다.
변경할 수 없는 클래스를 원하는 형태의 인터페이스나 클래스로 사용하고자 할 때 어댑터 클래스를 추가해서 사용할 수 있게 해주는 설계 패턴이다.
호출당하는 쪽의 메소드를 호출하는 쪽의 코드에 대응하도록 중간에 변환기 클래스를 통해 호출하는 패턴을 어댑터 패턴이라고 한다.
구성요소
- 클라이언트
- 인터페이스
- 서비스/외부 요소(어댑티) <- 변환 대상
- 어댑터
ex) 데이터베이스 시스템을 공통의 인터페이스인 ODBC/JDBC가 어댑터 패턴을 사용하여 다양한 데이터베이스 시스템을 단일한 인터페이스로 조작할 수 있게 해준다. JDBC가 어댑터의 역할을 한다.
- 타이거 클래스를 변경하지 않고 TigerAdapter 클래스를 통해서 Animal에 상속된 클래스처럼 사용할 수 있게 해줌
- TigerAdapter 클래스 내에서 타이거 클래스의 객체를 필드로 가짐
ex) 외부라이브러리 (Auth, Payment, Media)를 사용하려고 하는데 현재의 시스템 요구사항에 맞지 않아 재작성을 해야하는데 직접 접근할 수 없을 때 중간에 어댑터 클래스를 만들어줘서 작업을 변환시켜줌
프록시 패턴(Proxy Pattern)
프록시는 대리자, 대변인이라는 뜻을 가진 단어로 특정 객체로의 접근을 제어하는 대리인(특정 객체를 대변하는 객체)를 제공한다. 즉 어떤 작업의 실행을 대리인을 통해 실행하도록 하고, 대리자가 할 수 없는 일은 실제 작업을 처리하는 곳에서 실행한다.
대리자는 실제 서비스와 같은 이름의 메소드를 구현하고 이때 인터페이스를 사용해야 한다. 대리자는 실제 서비스에 대한 객체를 갖고, 실제 서비스와 같은 이름을 가진 메소드를 호출해서 그 값을 클라이언트에게 돌려준다. 대리자는 실제 서비스의 호출 전 후에 별도의 로직을 수행할 수도 있다.
제어 흐름을 조정하기 위한 목적으로 중간에 대리자를 두는 패턴
OCP(개방 폐쇄 원칙)과 DIP(의존 역전 원칙)이 적용된 설계패턴이다.
ex) 어떤 요청에 대해 그 결과를 캐시처럼 저장해놓고 새로운 요청이 이전에 받은 요청과 동일하다면 진짜로 일을 하는 객체에게 일을 시키지 않고 캐시에 저장된 결과를 바로 전달하여 속도 향상 및 CPU 자원 절약 등의 장점이 있음
프록시 패턴은 사이즈가 큰 객체가 로딩되기 전에도 프록시를 통해 참조가 가능하고, 실제 객체의 public protected 메소드를 숨기고 인터페이스를 통해 노출시킬 수 있다는 장점이 있지만, 프록시를 사용할 때마다 실제 객체를 매번 생성해줘야 해서 성능이 저하될 수도 있다는 단점도 있다.
프록시 패턴의 종류
- 가상 프록시 : 꼭 필요로 하는 시점까지 객체의 생성을 연기하고, 해당 객체가 생성된 것처럼 동작하도록 만들고 싶을 때 사용
- 원격 프록시 : 서로 다른 주소 공간에 있는 객체에 대해 마치 같은 주소 공간에 있는 것처럼 동작하게 하는 패턴(ex. Google Docs)
- 보호 프록시 : 주체 클래스에 대한 접근을 제어하기 위한 경우에 객체에 대한 접근 권한을 제어하거나 객체마다 다른 접근 권한을 주고 싶을 때 사용
데코레이터 패턴(Decorator Pattern)
데코레이터는 도장/도배업자/장식자를 의미한다. 데코레이터 패턴은 프록시 패턴과 구현 방법이 같지만 프록시 패턴의 경우 클라이언트가 최종적으로 돌려 받는 반환값을 조작하지 않고 전달하는 반면에 데코레이터 패턴은 클라이언트가 받는 반환값에 장식을 덧입힌다.
기능을 마치 장식처럼 계속 추가할 수 있고, 기능을 실행 중에 동적으로 변경 또는 확장시킬 수 있는 패턴이다.
데코레이터는 실제 서비스와 같은 이름의 메소드 구현하는데, 이때 인터페이스를 사용한다. 실제 서비스에 대한 참조 변수를 갖고 실제 서비스의 같은 이름을 가진 메소드를 호출하고, 그 반환값에 장식을 더해 클라이언트에게 돌려준다. 데코레이터는 실제 서비스의 메소드 호출 전 후에 별도의 로직을 수행할 수도 있다.
메소드 호출의 반환 값에 변화를 주기 위해서 중간에 장식자를 두는 패턴
OCP(개방 폐쇄 원칙)과 DIP(의존 역전 원칙)이 적용된 설계 패턴이다.
- Strings는 문자열을 여러개 가지고 있는 클래스고 장식할 대상에 해당함
- Decorator는 Strings에 대한 장식을 나타내고, 하위 3개의 클래스(SideDecorator, BoxDecorator, LineNumberDecorator)가 장식을 구체적으로 구현해놓은 클래스
- Strings와 Decorator는 Item 클래스를 상속받아 내용물과 장식의 구분을 없애고, 그 둘을 마치 동일한 개념으로 다룰 수 있게 됨
- Decorator 클래스 안에는 Item타입인 객체 변수를 생성해 장식할 대상을 다루는데, Item 타입 객체는 Item의 하위 클래스 모두가 될 수 있기 때문에 문자열에 대한 장식 뿐만 아니라 장식에 대한 장식도 가능하다.
- SideDecorator는 문자열에 " "을 붙여주고, BoxDecorator는 원래의 내용물을 완전히 감싸는 상자를 붙여주고, LineNumberDecorator는 문자열 앞에 숫자를 붙여주는 식으로 String이나 장식물에 장식을 더해준다.
싱글턴 패턴(Singleton Pattern)
싱글턴 패턴은 하나의 클래스 파일에 대해서 오직 하나의 객체만을 생성할 수 있게 보장해주는 패턴이다. 싱글턴 패턴이 적용된 클래스의 객체는 다른 클래스들에서 접근은 할 수 있지만, 생성은 못한다.
커넥션 풀, 스레드 풀, 디바이스 설정 객체 등과 같은 경우 인스턴스를 여러 개 만들게 되면 불필요한 자원을 사용하게 되고, 프로그램이 예상치 못한 결과를 낳을 수 있기 때문에 싱글턴 패턴을 적용하면 인스턴스를 하나만 만들고 그것을 재사용하게 해 문제를 해결한다.
싱글턴 패턴을 적용하면 의미상 두 개의 객체가 존재할 수 없다. 이를 위해 객체 생성을 위한 new를 사용할 수 없도록 생성자에 private 접근 제어자를 지정해주고, 유일한 단일 객체를 반환할 수 있는 static 메소드가 필요, 유일한 단일 객체를 참조할 static 참조 변수가 필요하다.
클래스의 인스턴스, 즉 객체를 하나만 만들어서 사용하는 패턴
단일 객체인 경우 결국 공유 객체로 사용되기 때문에 속성을 갖지 않는게 정석이고, 갖게 된다 하더라도 읽기 전용 속성을 가져야 한다. 단일 객체가 다른 단일 객체에 대한 참조를 속성으로 가져도 된다.
싱글턴 패턴의 특징
- private 생성자를 가짐
- 단일 객체 참조 변수를 정적 속성으로 가짐
- 단일 객체 참조 변수가 참조하는 단일 객체를 반환하는 getInstance() 정적 메소드를 가짐
- 단일 객체는 쓰기 가능한 속성을 갖지 않는 것이 정석
템플릿 메소드 패턴 (Template Method Pattern)
템플릿 패턴은 어떤 기능에 대해 실행되어야 할 알고리즘을 여러 단계로 나누고 각 단계에 대한 순서만 정의해놓고, 각 단계에 대한 세부 구현을 상황에 맞게 따로 구현할 수 있는 패턴이다.
상위 클래스에 공통 로직을 수행하는 템플릿 메소드(각 단계를 순서대로 수행하는 메소드)와 하위 클래스에 오버라이딩을 강제하는 추상 메소드 또는 선택적으로 오버라이딩할 수 있는 훅(Hook) 메소드를 두는 패턴을 템플릿 메소드 패턴이라 한다.
훅 메소드는 추상 클래스에서 구현된 메소드라 하위 클래스에서의 구현이 강제적이지 않다. 안에는 기본적인 내용만 구현되어 있거나 아무 코드도 들어있지 않아 하위 클래스에서 선택적으로 오버라이딩해서 사용할 수 있는 메소드를 의미한다.
템플릿 메소드를 사용하면 알고리즘 일부 단계를 서브클래스에서 구현할 수 있으며, 알고리즘의 구조는 그대로 유지하면서 알고리즘의 특정 단계를 서브클래스에서 재정의 가능
상위 클래스의 견본 메소드에서 하위 클래스가 오버라이딩한 메소드를 호출하는 패턴이다.
- AbstractClass : 템플릿 메소드를 정의하는 클래스로 하위 클래스에 공통 알고리즘을 정의하고 하위 클래스에서 구현된 기능을 primitive 메소드 또는 hook 메소드로 정의하는 클래스
- ConcreteClass : 물려받은 primitive 메소드 또는 hook 메소드를 구현하는 클래스로 상위 클래스에서 구현된 템플릿 메소드의 일반적인 알고리즘에서 하위 클래스에 적합하게 primitive 메소드나 hook 메소드를 오버라이드하는 클래스
- DisplayArticleTemplate : 아직 구현되지 않은 각 단계를 정해진 순서대로 실행시켜주는 추상 클래스
- SimpleDisplayArticle, CaptionDisplayArticle : 각 단계에 대한 메소드를 구체적으로 구현하는 클래스
- Article : 다른 세 클래스들에서 출력할 데이터를 얻을 수 있는 클래스
팩토리 메소드 패턴 (Factory Method Pattern)
객체 생성을 위한 패턴으로, 객체 생성에 필요한 과정을 템플릿처럼 정해놓고 각 과정을 다양하게 구현이 가능하게 해준다. 구체적으로 생성할 클래스를 유연하게 정할 수 있고 객체 생성에 대한 인터페이스과 구현을 분리해놓은 패턴이다.
오버라이드된 메소드가 객체를 반환하는 패턴
DIP(의존 역전 원칙)을 활용하는 패턴이다.
- Factory : Item을 생성해주는 절차를 정해주는데 절차의 구체적인 구현은 안되어 있는 추상 클래스
- Item : 인터페이스로 앞으로 생성될 다양한 종류의 Item을 동일한 타입으로 처리할 수 있게 해줌
- ItemFactory : Item을 생성하기 위한 각 절차의 코드를 구현해주는 클래스
- Sword, Shield, Bow : Item 인터페이스에 대한 구현 클래스로 필요한 시점에 각각 칼, 방패, 활을 생성해줌
- Factory, Item이 생성을 위한 인터페이스 부분이고, 나머지가 생성에 대한 구현 부분으로 완전히 분리
- 새로운 Gun이라는 구현 클래스를 만들려고 하면 실제 총을 생성해주는 구현 클래스 Gun은 Item 인터페이스를 구현하고, Gun 객체를 생성하는 과정은 ItemFactory에서 담당한다.
전략 패턴 (Strategy Pattern)
알고리즘군을 정의하고 캡슐화해서 각각의 알고리즘군을 수정해서 쓸 수 있게 해준다. 전략패턴을 사용하면 클라이언트로부터 알고리즘을 분리해서 독립적으로 변경할 수 있다.
어떤 하나의 기능을 구성하는 특정 부분을 실행 중에 다른 것으로 효과적으로 바꿀 수 있게 해주는 패턴으로, 필요할 경우 전략을 바꿀 수 있게 해준다. 순서대로 실행되어야 하는 단계를 실행 중에 중간에 있는 단계를 다른 것으로 바꿔줄 수 있다.
전략 패턴을 구성하는 3요소
- 전략 메소드를 가진 전략 객체
- 전략 객체를 사용하는 컨텍스트(전략 객체의 사용자/소비자)
- 전략 객체를 생성해 컨텍스트에 주입하는 클라이언트(제3자, 전략 객체의 공급자)
클라이언트는 여러 전략 중 하나를 선택해 전략 객체를 생성한 후 컨텍스트에 주입한다.
템플릿 메소드와는 같은 문제의 해결책으로 상속을 이용하는 템플릿 메소드 패턴과 객체 주입을 통한 전략 패턴 중에서 선택/적용할 수 있다. 단일 상속만이 가능한 자바에서는 전략 패턴이 더 많이 쓰인다.
클라이언트가 전략을 생성해 전략을 실행할 컨텍스트에 주입하는 패턴
OCP(개방 폐쇄 원칙)과 DIP(의존 역전 원칙)이 적용되었다.
ex)
- SumStrategy : 1부터 어떤 수 까지의 총 합을 구하는 인터페이스
- SimpleSumStrategy, GaussSumStrategy : 1부터 어떤 수 까지의 총 합을 얻는 구체적인 코드
- SumPrinter : 1부터 어떤 수 까지의 총 합을 출력해주는 클래스로 SumStrategy 인터페이스만을 알 뿐 실제 총 합을 계산하는 클래스들의 존재를 모르기 때문에 총 합을 계산하는 다른 방법의 클래스를 추가할 때 SumPrinter 클래스는 변경할 필요가 없음
템플릿 콜백 패턴(Template Callback Pattern - 견본/회신 패턴)
템플릿 콜백 패턴은 전략 패턴의 변형으로, 스프링의 3대 프로그래밍 모델 중 하나인 DI(의존성 주입)에서 사용하는 특별한 형태의 전략 패턴이다. 전략 패턴과 모든 것이 동일한데, 전략을 익명 내부 클래스로 정의해서 사용한다는 특징이 있다.
전략을 익명 내부 클래스로 구현한 전략 패턴
OCP(개방 폐쇄 원칙)과 DIP(의존 역전 원칙)이 적용된 설계 패턴이다.
ex) 전략 패턴의 예시와 비교하면 SumPrinter가 SumStrategy 인터페이스만 의존한다는 건 동일한데, SumPrinter는 템플릿 역할을 하고 SumStrategy는 콜백 역할을 하는 것이 템플릿 콜백 패턴이다. 템플릿에서는 동작 과정을 정의해놓고, 콜백 클래스에서 전략을 주입할 때는 익명 내부 클래스로 넣어준다.
스프링이 사랑한 다른 패턴들
이 외에도 다양한 디자인 패턴이 있고, 특히 스프링 MVC의 경우에는 프론트 컨트롤러 패턴(Front Controller Pattern : 최전선 제어자 패턴)과 MVC패턴(Model-View-Controller)을 활용하고 있다.
MVC는 소프트웨어가 서비스하는 방식에 대한 패턴으로 각 기능을 Model, View, Controller로 나눈다.
- Model
- 애플리케이션 정보, 데이터(데이터베이스, 처음 정의하는 상수, 초기화값, 변수 등)를 나타내고 정보들의 가공을 책임지는 컴포넌트
- 사용자가 편집하길 원하는 모든 데이터를 가지고 있어야 하며, View나 Controller에 대해서 몰라야 한다.
- 변경이 일어나면 이벤트를 발생시켜 누군가에게 전달해야하며, 누군가 Model을 변경하도록 요청하는 이벤트를 보내면 이를 수신할 처리 방법을 구현해야 한다.
- Model은 재사용가능해야 하며, 다른 인터페이스에서도 변하지 않아야 한다.
- View
- 데이터 및 객체의 입력, 그리고 보여주는 출력을 담당하는 컴포넌트
- Model이 가지고 있는 정보를 따로 저장하면 안된다.
- Model이나 Controller와 같이 다른 구성요소들을 몰라야 한다.
- 변경이 일어났을 때 누군가에게 변경을 알려줘야 하는 방법을 구현해야 하며, 변경 요청이 들어오면 이를 Model에게 전달해야 한다.
- View는 재사용 가능해야 하며, 다른 정보들을 표현하기 쉽게 설계해야 한다.
- Controller
- 이벤트를 처리하는 컴포넌트이다.
- Model과 View에 대해 알고 있어야 하고 변경을 모니터링해야 한다.
사용자가 보는 페이지, 데이터처리, 그리고 이 둘 사이를 제어하는 컨트롤 이 3가지 만으로 하나의 애플리케이션을 만들면 각자 맡은 바에만 집중할 수 있어 효율적이다. 유지보수성, 애플리케이션의 확장성, 유연성이 증가하고 중복 코딩이라는 문제점도 사라진다.
참고자료:
https://www.youtube.com/watch?v=Nc-3QUJicvo
https://www.youtube.com/watch?v=IHU-wDglGM0&list=PLDV-cCQnUlIaOFXCUv8vEMGxqzrrkGv_P&index=2
https://www.youtube.com/watch?v=YlIkB4frPcw&t=391s
https://jaimemin.tistory.com/2014
'도서 > 스프링 입문을 위한 자바 객체 지향의 원리와 이해' 카테고리의 다른 글
[도서/스프링 입문] #7 스프링 삼각형과 설정 정보 (0) | 2023.01.17 |
---|---|
[도서/스프링 입문] #5 객체 지향 설계 5원칙 - SOLID (1) | 2023.01.17 |
[도서/스프링 입문] #4 자바가 확장한 객체 지향 (0) | 2023.01.14 |
[도서/스프링 입문] #3 자바와 객체 지향 (0) | 2023.01.14 |
[도서/스프링 입문] #2 자바와 절차적/구조적 프로그래밍 (0) | 2023.01.14 |