북극곰의 개발일기

디자인 패턴 소개 / 스트래티지 패턴





posted by purplebeen on Wed Jan 10 2018 18:56:07 GMT+0900 (KST) in JAVA


디자인패턴 : 코드를 재사용하느 것과 마찬가지로 경험을 재사용하는 것

객체지향 디자인 원칙

  • 애플리케이션에서 달라지는 부분을 찾아내고, 달라지지 않는 부분으로 부터 분리 시킨다.

    -> 달라지는 부분을 찾아서 나머지 코드에 영향을 주지 않도록 캡슐화

  • 구현이 아닌 인터페이스에 맞춰서 프로그래밍한다.

    -> 실제 실행시 쓰이는 객체가 코드에 의해서 고정되지 않도록, 어떤 상위형식(super type)에 맞춰서 프로그래밍함으로써 다형성을 활용해야함.

  • 상속보다는 구성을 활용한다

스트래티지 패턴(Strategy Pattern) : 알고리즘군을 정의하고 각각을 캡슐화하여 교환해서 사용할 수 있도록 만든다.. 스트래티지 패턴을 활용하면 알고리즘을 상요하는 클라이언트와는 독립적으로 알고리즘을 변경할 수 있다.

예를들어서 오리를 시뮬레이션 하는 프로그램을 만든다고 하자.

오리가 난다거나(fly), 운다거나(Quack), 화면에 어떤 오리인지 보여주는(display)를 생각할 수 있다.

근데 만약에 이 프로그램을 상속을 이용해서 구성한다 하면, 모형 오리가 날아간다거나 아니면 나무오리에서 소리가 난다던가 하는 일이 벌어 질 수 있다.

그렇다고 이것들을 추상 메소드로 처리하게 되면 계속 새로운 오리 객체를 생성할때마다

각 메소드를 오버라이딩 해 줘야 하므로 코드의 재사용성이 떨어진다.

이를 해결하기 위해서는 먼저 애플리케이션에서 달라지는 부분인 fly()와 quack()을 분리 시켜야 한다.

일단 먼저 Super Class가 되는 Duck() Class를 보면,

package Chapter1;

public abstract class Duck {
	FlyBehavior flyBehavior;
	QuackBehavior quackBehavior;
	
	public Duck() {
	}
	
	public abstract void display();
	
	public void performFly() {
		flyBehavior.fly();
	}
	public void performQuack() {
		quackBehavior.quack();
		
	}
	
	public void swim() {
		System.out.println("모든 오리는 물에 떠야 한다.");
	}
}

각 하위클래스에서 변화하는 부분인 fly()와 quack()을 각각 FlyBehavior와 QuackBehavior라는 인터페이스로 보내 버렸다.

그리고 또한 클래스에 기본적으로 이 인터페이스의 객체 변수를 가지게 하였고, performFly()와 performQuack()에서 구현하게 하였다.

자 이제 그러면 FlyBehavior와 QuackBehavior 인터페이스를 확인해 보자.

package Chapter1;

public interface FlyBehavior {
	public void fly();
}
package Chapter1;

public interface QuackBehavior {
	public void quack();
}

인터페이스는 추상 메소드와 상수만으로 구성된다.

따라서 이 인터페이스에 있는 fly()와 quack() 메소드를 사용하기 위해서는 다른 클래스에서 구현 해줘야한다.

각각의 상황에 맞추워 여러 클래스에서 구현을 시켜 주었다.

package Chapter1;

public class FlyNoWay implements FlyBehavior {

	@Override
	public void fly() {
		// TODO Auto-generated method stub
		System.out.println("저는 날 수 없어요");
	}

}
package Chapter1;

public class FlyRocketPowered implements FlyBehavior{

	@Override
	public void fly() {
		// TODO Auto-generated method stub
		System.out.println("로켓 추진으로 날아감.");
	}

}
package Chapter1;

public class FlyWings implements FlyBehavior {

	@Override
	public void fly() {
		System.out.println("날개를 써서 날고 있다~~");
	}

}

FlyBehavior의 구현에 이어서 QuackBehavior를 구현해야 보자.

package Chapter1;

public class Quack implements QuackBehavior {

	@Override
	public void quack() {
		// TODO Auto-generated method stub
		System.out.println("꽥");
	}

}
package Chapter1;

public class Squeack implements QuackBehavior {

	@Override
	public void quack() {
		// TODO Auto-generated method stub
		System.out.println("삑");
	}

}
package Chapter1;

public class MuteQuack implements QuackBehavior {

	@Override
	public void quack() {
		// TODO Auto-generated method stub
		System.out.println("<< 조용~ >>");
	}

}

자 이제 그러면 Duck의 하위 클래스를 만들어 보자.

package Chapter1;

public class MallardDuck extends Duck {
	public MallardDuck() {
		quackBehavior = new Quack();
		flyBehavior = new FlyWings();
	}
	@Override
	public void display() {
		// TODO Auto-generated method stub
		System.out.println("저는 물오리입니다.");
	}

}

Duck을 상위 클래스로 하여 상속 받고 있다.

Duck에 있는 QuackBehavior 인터페이스의 객체 변수인 quackBehavior에 QuickBehavior의 quack()을 구현한 Quack()이라는 객체를 생성하여 대입했다.

또한 FlyBehavior 인터페이스의 객체 변수인 flyBehavior에 FlyBehavior의 fly()를 구현한 FlyWings()라는 객체를 생성하여 대입했다.

자 그럼 이제 이 클래스를 이용하여 실질적으로 클라이언트의 입장이 되어 돌려보자.

package Chapter1;

public class MiniDuckSimulator {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Duck mallard = new MallardDuck();
		mallard.performQuack();
		mallard.performFly();
		
	}

}

상위 클래스는 상위클래스가 가지고 있는 메소드나 변수 범위 안에서 하위 클래스의 객체를 생성할 수 있다.

따라서 Duck 형태로 MallarDuck() 객체를 생성하고,

이 객체에 맞게 performQuack()와 performFly()를 실행하게 되면 위에서 처럼 flyBehavior 에는 FlyWings() 객체가,

quackBehavior에는 Quack() 객체가 들어가게 되므로

performQuack()을 실행하게되면 Quack() 객체에 있는 quack()메소드가 실행되고,

performFly()를 실행하게 되면 FlyWings() 객체에 있는 fly() 메소드가 실행된다.

자 이제 그러면 이 상황에서 동적으로 행동을 지정할려면 어떻게 해야 할까?

정답은 간단하다. Super Class인 Duck()에 flyBehavior 변수와 quackBehavior 변수를 받아오는 setter method를 추가하면 된다.

package Chapter1;

public abstract class Duck {
	FlyBehavior flyBehavior;
	QuackBehavior quackBehavior;
	
	public Duck() {
	}
	
	public abstract void display();
	
	public void setFlyBehavior(FlyBehavior fb) {
		flyBehavior = fb;
	}
	
	public void setQuackBehaviod(QuackBehavior qb) {
		quackBehavior = qb;
	}
	
	public void performFly() {
		flyBehavior.fly();
	}
	public void performQuack() {
		quackBehavior.quack();
		
	}
	
	public void swim() {
		System.out.println("모든 오리는 물에 떠야 한다.");
	}
}

자 이제 그러면 오리가 로켓을 이용해서 날아가는 행동을 구현하기 위해 FlyBehaviors를 구현하는 FlyRockerPowered Class를 만들고,

동적으로 행동을 지정해서 실행해 보도록 하자.

package Chapter1;

public class FlyRocketPowered implements FlyBehavior{

	@Override
	public void fly() {
		// TODO Auto-generated method stub
		System.out.println("로켓 추진으로 날아감.");
	}

}
package Chapter1;

public class MiniDuckSimulator {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Duck mallard = new MallardDuck();
		mallard.performQuack();
		mallard.performFly();
		
		Duck model = new ModelDuck();
		model.performFly();
		model.setFlyBehavior(new FlyRocketPowered());
		model.performFly();
	}

}

이 프로그램을 실행 시켜보면,

프로그램이 실행하는 중간에 fly()의 행동이 로켓을 이용해 날아가는것으로 바뀌었음을 알 수 있다.

지금은 간단하게 오리를 시뮬레이션 하는 프로그램이라 "행동"이라고 표현했지만,

실제로 여러가지 프로젝트 안에 이런 디자인 패턴을 적용할때는 이러한 각각의 행동들을 각각의 알고리즘이라고 생각하고 접근하면 편할 것이다.