스프링이 사랑한 디자인 패턴
Adapter Pattern
package adapterPattern;
public class ServiceA {
void runServiceA() {
System.out.println("ServiceA");
}
}
package adapterPattern;
public class ServiceB {
void runServiceB() {
System.out.println("ServiceB");
}
}
package adapterPattern;
public class ClientWithNoAdapter {
public static void main(String[] args) {
ServiceA sa1 = new ServiceA();
ServiceB sb1 = new ServiceB();
sa1.runServiceA();
sb1.runServiceB();
}
}
package adapterPattern;
public class AdapterServiceA {
ServiceA sa1 = new ServiceA();
void runService() {
sa1.runserviceA();
}
}
package adapterPattern;
public class AdapterServiceB {
ServiceB sb1 = new ServiceB();
void runService() {
sb1.runserviceB();
}
}
package adapterPattern;
public class ClientWithAdapter {
public static void main(String[] args) {
AdapterServiceA asa1 = new AdapterServiceA();
AdapterServiceB asb1 = new AdapterServiceB();
asa1.runService();
asb1.runService();
}
}
- 호출되는 쪽의 메서드를 호출하는 쪽의 코드에 대응하도록 중간에 변환기를 통해 호출하는 패턴
Proxy Pattern
package proxyPattern;
public class Service {
public String runSomething() {
return "서비스 짱!!!";
}
}
package proxyPattern;
public class ClientWithNoProxy {
public static void main(String[] args) {
// 프록시를 이용하지 않은 호출
Service service = new Service();
System.out.println(service.runSomething());
}
}
ClientWithNoProxy
가Service
객체의runSomething()
메서드를 직접 호출
package proxyPattern;
public interface IService {
String runSomething();
}
package proxyPattern;
public class Service implements IService {
public String runSomething() {
return "서비스 짱!!!";
}
}
package proxyPattern;
public class Proxy implements IService {
IService service1;
public String runSomething() {
System.out.println("호출에 대한 흐름 제어가 주목적, 반환 결과를 그대로 전달");
service1 = new Service();
return service1.runSomething();
}
}
package proxyPattern;
public class ClientWithProxy {
public static void main(String[] args) {
// 프록시를 이용한 호출
IService proxy = new Proxy();
System.out.println(proxy.runSomething());
}
}
- 대리자는 인터페이스를 사용하여 실제 서비스와 같은 이름의 메서드를 구현
- 대리자는 실제 서비스에 대한 참조 변수를 가짐 (합성)
- 대리자는 실제 서비스의 같은 이름을 가진 메서드를 호출하고 그 값을 클라이언트에 반환
대리자는 실제 서비스의 메서드 호출 전후에 별도의 로직을 수행할 수도 있음
- 제어 흐름을 조정하기 위한 목적으로 중간에 대리자를 두는 패턴
- 개방 폐쇄 원칙(OCP)과 의존 역전 원칙(DIP)이 적용됨
Decorator Pattern
- 프록시 패턴과 구현 방법이 같음
- 프록시 패턴은 반환값을 그대로 전달, 제어 흐름의 변경 또는 별도 로직 처리가 목적
- 데코레이터 패턴은 반환값에 장식을 덧입힘
package decoratorPattern;
public interface IService {
public abstract String runSomething();
}
package decoratorPattern;
public class Service implements IService {
public String runSomething() {
return "서비스 짱!!!";
}
}
package decoratorPattern;
public class Decorator implements IService {
IService service;
public String runSomething() {
System.out.println("호출에 대한 장식 주목적, 클라이언트에게 반환 결과에 장식을 더하여 전달");
service = new Service();
return "정말" + service.runSomething();
}
}
package decoratorPattern;
public class ClientWithDecorator {
public static void main(String[] args) {
IService decorator = new Decorator();
System.out.println(decorator.runSomething());
}
}
- 장식자는 인터페이스를 사용하여 실제 서비스와 같은 이름의 메서드를 구현
- 장식자는 실제 서비스에 대한 참조 변수를 가짐 (합성)
- 장식자는 실제 서비스와 같은 이름을 가진 메서드를 호출하고, 그 반환값에 장식을 더해 클라이언트에게 반환
장식자는 실제 서비스의 메서드 호출 전후에 별도의 로직을 수행할 수도 있음
- 메서드 호출의 반환값에 변화를 주기 위해 중간에 장식자를 두는 패턴
- 개방 폐쇄 원칙(OCP)과 의존 역전 원칙(DIP)이 적용됨
Singleton Pattern
- 커넥션 풀, 스레드 풀, 디바이스 설정 객체 등과 같은 인스턴스를 하나만 만들고 재사용
new
를 실행할 수 없도록 생성자에private
접근 제어자 지정- 유일한 단일 객체를 반환할 수 있는 정적 메서드 필요
- 유일한 단일 객체를 참조할 정적 변수 필요
package singletonPattern;
public class Singleton {
static Singleton singletonObject; // 정적 참조 변수
private Singleton() { }; // private 생성자
// 객체 반환 정적 메서드
public static Singleton getInstance() {
if (singletonObject == null) {
singletonObject = new Singleton();
}
return singletonObject;
}
}
package singletonPattern;
public class Client {
public static void main(String[] args) {
// private 생성자이므로 new를 통해 인스턴스를 생성할 수 없다.
// Singleton s = new Singleton();
Singleton s1 = Singleton.getInstance();
Singleton s2 = Singleton.getInstance();
Singleton s3 = Singleton.getInstance();
System.out.println(s1); // SingletonPattern.Singleton@263c8db9
System.out.println(s2); // SingletonPattern.Singleton@263c8db9
System.out.println(s3); // SingletonPattern.Singleton@263c8db9
s1 = null;
s2 = null;
s3 = null;
}
}
private
생성자를 가짐- 단일 객체 참조 변수를 정적 속성으로 가짐
- 단일 객체 참조 변수가 참조하는 단일 객체를 반환하는
getInstance()
정적 메서드를 가짐 단일 객체는 쓰기 가능한 속성을 갖지 않음
- 클래스의 인스턴스, 즉 객체를 하나만 만들어 사용하는 패턴
Template Method Pattern
public class Dog {
public void playWithOwner() {
System.out.println("귀염둥이 이리온...");
System.out.println("멍! 멍!");
System.out.println("꼬리 살랑 살랑~");
System.out.println("잘했어");
}
}
public class Cat {
public void playWithOwner() {
System.out.println("귀염둥이 이리온...");
System.out.println("야옹~ 야옹~");
System.out.println("꼬리 살랑 살랑~");
System.out.println("잘했어");
}
}
- 두 클래스의
playWithOwner()
메서드는 한 줄을 제외하고 모두 동일 - 동일한 부분은 상위 클래스로, 달라지는 부분만 하위 클래스로 분할
package templateMethodPattern;
public abstract class Animal {
// 템플릿 메서드
public void playWithOwner() {
System.out.println("귀염둥이 이리온...");
play();
runSomething();
System.out.println("잘했어");
}
// 추상 메서드
abstract void play();
// Hook(갈고리) 메서드
void runSomething() {
System.out.println("꼬리 살랑 살랑~");
}
}
package templateMethodPattern;
public class Dog extends Animal {
@Override
// 추상 메서드 오버라이딩
void play() {
System.out.println("멍! 멍!");
}
@Override
// Hook(갈고리) 메서드 오버라이딩
void runSomething() {
System.out.println("멍! 멍!~ 꼬리 살랑 살랑~");
}
}
package templateMethodPattern;
public class Cat extends Animal {
@Override
// 추상 메서드 오버라이딩
void play() {
System.out.println("야옹! 야옹!");
}
@Override
// Hook(갈고리) 메서드 오버라이딩
void runSomething() {
System.out.println("야옹! 야옹!~ 꼬리 살랑 살랑~");
}
}
package templateMethodPattern;
public class Driver {
Animal bolt = new Dog();
Animal kitty = new Cat();
bolt.playWithOwner();
System.out.println();
System.out.println();
kitty.playWithOwner();
}
- 아래와 같은 메서드를 두는 패턴
- Template method: 상위 클래스에서 공통 로직을 수행
- Abstract method: 하위 클래스의 오버라이딩을 강제
- Hook method: 선택적으로 오버라이딩 할 수 있음
- 상위 클래스의 견본 메서드에서 하위 클래스가 오버라이딩한 메서드를 호출하는 패턴
- 의존 역전 원칙(DIP)을 활용하고 있음
Factory Method Pattern
public factoryMethodPattern;
public abstract class Animal {
// 추상 팩터리 메서드
abstract AnimalToy getToy();
}
package factoryMethodPattern;
// 팩터리 메서드가 생성할 객체의 상위 클래스
public abstract class AnimalToy {
abstract void identify();
}
package factoryMethodPattern;
// 팩터리 메서드가 생성할 객체의 상위 클래스
public class Dog extends Animal {
// 추상 팩터리 메서드 오버라이딩
@Override
AnimalToy getToy() {
return new DogToy();
}
}
package factoryMethodPattern;
// 팩터리 메서드가 생성할 객체
public class DogToy extends AnimalToy {
public void identify() {
System.out.println("나는 테니스공! 강아지의 친구!");
}
}
package factoryMethodPattern;
// 팩터리 메서드가 생성할 객체
public class CatToy extends AnimalToy {
public void identify() {
System.out.println("나는 캣타워! 고양이의 친구!");
}
}
package factoryMethodPattern;
public class Driver {
public static void main(String[] args) {
// 팩터리 메서드를 보유한 객체를 생성
Animal bolt = new Dog();
Animal kitty = new Cat();
// 팩터리 메서드가 반환하는 객체들
AnimalToy = boltBall = bolt.getToy();
AnimalToy = kittyTower = kitty.getTower();
// 팩터리 메서드가 반환한 객체들을 사용
boltBall.identify();
kittyTower.identify();
}
}
- 오버라이드된 메서드가 객체를 반환하는 패턴
- 의존 역전 원칙(DIP)을 활용
Strategy Pattern
- 전략 패턴을 구성하는 세 요소
- 전략 메서드를 가진 전략 객체
- 전략 객체를 사용하는 컨텍스트(전략 객체의 사용자/소비자)
- 전략 객체를 생성해 컨텍스트에 주입하는 클라이언트(제3자, 전략 객체의 공급자)
package strategyPattern;
public interface Strategy {
public abstract void runStrategy();
}
package strategyPattern;
public class StrategyGun Implements Strategy {
@Override
public void runStrategy() {
System.out.println("탕, 타당, 타다당");
}
}
package strategyPattern;
public class StrategySword Implements Strategy {
@Override
public void runStrategy() {
System.out.println("챙.. 채쟁챙 챙챙");
}
}
package strategyPattern;
public class StrategyBow Implements Strategy {
@Override
public void runStrategy() {
System.out.println("슝.. 쐐액.. 쉑, 최종 병기");
}
}
package strategyPattern;
public class Soldier {
void runContext(Strategy strategy) {
System.out.println("전투 시작");
strategy.runStrategy();
System.out.println("전투 종료");
}
}
package strategyPattern;
public class Client {
public static void main(String[] args) {
Strategy strategy = null;
Soldier rambo = new Soldier();
// 총을 람보에게 전달해서 전투를 수행하게 한다.
strategy = new StrategyGun();
rambo.runContext(strategy);
System.out.println();
// 검을 람보에게 전달해서 전투를 수행하게 한다.
strategy = new StrategySword();
rambo.runContext(strategy);
System.out.println();
// 활을 람보에게 전달해서 전투를 수행하게 한다.
strategy = new StrategyBow();
rambo.runContext(strategy);
}
}
- 같은 문제의 해결책으로 상속을 이용하는 템플릿 메서드 패턴과 객체 주입을 통한 전략 패턴 중에 선택/적용
- 클라이언트가 전략을 생성해 전략을 실행할 컨텍스트에 주입하는 패턴
Template Callback Pattern
package templateCallbackPattern;
public interface Strategy {
public abstract void runStrategy();
}
package templateCallbackPattern;
public class Soldier {
public void runStrategy(Strategy strategy) {
System.out.println("전투 시작");
strategy.runStrategy();
System.out.println("전투 종료");
}
}
package templateCallbackPattern;
public class Client {
public static void main(String[] args) {
Soldier rambo = new Soldier();
rambo.runContext(new Strategy() {
@Override
public void runStrategy() {
System.out.println("총! 총초종총 총! 총!");
}
});
System.out.println();
rambo.runContext(new Strategy() {
@Override
public void runStrategy() {
System.out.println("칼! 카가갈 칼! 칼!");
}
});
System.out.println();
rambo.runContext(new Strategy() {
@Override
public void runStrategy() {
System.out.println("도끼! 독독..도도독 독끼!");
}
});
}
}
- 중복된 코드가 존재
package templateCallbackPatternRefactoring;
public interface Strategy {
public abstract void runStrategy();
}
package templateCallbackPatternRefactoring;
public class Soldier {
void runContext(String weaponSound) {
System.out.println("전투 시작");
executeWeapon(weaponSound).runStrategy();
System.out.println("전투 종료");
}
private Strategy excuteWeapon(final String weaponSound) {
return new Strategy() {
@Override
public void runStrategy() {
System.out.println(weaponSound);
}
}
}
}
package templateCallbackPatternRefactoring;
public class Client {
public static void main(String[] args) {
Soldier rambo = new Soldier();
rambo.runContext("총! 총조종총 총! 총!");
System.out.println();
rambo.runContext("칼! 카가갈 칼! 칼!");
System.out.println();
rambo.runContext("도끼! 독독..도도독 독끼!");
}
}
- 전략을 익명 내부 클래스로 구현한 전략 패턴”
- 개방 폐쇄 원칙 (OCP), 의존 역전 원칙 (DIP) 적용
- 개방 폐쇄 원칙 (OCP), 의존 역전 원칙 (DIP) 적용