일반적으로 객체들이 동일한 동작(method)을 할 경우 상위 클래스를 생성하여 상속한다.


Ex. 무기(Weapon)는 공격한다는 동작이 있다고 가정하자.

Weapon 클래스의 attack() 메소드Sword 클래스와 Bow 클래스 등의 하위 클래스가 공통으로 가지는 동작일 것이다.

 


그러나, 객체들마다 동일한 동작일지라도 다른 방식을 가질 수 있다.


Ex. Sword 클래스에서는 찌르기공격이 있고, Bow 클래스에서는 화살 쏘기공격이 있다고 하자.

그리고 장난감 총이 신규 무기로 추가되었다. (ToyGun 클래스가 Weapon 클래스의 하위 클래스가 되었다!!)

그런데 이 장난감 총은 실제 총알이 없어서 어떤 공격도 할 수 없다


그렇다면 ToyGun 클래스는 attack() 메소드를 구현할 필요가 있을까


또는 활에 화살이 없는 경우가 있다면, Bow 클래스의 attack() 메소드는 공격하지 않는다.”고 동작해야 할 것이다.



상황에 맞게 동작을 정의해야 한다면, 상속은 그다지 유연하지 않다.

인터페이스(Interface)는 어떨까?


Ex. Sword 클래스, Bow 클래스, ToyGun 클래스의 공격 동작을 인터페이스를 구현한 객체(행동 객체)에게 위임한다면, 각 클래스의 입장에서 더 이상 공격 동작에 대해 걱정할 필요가 없다.



1. AttackBehavior 인터페이스를 생성하자.


1
2
3
4
5
public interface AttackBehavior {

    public void attack();

}
cs



2. 인터페이스를 구현할 행동 클래스를 생성하자.


1
2
3
4
5
6
public class StabAttack implements AttackBehavior {
    @Override
    public void attack() { 
        // 찌르기
    }
}
cs



1
2
3
4
5
6
public class ShootArrowAttack interface AttackBehavior {
    @Override
    public void attack() { 
        // 화살 쏘기 
    }
}
cs



1
2
3
4
5
6
public class NoAttack interface AttackBehavior {
    @Override
    public void attack() { 
        // 공격하지 않기
    }
}
cs

 


3. 행동 객체에게 위임해보자.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public abstract class Weapon {

    AttackBehavior attackBehavior;

    Weapon(AttackBehavior attackBehavior) {

        this.AttackBehavior = attackBehavior; // 어떤 공격이든지 동작할 수 있다.
 
    }
 
    public void attackAction() {
 
        attackBehavior.attack(); // 행동 객체에게 위임하기
 
    }

}
cs

 


4. 상황에 맞게 동작을 바꾸기 위해서는 행동 객체를 동적으로 할당할 필요가 있다.

필요할 때마다 각각의 다른 행동 객체를 사용한다면, 유연성이 크게 향상된다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public abstract class Weapon {
 
    AttackBehavior attackBehavior;
 
    Weapon() { }
 
    public abstract void display(); // 형태는 기본 동작이므로 추가하자!
 
    public void attackAction() {
 
        attackBehavior.attack(); // 행동 객체에게 위임하기
 
    }
 
    public setAttackBehavior(AttackBehavior attackBehavior) { 
        // 행동 객체를 동적으로 할당하기
        this.AttackBehavior = attackBehavior;
 
    }
 
}
cs

 


핵심은 인터페이스를 통해 각 동작이 캡슐화된다는 것이다

이러한 인터페이스 기반의 프로그래밍은 상속보다는 구성을 활용한다고 표현할 수 있다.

이제 어떤 종류의 무기든지 인터페이스를 구현한 행동 객체를 가짐으로써 동작(=행동)할 수 있게 된다.



5. 하위 클래스를 생성해보자.


1
2
3
4
5
6
public class Bow extends Weapon {
    @Override
    public void display() { 
        // 미친듯이 아름다운 활이다. 
    }
}
cs



Ex. 활에 화살이 부족하다면, 활로 찌르는 공격도 가능할 것이다.


1
2
3
4
5
6
7
8
9
10
11
12
public class MainClass {
    public static void main(String[] argc) {
        Weapon bow = new Bow();
 
        bow.setAttackBehavior(new ShootArrowAttack());
        bow.attackAction(); // 화살 슝슝 
 
        // 갑자기 화살이 부족해 !!
        bow.setAttackBehavior(new StabAttack());
        bow.attackAction(); // 찔러 찔러
    }
}
cs



Ex. 장난감 총은 "공격하지 않는다." 동작을 하거나, 아예 동작이 없을 수도 있다.


1
2
3
4
5
6
public class ToyGun extends Weapon {
    @Override
    public void display() {
        // 평범한 장난감 총이다.
    }
}
cs



1
2
3
4
5
6
7
8
9
public class MainClass {
    public static void main(String[] argc) {
        Weapon toygun = new ToyGun();
        toygun.setAttackBehavior(new NoAttack());
        toygun.attackAction();
 
        toygun.setAttackBehavior(null);
    }
}
cs




6. 인터페이스의 장점인 유연성을 활용하자.


무기가 아닌 것도 공격한다동작이 필요하다면?


Ex. 사전은 흉기로 지정되지 않아 공격해도 죄를 묻지 않는다고 가정하자. (물론 가정일 뿐이다.) 

그래서 사전에 공격한다동작을 추가하고 싶다. 그런데 Weapon 클래스를 상속해야 할까? Never!


1
2
3
4
5
6
public class Dictionary implements AttackBehavior {
    @Override
    public void attack() {
        // 사전으로 내리찍기 !!
    }
}
cs



1
2
3
4
5
6
public class MainClass {
    public static void main(String[] argc) {
        Dictionary dictionary = new Dictionary();
        dictionary.attack();
    }
}
cs

 


끝으로,

 “공격한다외에도 방어한다처럼 다른 동작이 있을 수 있다. 이러한 동작들은 어떤 객체에서 알고리즘군(family of algorithms)이라고 표현된다. 이러한 알고리즘군을 정의하고 각 동작의 방식(=알고리즘)캡슐화하여 교환해서 사용(=행동 객체의 사용)할 수 있도록 하면, 특정 동작을 해야 하는 객체와는 독립적으로 동작의 방식(=알고리즘)을 변경할 수 있다.


 즉, 임의의 동작이 객체에 영향을 받지 않고, 다양한 방식으로 구현될 수 있다. 객체는 단지 행동 객체를 골라서 사용하는 입장일 뿐이다. 


이것을  스트래티지 패턴(Strategy Pattern)  이라고 한다.


Ex.공격한다동작에는 찌르기방식, “활쏘기방식, “공격하지 않는다.” 방식이 있고, 각 공격의 방식들은 공격한다라는 행동 객체에 위임(어떻게 동작할 것인가 - 알고리즘 - 에 대한 책임을 진다.) 되기 때문에 이러한 행동 객체들로 구성된 클라이언트 객체에서는 하나의 동작으로도 여러 방식을 구현할 수 있게 된다. (공격하지 않는다 방식은 예시일 뿐이며, 표현의 모순이 있더라도 그렇구나 합시다.)





+ Recent posts