성욱박 2019. 4. 15. 16:11

Head Fist Design Pattern 책에 있는 내용들을 정리해서 올립니다.


1. Strategy Pattern (전략 패턴)


1.1 Design Pattern 이란?

우리는 개발을 하며 어떠한 문제에 부딪힌 적이 있을것입니다.

예를 들어 내가 만들어 놓은 객체를 가져다 사용하는 클래스를 설계했습니다. 그런데 그 객체에 수정사항이 생기는 경우는 정말 많죠.

"뭐 수정사항이 생겼으면 수정하면 되지 그게 뭐?" 라고 생각하실 수도 있습니다. 하지만 객체를 수정하면 해당 객체를 가져다 사용하는 클래스 또한 수정해야 하는 문제가 발생했을 것입니다. 이러한 문제는 두 클래스 사이의 결합도 때문에 발생하는 문제죠. 하나의 클래스를 고쳤다고 다른 모든 클래스들을 고쳐야 한다면 정말 암담하겠죠? 이렇듯 설계는 개발하는데 있어 상당히 중요한 요소입니다.

우리가 겪었고 또 앞으로도 겪게될 이러한 클래스의 설계적인 문제를 해결해 놓은 방법을 디자인 패턴이라고 합니다.


1.1.2 Strategy Pattern이란?

1)알로리즘군을 정의하고 각각을 캡슐화하여 교환해서 사용할 수 있도록 만든 패턴

- 기능의 클래스 집합을 만들어 사용

2)클라이언트와 알고리즘을 구성을 이용해서 합치기 때문에 두 클래스는 독립적이다. 따라서 클라이언트와는 독립적이고 안정적으로 알고리즘의 수정이나 확장에 어려움이 없다. 실행시에 setter method를 이용해 연결할 알고리즘 구성을 변경할 수 있다.

- 기능을 사용할 클래스와 기능역할을 하는 클래스는를 구성을 이용해 결합시킨다.


1.2 오리게임 만들기

오리 게임을 만들려고 합니다. 오리의 종류와 기능을 설계해 보겠습니다.


1.2.1 보통 초보 개발자들의 설계

오리의 종류

- MallardDuck (청둥 오리)

- RedHeadDuck (붉은 머리 오리)


오리가 할 수 있는 행동

- 날 수 있다. (공통)

- 헤엄 칠 수 있다. (공통)

- 오리들의 모습은 다르다 (공통 x)


개발경험이 부족하신 분들은 디자인 패턴이란 말을 처음 들어본 사람도 있을것입니다. 일단 만드는게 우선이고 동작하기만 하면 되니까요!

그런분들은 위 요구사항들을 보고 이렇게 생각할 겁니다.


- 어? 오리 종류가 두 마리니까 오리 클래스를 만들고 상속받아서 하면 되겠네.

- 모습은 다르니까 추상 메소드로해서 재정의해야지.

 

1.2.1.1 Duck (Super class)

- 모든 오리들이 공통적으로 가진 기능을 상속으로 물려주기 위한 Super class 입니다.

1
2
3
4
5
6
7
8
9
10
abstract class Duck {
    void  quack() {
        System.out.println("꽥꽥");
    }
    void swim() {
        System.out.println("첨벙 첨벙");
    }
    abstract void display();
}
 
cs


1.2.1.2 MallardDuck (Sub class)

- Duck super class를 상속받아 공통된 부분을 상속받아 코드의 중복을 줄였습니다. (코드의 재사용)

1
2
3
4
5
6
class MallardDuck extends Duck {
    @Override
    void display() {
        System.out.println("MallardDuck은 청둥 오리.");
    }    
}
cs



1.2.1.2 RedHeadDuck (Sub class)

- Duck super class를 상속받아 공통된 부분을 상속받아 코드의 중복을 줄였습니다. (코드의 재사용)

1
2
3
4
5
6
class RedHeadDuck extends Duck {
    @Override
    void display() {
        System.out.println("RedHeadDuck은 붉은 머리 오리. ");
    }
}
cs

1.2.2 초보 개발자의 오리 게임 확장 
- 오리는 이 세상에 2종류가 아니라 더많은 종류가 있습니다. 하나의 오리를 더 추가해 보겠습니다. (새로운 클래스 추가)
- 생각해 보니 오리는 날 수도 있습니다. (새로운 기능의 추가)

1.2.2.1 Super class에 새로운 기능을 추가
fly() 메소드 추가
1
2
3
4
5
6
7
8
9
10
11
12
abstract class Duck {
    void  quack() {
        System.out.println("꽥꽥");
    }
    void swim() {
        System.out.println("첨벙 첨벙");
    }
    void fly() {
        System.out.println("훨훨");
    }
    abstract void display();
}
cs

1.2.2.2 새로운 오리 클래스 작성
RubberDuck class 추가
- 고무오리는 보통오리처럼 울지않고 바람빠지는 소리를 낸다.
- 고무오리는 날 수 없다.
1
2
3
4
5
6
class RubberDuck extends Duck {
    @Override
    void display() {
        System.out.println("RubberDuck은 고무오리.");
    }    
}
cs


1.2.2.3 초보 개발자의 오리 게임 확장의 문제점
- 고무오리는 날 수 없는데 날수 있게 되었습니다.
- 고무오리는 보통오리와 달리 바람빠지는 소리를 내야합니다.

1.2.2.4 초보 개발자의 오리 게임 확장의 문제 해결방안
- 고무오리는 날 수 없기 때문에 fly() 메소드를 깡통으로 오버라이딩 합니다.
- 고무오리는 바람 빠지는 소리를 내도록 quack() 메소드를 오버라이딩 합니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class RubberDuck extends Duck {
 
    @Override
    void display() {
        System.out.println("RubberDuck은 고무오리.");
    }
 
    @Override
    void quack() {
        System.out.println("삑삑");
    }
    
    @Override
    void fly() {}
    
}
cs

1.2.2.5 초보 개발자의 오리 게임 확장의 문제 해결방안의 문제점
1)어플리케이션의 업데이트(기능 추가, 제품 추가)가 지속적으로 이루어진다면 새로운 기능과 추가된 제품에 매번 같은 작업을 반복해야한다.
2)기존 규격을 변경해야 하는경우 (ex.오리마다 우는 소리를 다르게 변경)
- 모든 sub class의 quack()메소드를  새롭게 오버라이딩 해줘야한다.
- 주기적으로 새로운 제품이 추가되어 sub class의 개수가 50개가 된다면 50개의 클래스의 메소드를 오버라이딩 해야한다.

1.2.2.6 point 
1)상속을 이용하면 super 클래스를 상속받은 모든 구현 클래스에 영향을 미친다.
  예외적으로 영향을 미치면 안되는 구현 클래스에게 까지 영향을 미치게 된다.
2)예외없이 모든 서브 클래스가 공통된 기능을 갖는 것이 아니라면 상속을 이용하는 방법은 좋지 않다.
3)지속적인 변경이 있는 application을 개발할 경우 상속을 이용하는 방법은 좋지않다.

1.2.3 중급 개발자의 오리게임
초보 개발자들이 어떻게 개발을 하고 그럴때 어떤 문제가 생기는지 알아보았습니다.
중급 개발자는 좀 더 생각을 할 것 같지 않나요? 뭔가 인터페이스를 사용할 수 있다면 초보자중 중급이라고 할수 있을 것 같네요.
아마 중급 개발자는 이렇게 생각할 것 같습니다.
- 결국 비슷하긴 하지만 완전히 공통되는게 아닌걸 상속받아서 문제인거잖아?
- 그럼 정말 어떤 오리든 공통적으로 갖는갖는 것만 상속받으면 되겠네.
- 모든 sub class가 갖는것은 아니거나 기능을 가지긴 하되 약간씩의 차이가 있는 경우는 interface로 만들자.

1.2.3.1 공통 기능만을 상속받는다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
abstract class Duck {
    
    // 모든 sub class의 공통 기능
    void swim() {
        System.out.println("첨벙 첨벙");
    }
    
    /* 모든 sub class가 가지는 공통된 기능 X
    void  quack() {
        System.out.println("꽥꽥");
    }
    void fly() {
        System.out.println("훨훨");
    }
    */
 
    abstract void display();
}
cs

1.2.3.2 모든 sub class가 갖는것은 아니거나 기능을 가지긴 하되 약간씩의 차이가 있는 경우는 interface로 만든다.

모든 sub class가 기능을 가지긴 하되 약간씩의 차이가 있는 경우

1
2
3
4
interface Quackable {
    void quack();
}
 
cs

모든 sub class가 갖는것은 아닌경우

1
2
3
interface Flyable {
    void fly();
}
cs


1.2.3.3 기능이 필요한 경우 interface를 구현한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class MallardDuck extends Duck implements Quackable,Flyable {
 
    @Override
    void display() {
        System.out.println("MallardDuck은 청둥 오리.");
    }
 
    @Override
    public void fly() {
        System.out.println("훨훨");
    }
 
    @Override
    public void quack() {
        System.out.println("꽥꽥");
    }    
 
    
}
cs


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class RedHeadDuck extends Duck implements Quackable,Flyable {
 
    @Override
    void display() {
        System.out.println("RedHeadDuck은 붉은 머리 오리. ");
    }
 
    @Override
    public void fly() {
        System.out.println("훨훨");
    }
 
    @Override
    public void quack() {
        System.out.println("꽥꽥");
    }    
 
}
 
cs


1
2
3
4
5
6
7
8
9
10
11
12
13
14
 
class RubberDuck extends Duck implements Quackable {
 
    @Override
    void display() {
        System.out.println("RubberDuck은 고무오리.");
    }
    
    @Override
    public void quack() {
        System.out.println("삑삑 (바람 빠지는 소리)");
    }    
    
}
cs

1.2.3.4 중급 개발자 오리게임의 문제점
상속으로 인한 예외적인 sub class에게까지 기능이 추가되는 문제점은 해결되었지만, 코드 재사용의 장점이 사라졌다.
기능의 수정이 있을 때  해당 기능을 implements한 모든 class를 하나 하나 오버라이딩 해야한다.

1.2.4 Strategy pattern을 적용한 오리게임

fly()와 quack()은 Duck super class에서 sub class마다 달리지는 부분이다.

Duck super class로 부터 분리해서 각 기능을 나타낼 클래스 집합을 만든다.



1.2.4.1 울음소리의 클래스 집합

울음소리 기능 클래스들을 묶을 상위 인터페이스

1
2
3
4
5
package QuackBehavior;
 
public interface QuackBehavior {
    void quack();
}
cs

울음소리 기능1
1
2
3
4
5
6
7
8
package QuackBehavior;
 
public class OriginQuack implements QuackBehavior {
    @Override
    public void quack() {
        System.out.println("꽥꽥");
    }
}
cs


울음소리 기능2

1
2
3
4
5
6
7
8
package QuackBehavior;
 
public class Squeak implements QuackBehavior {
    @Override
    public void quack() {
        System.out.println("삑삑");
    }
}
cs


울음소리 기능3

1
2
3
4
5
6
7
8
package QuackBehavior;
 
public class MuteQuack implements QuackBehavior {
    @Override
    public void quack() {
        // 소리 안냄.
    }
}
cs


1.2.4.2 날기 기능의 클래스 집합

날기 기능 클래스들을 묶을 상위 인터페이스

1
2
3
4
5
package FlyBehavior;
 
public interface FlyBehavior {
    void fly();
}
cs

날기기능1
1
2
3
4
5
6
7
8
package FlyBehavior;
 
public class FlyWithWings implements FlyBehavior {
    @Override
    public void fly() {
        System.out.println("훨훨");
    }
}
cs


날기기능2

1
2
3
4
5
6
7
8
package FlyBehavior;
 
public class FlyNoWay implements FlyBehavior {
    @Override
    public void fly() {
        // 날지 못함.
    }
}
cs


1.2.4.3 기능 클래스들을 사용할 Client 클래스들

Duck Super class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import FlyBehavior.FlyBehavior;
import QuackBehavior.QuackBehavior;
 
abstract class Duck {
    // field
    QuackBehavior quackBehavior; 
    FlyBehavior flyBehavior;
    
    // method
    void swim() {
        System.out.println("첨벙 첨벙");
    }
    void performQuack() {
        quackBehavior.quack();
    }
    void performFly() {
        flyBehavior.fly();
    }
    abstract void display();
}
cs


MallardDuck class

1
2
3
4
5
6
7
8
9
10
11
12
13
import FlyBehavior.FlyWithWings;
import QuackBehavior.OriginQuack;
 
class MallardDuck extends Duck {
    MallardDuck(){
        quackBehavior = new OriginQuack();
        flyBehavior = new FlyWithWings();
    }
    @Override
    void display() {
        System.out.println("MallardDuck은 청둥 오리.");
    }    
}
cs


RubberDuck class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import FlyBehavior.FlyNoWay;
import QuackBehavior.MuteQuack;
 
class RubberDuck extends Duck {
    public RubberDuck() {
        quackBehavior = new MuteQuack();
        flyBehavior = new FlyNoWay();
    }
    @Override
    void display() {
        System.out.println("RubberDuck은 고무오리.");
    }
}
 
cs


1.2.4.4 main

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class DuckSimulator {
    public static void main(String args[]) {
        Duck mallard = new MallardDuck();
        mallard.performQuack();
        mallard.performFly();    
        
        Duck RedHeadDuck = new RedHeadDuck();
        RedHeadDuck.performQuack();
        RedHeadDuck.performFly();
        
        Duck RubberDuck = new RubberDuck();
        RubberDuck.performQuack();
        RubberDuck.performFly();
    }
}
cs


1.3 디자인 원칙1

Application에서 달라지는 부분을 찾아서 달라지지 않는 부분으로부터 분리 시킨다.

코드에 업데이트 마다 변경될 수 있는 부분을 따로 뽑아내 캡슐화 시킨다.

  이렇게 하면 나중에 바뀌지 않는 부분에는 영향을 주지않고 고치거나 확장할 수 있다.


1.3.1 디자인 원칙1을 적용시킨 Strategy Pattern

변경되는 기능의 클래스 집합을 만든다.

각 클래스 집합에는 각각의 기능을 구현한 것을 전부 집어 넣는다.


1.4 디자인 원칙 2

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


1.4.1 디자인 원칙2을 적용시킨 Strategy Pattern

각 기능을 interface로 설정하고 그 interface를 구현하여 class 집합을 만든다.

이러한 방법은 기능을 super class에서 구현하거나 sub class에서 구현하는 방식과는 상반된 방법이다.(구현 위주)

sub class에서는 interface로 표현되는 기능을 사용하게 된다.


여기서 interface란 상위 형식을 말하는 것이기 때문에 abstract super class를 사용해도 무관하다.    * 

엄연히 말하면 Point는 상위형식에 맞춰서 프로그래밍 해야 한다는 것이다.                          *** 

변수 선언시 상위 형식으로 선언하면 상위 형식을 구현한 어떠한 객체라도 집어 넣을 수 있기 때문이다. (다형성)

이렇게 하면 변수를 선언하는 클래스에서는 실제 변수에 할당되는 객체의 형식을 몰라도 된다.

Ex)

------------------------------------------------------------------------

구현 클래스 형식으로 선언하면

Dog d = new Dog();

d.bark(); <- 어떤 구체적인 구현에 맞춰서 코딩해야 한다.

------------------------------------------------------------------------

상위 형식(interface or abstract super class)에 맞춰서 프로그래밍 한다면

Animal animal = getAnimal();

animal.makeSound();

Animal의 하위 형식중 어떤 형식인지 알 필요없이 코딩할 수 있다.

------------------------------------------------------------------------


1.5 디자인 원칙3

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

(is a) (has a)