레이블이 소프트웨어 디자인패턴인 게시물을 표시합니다. 모든 게시물 표시
레이블이 소프트웨어 디자인패턴인 게시물을 표시합니다. 모든 게시물 표시

스테이트 패턴

스테이트 패턴이란, 객체 내부 상태에 따라 객체의 행동이 바뀌는 것이다.

뽑기 기계가 있다고 하자. 사람은 이 기계에 동전을 넣고 손잡이를 돌리면 뽑기기계의 상품을 받게 된다.  기계의 입장에선 동전이 있고 없고의 상태를 가질 수 있다. (설명을 쉽게 하기 위해 행동과 상태를 제한하였다.)

위에서 동전이 있고, 없고는 상태이며, 동전을 넣고 손잡이를 돌리는건 행동이 된다.
이를 간단하게 구현해보면 아래와 같다. state 상태에 따라 동전을 넣고 손잡이를 돌리는 행동에 대해 다르게 동작한다.

public class gamble {
 final static int NO_COIN = 0;
 final static int HAS_COIN = 1;
 
 int state = NO_COIN; // 초기상태는 동전이 없음 
 
 public void insertCoin() {
  if(state == NO_COIN) {
   System.out.println('동전을 넣으셨습니다.');
   state = HAS_COIN;
  }
  else if(state == HAS_COIN) {
   System.out.println('동전이 이미 있습니다..');
  }
 }
 
 public void turnHandle() {
  if(state == NO_COIN) {
   System.out.println('동전을 넣어주세요.');
  }
  else if(state == HAS_COIN) {
   System.out.println('상품이 나옵니다.');
   state = NO_COIN;
  }
 }
}
위의 구조에서는 문제가 없지만, 개발해야할 제품의 상태(NO_COIN 과 같은)와 행동(InsertCoin)이 많아질수록 변화에 유연하지 않는다. 이 때, 사용할 수 있는 패턴이 스테이트 패턴이다.

위 예를 스테이트 패턴으로 바꾸어보자.
우선 행동에 대한 메소드가 들어있는 인터페이스를 정의한다. (여기선 state 인터페이스)
다음은 state를 구현하는 상태 class를 만든다. 여러 상태가 있다면, 각 상태에 해당하는 class를 구현한다.


public class gamble {
 state hasCoin;
 state noCoin;
 
 state s = noCoin; // 초기상태는 동전이 없음 
 
 public gamble() {
  hasCoin = new HasCoin(this);
  noCoin = new NoCoin(this);
 }
 
 public setState(State s) {
  this.s = s;
 }
 public void insertCoin() {
  s.insertCoin();
 }
 public void turnHandle() {
  s.turnHandle();
 }
 // getter 메소드...
}

public interface state {
 void insertCoin();
 public void turnHandle();
}

public class HasCoin implements state {
 gamble g;
 
 public HasCoin(gamble g) {
  this.g = g;
 }
 
 public void insertCoin() {
  System.out.println('동전이 이미 있습니다..');
 }
 public void turnHandle() {
  System.out.println('상품이 나옵니다.');
  g.setState(gamble.getNoCoinState());
 }
}

public class NoCoin implements state {
 gamble g;
 
 public NoCoin(gamble g) {
  this.g = g;
 }
 
 public void insertCoin() {
  System.out.println('동전을 넣으셨습니다.');
  g.setState(gamble.getHasCoinState());
 }
 public void turnHandle() {
  System.out.println('동전을 넣어주세요.');
 }
}

템플릿 메소드 패턴

템플릿 메소드 패턴은 알고리즘을 캡슐화 한다. 이해가 안되니 아래를 보자.

Coffee와 Tea를 만드는 클래스가 있다. make()는 일련의 메소드를 모아 각 음료를 만드는 함수이다.

public class Coffee {
 void make() {
  hotWater();
  putCoffee();
  putCup();
  putSugar();
 }
 public void hotWater{
  // 물을 끓인다.
 }
 public void putCoffee{
  // 커피를 넣는다.
 }
 public void putCup{
  // 커피를 컵에 넣는다
 }
 public void putSugar{
  // 설탕을 넣는다.
 }
}

public class Tea {
 void make() {
  hotWater();
  putTea();
  putCup();
  putLemon();
 }
 public void hotWater{
  // 물을 끓인다.
 }
 public void putTea{
  // 차를 넣는다.
 }
 public void putCup{
  // 차를 컵에 넣는다
 }
 public void putLemon{
  // 레몬을 넣는다.
 }
}

Coffee와 Tea 메소드를 보니 코드가 중복되는 부분이 있으며, 일단 코드에 중복이 있을 경우 디자인을 고쳐야 하지 않을까 라는 생각이 필요하다.

Beberage(음료)라는 상위 클래스를 만들었다. make 메소드는 서브 클래스마다 다르므로 추상 메소드로 선언한다. hotwater, putCup 메소드는 서브클래스로 공통으로 쓰기때문에 부모 클래스에 정의하였다.  Coffee, Tea 클래스는 make 메소드를 내부에서 정의하고 알고리즘의 다른 부분들(putCoffee, putTea / putSugar, putLemon)은 서브클래스 내부에서 정의 하였다.

위의 다이어그램을 다시 생각해보자. 사실 putCoffe와 putTea는 만드는 과정 중 뜨거운 물에 넣는다는 의미에서 같은 맥락이고, addSugar, addLemon는 만들어진 음료 위에 무엇을 넣는것에서 같은 맥락이다.
따라서 putCoffee와 putTea를 putSomething, addSugar, addLemon을 addItem 으로 대체해보자. (조금 어거지다)


public class Beverage {
 final void make() {
  hotWater();
  putSomething();
  putCup();
  addItem();
 }
 abstract void putSomething();
 abstract void addItem();
 
 void hotWater{
  // 물을 끓인다.
 }
 void putCup{
  // 커피를 컵에 넣는다
 }
}

public class Coffee extends Beverage{
 public void putSomething{
  // 커피를 넣는다.
 }
 public void addItem{
  // 설탕을 넣는다.
 }
}

public class Tea {
 public void putSomething{
  // 차를 넣는다.
 }
 public void addItem{
  // 레몬을 넣는다.
 }
}

지금까지 한 것이 템플릿 메소드 패턴이라 할 수 있다.
Beverage의 make가 캡슐화된 알고리즘이 되며, 템플릿 메소드에서는 알고리즘의 각 단계들을 정의하며 한 개 이상의 알고리즘 단계가 서브클래스에 의해 제공될 수 있다.

처음 만든 클래스와 템플릿 메소드를 사용한 경우를 비교하면 아래표와 같다.























어댑터 패턴과 퍼사드 패턴

해외 여행을 하다보면 오른쪽과 같은 전기 꼽는데가 있고 한국에서 220v 충전기를 쓰려면 왼쪽과 같은 어댑터를 사용해야 한다.

IT 측면에서 보면, 이미 개발되어 있는 시스템이 있는데 새로운 제품과 통신하는 과정에서 서로의 인터페이스가 다를 수 있다. 개발된 시스템을 바꿀수도 있지만, 이를 그대로 두고 중간에 인터페이스를 맞춰주는 모듈이 있다고 하면 이를 어댑터라고 할 수 있다.

어댑터 패턴이란, 한 클래스의 인터페이스를 클라이언트에서 사용하고자 하는 다른 인터페이스로 변환하는 것.

퍼사드 패턴이란, 서브시스템의 정의된 다양한 인터페이스들을 어떤 기준으로 묶어 고 수준 인터페이스를 제공하여 쉽게 사용하도록 하는 것.

가령 식사를 한다고 가정하자 아래와 같이 식사에 대한 3가지 기능이 있다고 할 때, 이를 하나로 묶어 하나의 인터페이스로 3가지 기능을 수행한다면 퍼사드 패턴이라고 할 수 있다.

1. 음식을 만든다
2. 밥을 먹는다
3. 설거지를 한다.

퍼사드/어댑터/데코레이션 패턴의 차이를 간단히 설명하면 아래와 같다.
퍼사드 - 인터페이스를 간단하게 바꿈
어댑터 - 한 인터페이스를 다른 인터페이스로 변환한다.
데코레이션 - 인터페이스는 두고 책임만 추가

커맨드 패턴

객체지향 모델링은 실 세계를 모델링 하는 것 처럼 소프트웨어를 모델링 하는 것.
레스트토랑을 생각해보면 손님이 들어와 주문서를 작성하면, 웨이터는 주문서를 받아 주방으로 전달한다. 웨이터 입장에선 주문을 받아 그대로 주방으로 전달하면 되기 때문에 고객이 어떤 주문을 했는지 주방에서 어떤 요리를 만드는지는 전혀 신경 쓸 필요가 없다.

커맨드 패턴이란, 요구사항을 객체로 캡슐화 할 수 있는 디자인 패턴으로 작업을 요청한 쪽과 처리하는 쪽을 분리할 수 있다.

집안에서 공용으로 쓰는 리모콘이 있다고 가정하자. 이 리모콘을 통해 문을 열고 닫을 수 있으며, 불을 키고 끌수있고, 커튼을 열고 닫을 수 있다. 여기서 요구사항은 리모콘을 통해 요청되는 작업들이며 이 작업들을 캡슐화하여 요청작업과 실행작업을 분리시킨다.

간단한 예로 불을 켜는 리모콘 소스를 보자. (예시를 위한 코드지, 실제 동작하지 않음)

public interface Command {
 public void execute();
}

public class LightOnCommand implements Command {
 Light light;
 
 public LightOnCommand(Light light) {
  this.light = light;
 }
 
 public void execute() {
  light.on();
 }
}

public class SimpleRemoteControl {
 Command slot;

 public SimpleRemoteControl() {}
 public void setCommand(Command command) {
  slot = command;
 }
 public void putButton() {
  slot.execute();
 }
}
SimpleRemoteControl 클래스는 Command slot 변수를 가지며, setCommand 메소드를 통해 slot 변수를 set 할 수 있다. Command 변수로는 불을 키고끄고, 문을 열고 닫는 등 다양한 명령들이 들어올 수 있다.

Command 인터페이스를 구현한 클래스(여기선 LightOnCommand)들이 실행작업을 구현하며, 요청작업은 SimpleRemoteControl의 setCommand를 호출한 쪽에서 명령을 요청하므로 커맨드 패턴처럼 분리된 걸 알 수 있다.

위 구조로 확장을 하자. 불을 켜고 끄고, 문을 열고 닫고, 에어콘을 키고 끄는 형태를 다이어그램으로 보면 아래와 같다.

집 주인의 다양한 요청(불, 에어콘, 문, 커튼 등등)의 구현은 Command 인터페이스를 구현하는 클래스들에서 맡고 있다. 요청은 SimpleRemoteControl 클래스에서 SetCommand로 수행한다. SimpleRemoteControl은 집 주인의 요청에 대해 신경쓸필요가 없으며 execute()로 실제 어떤 행동이 수행되는지 알 필요가 없다.







싱글톤 패턴

개발하다 보면 싱글톤 이라는 말을 종종 듣곤한다.
싱글톤 패턴이란, 클래스의 객체는 오직 하나만 만들어지고 어디서든 그 객체에 접근할 수 있어야 한다.

아래 코드는 싱글톤의 예이다. 주목할 부분은 생성자인데, private로 선언했기 때문에 singeton class 내부에서만 인스턴스를 생성할 수 있다. 인스턴스를 생성하는 주체는 오직 자기 자신이며, 한번 만들어지면 if문에 따라 존재하는 인스턴스를 반환하기 때문에 두번 이상 생성될 수 없다. 또한 public getInstance 메소드를 통해 어디서든 singleton 인스턴스를 받을 수 있다.

public class singleton {
 private static singleton only_one_instance;
 
 private singleton() {}
 
 public static singleton getInstance() {
  if(only_one_instance == null) {
   only_one_instance = new singleton();
  }
  return only_one_instance;
 }
}
멀티 스레드 환경에서 싱글톤 객체의 생성은 종종 문제를 일으킨다.
스레드 A,B가 있다고 할때 위 코드를 실행하는 순서가 아래와 같은 흐름을 탄다고 하자.

1. 스레드 A가 if(only_one_instance == null) { 구문을 수행하였다.
2. 스레드 B가 cpu time을 얻어 f(only_one_instance == null) { 구문을 수행하였다.
3. 스레드 B가 only_one_instance = new singleton(); 구문을 수행하여 instance를 생성했다. 4. 스레드 A가 cpu time을 얻어 only_one_instance = new singleton(); 구문을 수행하여 instance를 생성했다. 

위와 같은 흐름으로 수행되면 인스턴스가 2개가 생성되어 예상치 못한 결과를 얻을 수 있다. 책에서는 위 현상을 막기 위해 3가지 정도의 방법을 제안했다.

1. if문으로 인스턴스를 체크하여 생성하는 로직에 lock을 걸어 스레드를 동기화 하는 방법
2. 내부 클래스 인스턴스 (only_one_instance)를 선언과 동시에 초기화하는 방법
3. volatile 키워드를 사용하여 멀티스레딩 환경에서도 안전하게 사용하는 방법
 - private volatile static singleton only_one_instance;

팩토리 패턴

* Simple factory


kimbob 가게를 생각해보자. 주문을 하면 김밥객체를 받아 재료를 준비하고 만들고 돌돌말아 자른뒤 포장한다고 가정해보자. 이를 코드로 보면 아래와 같다.

Kimbob orderKimbob() {
 Kimbob kimbob = new Kimbob();
 
 kimbob.prepare();
 kimbob.make();
 kimbob.roll();
 kimbob.cut();
 kimbob.box();
}
kimbob 가게의 김밥이 한줄 뿐이겠는가? 여러 김밥이 존재할 수 있다.

Kimbob orderKimbob(String type) {
 Kimbob kimbob;
 
 if(type.equals("normal")) {
  kimbob = new Kimbob();
 }
 else if(type.equals("tuna")) {
  kimbob = new TunaKimbob();
 }
 else if(type.equals("kimchi")) {
  kimbob = new KimchiKimbob();
 }
 
 Kimbob kimbob = new Kimbob();
 
 kimbob.prepare();
 kimbob.make();
 kimbob.roll();
 kimbob.cut();
 kimbob.box();
}

제일 중요한 디자인 원칙 중 하나는 변경되는 부분을 변경되지 않는 부분과 분리하여 캡슐화 하는 것이다. 위 예를 보면 변경되는 부분은 김밥 객체를 받는 부분이다. 일단 객체만 받으면 준비,만들기,말기,자르는 기능들은 변하지 않는다.

그럼 위에서 변경되는 부분을 factory라는 객체로 캡슐화 시켜보자


public class SimpleKimbobFactory {
 public Kimbob createKimbob(String type) {
  Kimbob kimbob = null;
  
  if(type.equals("normal")) {
   kimbob = new Kimbob();
  }
  else if(type.equals("tuna")) {
   kimbob = new TunaKimbob();
  }
  else if(type.equals("kimchi")) {
   kimbob = new KimchiKimbob();
  }
  
  return kimbob
 }
}

이렇게 변경할 경우 무엇이 좋은거지? 궁금증이 들 수 있다. kimbob 객체의 생성을 배달, 가격, 설명 등의 기능에서 필요하다고 하면, factory로 캡슐화한 클래스만 변경하면 된다.


* factory 메소드 패턴

위의 김밥집이 잘되어 중국과 일본에도 진출되었다고 생각해보자. 기존의 simple factory를 제거하고 아래와 같이 factory를 만들 수 있다.

ChinaKimbobFactory cFactory = new ChinaKimbobFactory();
KimbobStore chinakimbobStore = new KimbobStorecFactory();
chinakimbobStore.orderKimbob('tuna');

JapanKimbobFactory jFactory = new JapanKimbobFactory();
KimbobStore japankimbobStore = new KimbobStorecFactory();
japankimbobStore.orderKimbob('kimchi');

이렇게 적용해보니 나라마다 만드는 방식이 조금씩 달라 문제가 발생했다. (책에서 이와 같은 형식으로 설명하는데 뭐가 문제인지 잘 이해가 안간다 똑똑하고 싶어...)

이때 김밥을 만드는 활동은 KimbobStore 클래스에 국한시키면서, 나라마다 고유의 스타일을 살릴 수 있는 방법이 있다. factory 패턴을 적용하는 것이다. 정의는 뒤에서 하고 먼저 아래를 보자.

createKimbob 메소드를 kimbobStore에 다시 집어놓고 추상메소드로 선언한다. 각 나라에서는 고유의 스타일에 맞게 메소드를 정의할 것이다.

public abstract class KimbobStore{

 public Kimbob orderKimbob(String type) {
  Kimbob kimbob;
  kimbob = createKimbob(type);

  kimbob.prepare();
  kimbob.make();
  kimbob.roll();
  kimbob.cut();
  kimbob.box();

  return kimbob;
 }
 protected abstract Kimbob createKimbob(String type);
} 
추상화 메소드 createKimbob 선언하고 KimbobStore의 서브클래스 JapanKimbobFactory, ChinaKimbobFactory에서 createKimbob 메소드를 각 나라 스타일에 맞게 정의하면 된다.

 팩토리 메소드 패턴에서는 객체의 생성을 캡슐화 한다. 그리고 서브 클래스에서 어떤 객체를 만들지를 결정한다.

위의 Simple factory 부터 정리해보면 객체의 생성이 변경되는 부분일 때, 이를 캡슐화하고 어떤 객체가 만들어질지는 서브클래스에서 결정하게 되면 factory 메소드 패턴을 쓴다고 볼 수 있다.

데코레이터 패턴

케익 데코레이터 라고 한다면, 케익 위에 다양한 장식들로 케익을 꾸미는 것을 의미한다. 여기서 장식물을 뺸 케익 자체는 변경되지 않는다.

소프트웨어 디자인 패턴의 데코레이션이란, 객체의 기능을 유연하게 확장할 수 있는 방법을 제공한다.

데코레이션 패턴은 OCP(open-closed principle) 원칙을 따른다.
OCP란 클래스 확장은 열려있고 코드 변경에 대해서는 닫혀 있어야 한다는 것이다.

OCP 원칙 자체는 좋아보이지만, 이를 적용시키기 전 충분함 검토가 필요하다. 무조건 OCP를 적용시키면 쓸데없이 복잡해지고 시간이 들 수 있다.

이론적으로 뭉뚱그리면 어렵지않은데, 어찌 자세히 들어갈수록 잘 이해가안되 comment만 하고 넘어간다.

옵저버 디자인 패턴

옵저버 디자인 패턴


옵저버 패턴은 신문으로 예를 들어보자. 신문의 출판사와 구독자가 있을 것이다.

출판사는 신문을 출판하고, 구독자는 출판사에 구독 신청을 하면 신문이 나올때마다 해당 신문을 배달받을 수 있다. 구독자로 있는 한, 신문이 나올때 마다 구독할 수 있다.
신문을 구독하고 싶지 않으면 해지를 신청하고, 더 이상 신문이 나와도 구독받지 않는다.

옵저버 패턴에서 출판사를 주제, 구독자를 옵저버라고 부른다.
주제의 데이터가 달리지면 등록한 옵저버들에 대해 소식이 전해진다.

옵저버 패턴의 정의는 한 객체의 상태가 바뀌면, 그 객체에 의존하는 다른 객체들에게 자동으로 내용이 갱신되는 일대다 의존성을 정의한다.

아래를 보자.
인터페이스 Subject를 구현하는 주제(NewsSubject)와 Observer 인터페이스를 구현하는 NewsObserverA와 NewsObserverB가 있다.

주제(NewsSubject)는 옵저버들을 추가/삭제하는 registerObserver와 removeObserver 메소드를 구현하며, 데이터가 변경될 경우 옵저버에게 알리는 notifyObserver 메소드를 구현한다. 주제의 데이터가 변경될 경우 Observer 인터페이스를 구현하는 모든 클래스들은 update 메소드를 통해 변경된 데이터를 다룰 수 있다.


아래 코드는 실제로 동작하진 않지만 위 클래스 구성도를 구현하면 이렇다 라는 개념으로 코딩하였다.

Subject 인터페이스를 정의하고, NewsSubject class가 구현하도록 하였다. NewsSubject는 각 옵저버들을 추가/삭제하는 기능을 구현하고 데이터 변경 시, 각 옵저버들의 update 함수를 호출함으로써 옵저버 패턴을 구현할 수 있다.

public interface Subject {
 public void registerObject(Observer o);
 public void removeObject(Observer o);
 public void notifyObject();
}

public class NewsSubject implements Subject {
 private ArrayList observers;
 private int news;
 
 public void registerObject(Observer o) {
  Observer.add(o);
 }
 
 public void removeObject(Observer o) {
  Observer.remove(o);
 }
 
 public void notifyObject() {
  for(int i=0; i<observers.size(); i++) {
   Observer observer = (Observer)observers.get();
   observer.update(news);
  }
 }
 
 public void makeNews(int news) {
  this.news = news;
  notifyObject();
 }
}

아래는 Observer 인터페이스를 선언하고 NewsObserverA 클래스에서 Observer를 구현하였다. 
NewsSubject 주제가 makeNews를 통해 새로운 신문을 출판하면 각 옵저버의 update(news) 함수를 호출하고,
옵저버는 해당 데이터를 수신하여 자신이 원하는 일을 할 수 있다.

public interface Observer {
 public void update(int news);
}

public class NewsObserverA implements Observer {
 private int news;
 private Subject newsSubject;
 
 public NewsObserverA(Subject newsSubject) {
  this.newsSubject = newsSubject;
 }
 
 public void update(int news) {
  this.news = news;
  
  // do somethig by news
 }
}


느슨한 결합 (loose coupling)

두 객체가 느슨하게 결합되어 있다는 건, 둘의 상호작용을 서로가 잘 모른다는 의미
옵저버 패턴에서는 주제와 옵저버가 서로 느슨하게 결합 됨

왜그럴까?
- 주제가 옵저버에 대해 하는건 특정 인터페이스(Observer)를 구현한다는 것만 알고있음
- 옵저버는 언제든지 새로 추가될 수 있다. 추가 된 Observer는 Observer 인터페이스만 구현하면 주제를 전혀 변경할 필요가 없다.

스트래티지 패턴 (Strategy Pattern)

스트래티지 패턴이란.. 기능을 정의하고 변경되는 부분을 캡슐화하여 교환해서 사용하는 패턴... 말로는 어려우니 아래를 보자.

Dog라는 부모클래스를 Japan, korea dog에서 상속받고 있다.
모든 개들은 walk (걸음) 속성을 지니고 있으므로 부모 class인 Dog에 구현하고, 외형(display)은 모두 다르므로 Dog에 추상 메소드로 정의하고 자식 클래스들이 오버라이딩 하고 있다.


이 때, japan dog에 run (달리는) 기능이 필요해 부모 Dog 클래스에서 구현하고 자식 클래스들이 상속받았다.
하나 문제가 생겼다. 한국개들은 달리는 속도가 다른데 부모 클래스의 run 메소드를 그대로 상속 받는 점이였다.  korea Dog 클래스에 run 함수를 오버라이딩 하여 처리하도록 하였다.
문제는 다음과 같은 가정에서 발생한다. 만약 전 세계의 개들을 객체로 만드는 일이 필요하다. 개들마다 속도가 다르다면 하위 클래스의 run 함수 전부를 오버라이딩 할 것 인가? 가능은 하지만 이 방법은 객체지향의 장점을 잘 활용할 수 없다고 본다.

strategy 패턴은 변경되는 부분을 캡슐화하고 객체들간 교환을 통해 디자인하는 패턴이다.
strategy 패턴의 디자인 원칙을 보자면 아래와 같다.
 1. 프로그램의 변경되는 부분을 찾아내고 변경되지 않는 부분으로부터 분리한다.
 2. 상속보다는 구성을 활용한다.

위 예제로 돌아가보면, 변경되는 부분은 무엇인가? run 기능이다. 이 기능을 변하지 않는 다른 기능들과 분리시켜야 한다. 또한 상속을 배제하고 인터페이스를 통해 접근하도록 한다.
Dog 클래스는 인터페이스 변수 하나를 두는 것으로 더 이상 run에 대한 기능은 신경쓰지 않아도 된다.


위 그림을 코드로 표현해보자. 우선 인터페이스 부분은 아래와 같다.


interface DoRun {
 public void run();
}

class fast implements DoRun {
 public void run() {
  System.out.println("빠름");
 }
}

class slow implements DoRun {
 public void run() {
  System.out.println("느림");
 }
}
클래스 부분을 코드로 보면 아래와 같다.

class koreaDog extends Dog {
 public koreaDog() { dorun = new fast(); }
 
 void display() {
  System.out.println("한국개생김새");
 }
}

class japanDog extends Dog {
 public japanDog() { dorun = new slow(); }
 
 void display() {
  System.out.println("일본개생김새");
 }
}

public abstract class Dog {
 DoRun dorun;
 
 public Dog() {}
 public void walk() { 
  System.out.println("모든 개는 걸어요");
 }
 public void performRun() {
  dorun.run();
 }
 abstract void display();
 
 public static void main(String[] args) {
  Dog dog = new japanDog();
  dog.display();
  dog.performRun();
  
  dog = new koreaDog();
  dog.display();
  dog.performRun();
 }
}

정리를 해보자. 스트래티지 패턴이란, 객체마다 달라지는 부분은 오버라이딩으로 처리하기 보단, 변경되는 부분을 변경되지 않는 부분과 나누고 인터페이스를 선언해 객체간의 교환을 통해 문제를 해결하는 디자인 패턴이라 생각하면된다.

레퍼런스
- Head First 디자인패턴