Link Search Menu Expand Document

스프링이 사랑한 디자인 패턴

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());
  }
}

프록시 패턴을 적용하기 전 클래스 다이어그램

https://imgur.com/uxoS66A.png

  • ClientWithNoProxyService 객체의 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) 적용