6장 스프링이 사랑한 디자인 패턴
카테고리: Oop-for-spring
스프링 입문을 위한 자바 객체 지향의 원리와 이해 6장의 내용을 정리한 글입니다.
디자인 패턴은 실제 개발 현장에서 비즈니스 요구 사항을 프로그래밍으로 처리하면서 만들어진 다양항 해결책 중에서 많은 사람들이 베스트 프렉티스를 정리한 것입니다. 디자인 패턴은 당연히 객체 지향 특성과 설계 원칙을 기반으로 구현돼 있습니다.
스프링 역시 다양한 디자인 패턴을 활용하고 있습니다.
어댑터 패턴(Adapter Pattern)
어댑터 패턴은 호환되지 않는 인터페이스들을 연결하는 디자인 패턴을 말합니다. 이 패턴은 기존의 클래스를 수정하지 않고도, 특정 인터페이스를 필요로 하는 코드에서 사용할 수 있게 해줍니다. 또한 클래스의 인터페이스를 다른 인터페이스로 변환할 수도 있습니다.
어댑터 패턴 구현 방법
public interface Target {
void doSomething();
}
public class Adaptee {
void performAction() {
System.out.println("Action performed!");
}
}
위의 두 인터페이스와 클래스는 서로 호환되지 되지 않습니다. 하지만 Target 인터페이스에서 Adaptee 클래스의 performAction()메서드의 기능을 사용하고 싶을 때, 어댑터 패턴을 사용할 수 있습니다.
public class Adapter implements Target {
private final Adaptee adaptee;
public Adapter(Adaptee adpatee) {
this.adpatee = adpatee;
}
@Override
void doSomething() {
adpatee.performAction(); // Target 인터페이스에서 Adaptee 클래스의 기능을 사용 가능하게 합니다.
}
}
public class Clinet {
public static void main(String[] args) {
Adptee adaptee = new Adptee();
Target target = new Adpter(adptee);
target.doSomething(); // Target 인터페이스에서 Adaptee 클래스의 기능을 사용하고 있습니다.
}
}
앞선 예제에서 살펴봤듯이 어댑터 패턴을 이용하면 기존의 클래스를 수정하지 않고도 클라이언트에서 새로운 인터페이스를 사용할 수 있습니다. 하지만 어댑터 클래스를 추가적으로 작성해야 하기때문에 소스 코드가 늘어나게 됩니다. 이러한 trade-off를 고려하여 사용해야 합니다.
프록시 패턴(Proxy Pattern)
프록시는 다른 객체에 대한 대체 또는 자리표시자를 제공할 수 있는 구조 디자인 패턴입니다. 프록시는 원래 객체에 대한 접근을 제어하므로, 당신의 요청이 원래 객체에 전달되기 전 또는 후에 무언가를 수행할 수 있도록 합니다.
프록시 패턴 구현 방법
public interface IService {
String runSomething();
}
public class Service implements IService {
@Override
public String runSomething() {
return "서비스 짱!";
}
}
public class Proxy implements IService {
IService service;
@Override
public String runSomething() {
// 호출에 대한 흐름 제어가 주목적, 반환 결과를 그대로 전달합니다.
service = new Service();
return service.runSomething();
}
}
위의 에제에서 중요 포인트는 다음과 같습니다.
- 대리자는 실제 서비스와 같은 이름의 메서드를 구현한다. 이때 인터페이스를 사용한다.
- 대리자는 실제 서비스에 대한 참조 변수를 갖는다(합성)
- 대리자는 실제 서비스의 같은 이름을 가진 메서드를 호출하고 그 값을 클라이언트에게 돌려준다.
- 대리자는 실제 서비스의 메서드 호출 전후에 별도의 로직을 수행할 수도 있다.
프록시 패턴은 실제 서비스 메서드의 반환값에 가감하는 것을 목적으로 하지 않고 제어의 흐름을 변경하거나 다른 로직을 수행하기 위해 사용합니다. 하지만 프록시 패턴을 사용하면 객체를 생성할 때 한 단계를 거치게 되므로, 빈번한 객체 생성이 필요한 경우 성능이 저하될 수 있고, 로직이 난해해져 가독성이 떨어질 수 있습니다.
데코레이터 패턴(Decorator Parttern)
데코레이터는 객체들을 새로운 행동들을 포함한 특수 래퍼 객체들 내에 넣어서 위 행동들을 해당 객체들에 연결시키는 구조적 디자인 패턴입니다. 데코레이터 패턴과 프록시 패턴은 구현 방법이 같습니다.
다만 프록시 패턴은 클라이언트가 최종적으로 돌려 받는 반환값을 조작하지 않고
전달하는 반면, 데코레이터 패턴은 클라이언트가 받는 반환값에 장식을 덧입힙니다.
데코레이터 패턴 구현 방법
public interface IService {
String runSomething();
}
public class Service implements IService {
@Override
public String runSomething() {
return "서비스 짱!";
}
}
class Proxy implements IService {
IService service;
@Override
public String runSomething() {
service = new Service();
return "정말 " + service.runSomething(); // 호출에 대한 장식이 주목적입니다. 클라이언트에게 반환 결과에 장식을 더하며 전달합니다.
}
}
위의 에제에서 중요 포인트는 다음과 같습니다.
- 장식자는 실제 서비스와 같은 이름의 메서드를 구현한다. 이때 인터페이스를 사용한다.
- 장식자는 실제 서비스에 대한 참조 변수를 갖는다(합성)
- 장식자는 실제 서비스의 같은 이름을 가진 메서드를 호출하고, 그 반환값에 장식을 더해 클라이언트에게 돌려준다.
- 장식자는 실제 서비스의 메서드 호출 전후에 별도의 로직을 수행할 수 있다.
싱글턴 패턴(Singleton Pattern)
싱글턴은 클래스에 인스턴스가 하나만 있도록 하면서 이 인스턴스에 대한 전역 접근(액세스) 지점을 제공하는 생성 디자인 패턴입니다.
싱글턴 패턴을 적용할 경우 의미상 두 개의 객체가 존재할 수 없습니다. 이를 구현하려면 객체 생성을 위한 new 에 제약을 걸어야 하고, 만들어진 단일 객체를 반환할 수 있는 메서드가 필요합니다.
따라서 필요한 요소는 다음과 같습니다.
- new를 실행할 수 없도록 생성자에 private 접근 제어자를 지정한다.
- 유일한 단일 객체를 반환할 수 있는 정적 메서드가 필요하다.
- 유일한 단일 객체를 참조할 정적 참조 변수가 필요하다.
싱글턴 패턴 구현 방법
public class Singleton {
static Singleton singletonObject;
private Singleton() {}; // private 생성자
// 객체 반환 정적 메서드
public static Singleton getInstance() {
// 멀티 쓰레드 환경해서는 제대로 동작하지 않을 수도 있습니다.
if (singletonObject == null) {
singletonObject = new Singleton();
}
return singletonObject;
}
}
싱글턴 패턴을 한 문장으로 하면 클래스의 인스턴스, 즉 객체를 하나만 만들어 사용하는 패턴
입니다.
싱글톤 패턴의 특징은 다음과 같습니다.
- private 생성자를 갖는다.
- 단일 객체 참조 변수를 정적 속성으로 갖는다.
- 단일 객체 참조 변수가 참조하는 단일 객체를 반환하는 getInstance() 정적 메서드를 갖는다.
- 단일 객체는 쓰기 가능한 속성을 갖지 않는 것이 정석이다.
템플릿 메서드 패턴(Template Method Pattern)
템플릿 메서드는 부모 클래스에서 알고리즘의 골격을 정의하지만, 해당 알고리즘의 구조를 변경하지 않고 자식 클래스들이 알고리즘의 특정 단계들을 오버라이드(재정의)할 수 있도록 하는 행동 디자인 패턴입니다.
템플릿 메서드 패턴 구현 방법
public abstract class Animal {
// 템플릿 메서드
public void playWithOwner() {
System.out.println("귀염둥이 이리온...");
play();
runSomething();
System.out.println("잘했어");
}
}
// 추상 메서드
abstract void plya();
// Hook(갈고리) 메서드
void runSomething() {
System.out.println("꼬리 살랑 살랑~");
}
public class Dog extends Animal {
@Override
// 추상 메서드 오버라이딩
void play() {
System.out.println("멍! 멍!");
}
@Override
// Hook(갈고리) 메서드 오버라이딩
void runSomething() {
System.out.println("멍! 멍!~꼬리 살랑 살랑~");
}
}
상위 클래스인 Animal에는 템플릿(견본)을 제공하는 palyWithOnwer() 메서드와 하위 클래스에게 구현을 강제하는
play() 추상 메서드, 하위 클래스가 선택적으로
오버라이딩할 수 있는 runSomething() 메서드가 있습니다. 이 처럼 상위 클래스에 공통 로직을 수행하는 템플릿 메서드와 하위 클래스에 오버라이딩을 강제하는
추상메서드 또는 선택적으로
오버라이딩 할 수 있는 훅(Hook) 메서드를 두는 패턴을 템플릿 메서드 패턴이라고 합니다.
팩터리 메서드 패턴(Factory Method Pattern)
팩토리 메서드는 부모 클래스에서 객체들을 생성할 수 있는 인터페이스를 제공하지만, 자식 클래스들이 생성될 객체들의 유형을 변경할 수 있도록 하는 생성 패턴입니다.
팩터리 메서드 패턴 구현 방법
public abstract class Animal {
// 추상 팩터리 메서드
abstract AnimalToy getToy();
}
public abstract class AnimalToy {
// 추상 팩터리 메서드
abstract void identity();
}
public class Dog extends Animal{
// 추상 팩터리 메서드 오버라이딩
@Override
AnimalToy getToy() {
return new DogToy();
}
}
// 팩터리 메서드가 생성할 객체
public class DogToy extends AnimalToy {
public void identity() {
System.out.println("나는 테니스콩! 강아지의 친구");
}
}
위의 예제를 통해, 기억해야 할 것은 오버라이드된 메서드가 객체를 반환하는 패턴
입니다.
전략 패턴
전략 패턴은 알고리즘들의 패밀리를 정의하고, 각 패밀리를 별도의 클래스에 넣은 후 그들의 객체들을 상호교환할 수 있도록 하는 행동 디자인 패턴입니다. 전략 패턴을 구성하는 요소는 다음과 같습니다.
- 전략 메서드를 가진 전략 객체
- 전략 객체를 사용하는 컨텍스트(전략 객체의 사용자/소비자)
- 전략 객체를 생성해 컨텍스트에 주입하는 크라이언트(제3자, 전략 객체의 공급자)
전략 패턴 구현 방법
public interface Strategy {
public abstract void runStrategy();
}
---
public class StrategyGun implements Strategy {
@Override
public void runStrategy() {
System.out.println("탕, 타당, 타다당");
}
}
---
public class StrategySword implements Strategy {
@Override
public void runStrategy() {
System.out.println("챙.. 채쟁챙 챙챙");
}
}
---
public class StrategyBow implements Strategy {
@Override
public void runStrategy() {
System.out.println("슝.. 쐐액.. 쉑, 최종 병기");
}
}
public class Soldier {
void runContext(Strategy strategy) {
System.out.println("전투 시작");
strategy.runStrategy();
System.out.println("전투 시작");
}
}
public class Client {
void main() {
Strategy strategy = null;
Soldier rambo = new Soldier();
// 총을 람보에게 전달해서 전투를 수행하게 한다.
strategy = new StrategyGun();
rambo.runContext(strategy); // 출력 : 탕, 타당, 타다당
// 검을 람보에게 전달해서 전투를 수행하게 한다.
strategy = new StrategySword();
rambo.runContext(strategy); // 출력 : 챙.. 채쟁챙 챙챙
// 활을 람보에게 전달해서 전투를 수행하게 한다.
strategy = new StrategyBow();
rambo.runContext(strategy); // 출력 : 슝.. 쐐액.. 쉑, 최종 병기
}
}
위의 예제 처럼 전략을 다양하게 변경하면서 컨텍스트를 실행할 수 있습니다. 전략 패턴은 다양한 곳에서 다양한 문제 상황의 해결책으로 사용됩니다.
전략 패턴은 템플릿 메서드 패턴과 유사합니다. 템플릿 메서드 패턴은 상속
을 사용하고, 전략 패턴은 객체 주입
을 사용합니다. 단일 상속만이 가능한 자바 언어에서는 상속이라는 제한이 있는 템플릿 메서드 패턴보다는 전략 패턴이 더 많이 활용됩니다.
탬플릿 콜백 패턴(Template Callback Pattern - 견본/회신 패턴)
템플릿 콜백 패턴은 전략 패턴의 변형으로, 스프링의 3대 프로그래밍 모델 중 하나인 DI(의존성 주입)에서 사용하는 특별한 형태의 전략 패턴입니다. 템플릿 콜백 패턴은 전략 패턴과 모든 것이 동일한데 전략을 익명 내부 클래스
로 정의해서 사용하는 특징이 있습니다.
템플릿 콜백 패턴 구현 방법
public interface Strategy {
public abstract void runStrategy();
}
public class Soldier {
void runContext(Strategy strategy) {
System.out.println("전투 시작");
strategy.runStrategy();
System.out.println("전투 시작");
}
}
public class Client {
void main() {
Strategy strategy = null;
Soldier rambo = new Soldier();
// 총을 람보에게 전달해서 전투를 수행하게 한다.
rambo.runContext(new Strategy {
System.out.println("출력 : 탕, 타당, 타다당");
}); // 출력 : 탕, 타당, 타다당
// 검을 람보에게 전달해서 전투를 수행하게 한다.
rambo.runContext(new Strategy {
System.out.println("출력 : 챙.. 채쟁챙 챙챙");
}); // 출력 : 챙.. 채쟁챙 챙챙
// 활을 람보에게 전달해서 전투를 수행하게 한다.
rambo.runContext(new Strategy {
System.out.println("슝.. 쐐액.. 쉑, 최종 병기");
}); // 출력 : 슝.. 쐐액.. 쉑, 최종 병기
}
}
위의 코드를 람다를 사용해서 깔끔하게 만들 수도 있습니다.
public class Client {
void main() {
Strategy strategy = null;
Soldier rambo = new Soldier();
// 총을 람보에게 전달해서 전투를 수행하게 한다.
rambo.runContext(() -> System.out.println("출력 : 탕, 타당, 타다당"));
// 출력 : 탕, 타당, 타다당
// 검을 람보에게 전달해서 전투를 수행하게 한다.
rambo.runContext(() -> System.out.println("출력 : 챙.. 채쟁챙 챙챙"));
// 출력 : 챙.. 채쟁챙 챙챙
// 활을 람보에게 전달해서 전투를 수행하게 한다.
rambo.runContext(() -> System.out.println("슝.. 쐐액.. 쉑, 최종 병기"));
// 출력 : 슝.. 쐐액.. 쉑, 최종 병기
}
}
템플릿 콜백 패턴을 한 문장으로 말하면 전략을 익명 내부 클래스로 구현한 전략 패턴
입니다.
댓글 남기기