지난 주에는 객체지향의 개념을 재검토 및 SOLID 개념을 한 번 맛보는 자리였었습니다. 또한 배운 주제를 통하여 과제를 진행했었고, 예상대로 멋진고 인텔리한 우리 스터디 멤버들은 문제를 잘 해결해주었습니다. 


지난 주차에 했던 내용에 대한 포스팅은 아래 링크에서 확인! 



해당 글에서는 과제에 대한 리뷰를 진행해 보고자 합니다.


1. 기존 구현된 Task 클래스에 goMBM() 이라는 메소드를 추가.


스터디에서 진행했던, Task 에 요구사항 추가가 있었습니다. 

우리 회사에 다니는 모든 직군은 MBM 이라는 행사에 참여할 가능성이 있지만, 보통은 마케팅 팀에서 대부분 진행을 하는 것으로 압니다. 

(맞나요? ㅡㅡ^)


무튼, 아래와 같이 코드를 작성하려 하였습니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
/**
 * 업무 처리
 *
 * Created by Doohyun on 2017. 4. 17..
 */
public abstract class Task {
 
    /**
     * 일을 한다는 약속
     *
     * <pre>
     *     이 메소드를 실행시키면 일을 하는 것.
     * </pre>
     */
    public abstract void runTask();
 
    /**
     * MBM 을 수행하는 메소드
     * 
     * <pre>
     *     구체적인 것이 추상적인 상태로 올라옴.
     * </pre>
     */
    public void goMBM() {
        if (this instanceof Marketing) {
            // 마켓팅 main.
        } else if (this instanceof Devlopment) {
            // 일단 여지는 있어보임.
        }
    }
 
    /**    
     * 면접관을 담당하는 메소드
     * 
     * <pre>
     *     면접관을 수행하는 메소드도 추가되길 바람.
     *     물론 이 업무 역시, 특정 직군 (마켓팅,개발) 만 할 수도 있음.
     * </pre>
     * 
     */
    public void goInterview() {
    }
}
cs


구현된 goMBM 을 보니, 좋지 않은 냄새가 납니다. 


일단 추상적인 것이 구체적인 것에 의존하면 안된다는 DIP (의존성 역전 원칙) 을 지키지 않고 있으며, 만약 해당 소스를 사용하는 어플리케이션에서 Task 의 구현체를 변경한다면, Task 자체 소스도 변경해 줘야하기 때문에 OCP (개발-폐쇄 원칙)도 지키지 않는 것으로 보입니다.


[Marketing 이 삭제가 된다면 Task 내부 메소드를 수정해야 하며, 새로운 작업을 MBM 으로 추가하고 싶다면 또 다른 구현체에 의존해야 합니다.]


이 소스의 리팩토링 여지는 있으며, 어떻게 생각해보면 간단하게 해결될 수 도 있습니다. 위 소스의 가장 큰 문제는 추상적인 것이 구체적인 것에 의존한고 있다는 것이며, 그 고리만 끊어주면 파생되는 문제는 해결될 것으로 보입니다.


일단 아래와 같이 Task 클래스 내부의 goMBM() 은 아무 일도 하지 않게 하려 합니다. Task 라는 추상개념으로 goMBM() 을 실행하는 것이 목적이기 때문에 invoker 는 살려두려 합니다. 


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
/**
 * 업무 처리
 *
 * Created by Doohyun on 2017. 4. 17..
 */
public abstract class Task {
 
    /**
     * 일을 한다는 약속
     *
     * <pre>
     *     이 메소드를 실행시키면 일을 하는 것.
     * </pre>
     */
    public abstract void runTask();
 
    /**
     * MBM 을 수행하는 메소드
     * 
     * <pre>
     *     추상적인 곳에서 추상클래스가 아닌 아무 일도 하지 않는 메소드를 hooker 라고 합니다.
     * </pre>
     */
    public void goMBM() {
    }
 
    /**    
     * 면접관을 담당하는 메소드
     * 
     * <pre>
     *     추상적인 곳에서 추상클래스가 아닌 아무 일도 하지 않는 메소드를 hooker 라고 합니다.
     * </pre>
     * 
     */
    public void goInterview() {
    }
}
cs


필수적인 runTask() 만이 추상의 구현을 하위 개념에게 강제하였고, 나머지 메소드 (goMBM(), goInterview()) 는 hooker 로써, 구현의 여부를 선택하게 하였습니다. 


goMBM() 메소드는 현재 요구사항으로 보았을 때, 일단 Marketing 에 구현될 필요가 있어 보입니다. goMBM() 에서 Marketing 이 해야할 일을 구체개념으로 빼도록 하겠습니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
 * 마케팅 업무
 * Created by Doohyun on 2017. 4. 17..
 */
public class Marketing extends Task{
 
    @Override
    public void runTask() {
        System.out.println("마케팅 일을 하는 중...");
    }
 
    @Override
    public void goMBM() {
        System.out.println("마케팅은 MBM 을 떠난다.");
    }
}
cs


주목할 점은 goInterview() 를 오버라이딩 하지 않고 있으며, 즉 선택적으로 행위를 추가할 수 있음을 알 수 있습니다.



2. 가위바위보의 승패를 출력하는 메소드 제작


두 상태에 대한 승패여부를 if 나 자료구조 없, 나타내는 방법을 생각해보라는 주제였습니다. 아래 소스는 초기에 주어진 요구사항으로, 각 케이스에 대한 모든 분기가 되어있음을 알 수 있습니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
/**
  * 심플한 가위,바위,보 결과
  * 
  * @param me
  * @param enemy
  * @return
  */
public static String 가위바위보_결과 (final String me, final String enemy) {
    if (me.equals("가위")) {
        switch (enemy) {
        case "가위":
            return "무승부";
        case "바위":
            return "패";
        case "보":
            return "승";
        }
    } else if (me.equals("바위")) {
        switch (enemy) {
        case "가위":
             return "승";
        case "바위":
            return "무승부";
        case "보":
            return "패";
        }
    } else if (me.equals("보")) {
        switch (enemy) {
        case "가위":
            return "패";
        case "바위":
            return "승";
        case "보":
            return "무승부";
        }
    }
 
    throw new RuntimeException("입력 오류");
}
 
cs


참여자 전원 모두 과제를 잘 해왔으며, 제출자의 코드를 기반으로 포스팅을 하고자합니다.


리팩토링을 생각해볼 부분은 분기처리이며, 각 분기처리에 대해서 책임소지가 있는 클래스로 기능을 이관(SRP)하는 작업이 필요할 것 같습니다.


책임 분배를 위해 아래와 같은 구체화 클래스를 만들려고 합니다. 

서로의 매치를 if 없이 관계를 주기 위하여, 오버로딩 개념을 사용하였습니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
/**
 * 클래스 간의 서로의 사용개념을 처리하기 위한 오버로딩 인터페이스
 */
public interface StrategyVisitor {
    String visit(가위 가위);
    String visit(바위 가위);
    String visit(보 가위);
}
 
// 구체화된 가위 클래스
public class 가위 implements StrategyVisitor{
 
    @Override
    public String visit(가위 enemy) {
        return "무승부";
    }
 
    @Override
    public String visit(바위 enemy) {
        return "패";
    }
 
    @Override
    public String visit(보 enemy) {
        return "승";
    }
 
    @Override
    public String toString() {
        return "가위";
    }
}
 
// 구체화된 바위 클래스
public class 바위 implements StrategyVisitor {
 
    @Override
    public String visit(가위 enemy) {
        return "승";
    }
 
    @Override
    public String visit(바위 enemy) {
        return "무승부";
    }
 
    @Override
    public String visit(보 enemy) {
        return "패";
    }
 
    @Override
    public String toString() {
        return "바위";
    }
}
 
// 구체화된 보 클래스
public class 보 implements StrategyVisitor {
 
    @Override
    public String visit(가위 enemy) {
        return "패";
    }
 
    @Override
    public String visit(바위 enemy) {
        return "승";
    }
 
    @Override
    public String visit(보 enemy) {
        return "무승부";
    }
 
    @Override
    public String toString() {
        return "보";
    }
}
cs


클라이언트 코드에서는 아래와 같이 제출을 하였었습니다. 자료구조에 데이터를 넣고, 모든 상황에 대한 시뮬레이션을 수행하는 코드입니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
List<StrategyVisitor> me = Arrays.asList(new 가위(), new 바위(), new 보());
 
// 가위에 대한 응용처리
{
    가위 a = new 가위();
 
    System.out.println("============================");
    System.out.println("게입 시작합니다! \n");
    for (StrategyVisitor game1 : me) {
        System.out.println("me:" + game1.getClass().getSimpleName() + "  enemy:" + a.getClass().getSimpleName());
        System.out.println("result:" + game1.visit(a) + "\n");
    }
}
 
// 바위에 대한 응용처리
{
    바위 b = new 바위();
    System.out.println("============================");
    System.out.println("게입 시작합니다! \n");
    for (StrategyVisitor game1 : me) {
        System.out.println("me:" + game1.getClass().getSimpleName() + "  enemy:" + b.getClass().getSimpleName());
        System.out.println("result:" + game1.visit(b) + "\n");
    }
}
 
// 보에 대한 응용처리
{
    보 c = new 보();
            
    System.out.println("============================");
    System.out.println("게입 시작합니다! \n");
    for (StrategyVisitor game1 : me) {
        System.out.println("me:" + game1.getClass().getSimpleName() + "  enemy:" + c.getClass().getSimpleName());
        System.out.println("result:" + game1.visit(c) + "\n");
    }
}
cs


요구사항처럼 if 없이 관계는 처리가 되었습니다. 

하지만 제출자의 입장에서는 위와 같이 세 가지 케이스에 대하여 모든 경우를 비지니스 로직으로 만들고 싶지는 않았고, 자료구조를 두 개 선언하여 2중 for 문으로 해결하고 싶었다고 합니다.


하지만 아래와 같은 사유로 실패했습니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
List<StrategyVisitor> me = Arrays.asList(new 가위(), new 바위(), new 보());
List<StrategyVisitor> enemy = Arrays.asList(new 가위(), new 바위(), new 보());
        
for (StrategyVisitor a : me) {
    System.out.println("============================");
    System.out.println("게입 시작합니다! \n");
    
    for (StrategyVisitor b : enemy) {
        System.out.println("me:" + a.getClass().getSimpleName() + "  enemy:" + b.getClass().getSimpleName());
        
        // 오버로딩 전략으로 가려고 했지만, enemy 리스는 추상적인 StrategyVisitor!!!!
        System.out.println("result:" + a.visit(b) + "\n");
    }
}
cs


두 자료구조의 for-loop 을 방문하면서, 로직을 처리하기 위하여 고전적인 방문자 패턴을 응용해보려 합니다.


3. 방문자패턴을 이용한 3차 리팩토링


오버로드를 수행하는 StrategyVisitor 를 일종의 방문자라 생각한다면, 방문할 Element 만 구현해주면 그만입니다.


방문할 Element 객체를 아래와 같이 정의합니다.


1
2
3
4
5
6
7
8
/**
 * 방문 대상 Element
 *
 * Created by Doohyun on 2017. 4. 27..
 */
public interface Strategy {
    String accept(StrategyVisitor strategyVisitor);
}
cs


방문대상인 Strategy 는 기존 StrategyVisitor 를 매개변수로 받는 메소드를 정의했습니다. 즉 방문자가 해당 Element 를 방문했을 때, 해야할 일을 구현체에 작성해주면 됩니다. 


이를위해 저는 기존 만들어진 가위,바위,보를 재활용하고자 합니다. 아래와 같이 말이죠.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
/**
 * Element 와 Visitor 를 한 곳에 구현.
 * 구체화된 가위 클래스
 */
public class 가위 implements StrategyVisitor, Strategy{
 
    @Override
    public String visit(가위 enemy) {
        return "무승부";
    }
 
    @Override
    public String visit(바위 enemy) {
        return "패";
    }
 
    @Override
    public String visit(보 enemy) {
        return "승";
    }
 
    // 방문자가 본인을 다녀갔을 때의 처리를 수행합니다.
    @Override
    public String accept(StrategyVisitor strategyVisitor) {
       return strategyVisitor.visit(this);
    }
 
    @Override
    public String toString() {
        return "가위";
    }
}
 
// 구체화된 바위 클래스
public class 바위 implements StrategyVisitor, Strategy {
 
    @Override
    public String visit(가위 enemy) {
        return "승";
    }
 
    @Override
    public String visit(바위 enemy) {
        return "무승부";
    }
 
    @Override
    public String visit(보 enemy) {
        return "패";
    }
 
    @Override
    public String accept(StrategyVisitor strategyVisitor) {
        return strategyVisitor.visit(this);
    }
 
    @Override
    public String toString() {
        return "바위";
    }
}
 
// 구체화된 보 클래스
public class 보 implements StrategyVisitor, Strategy {
 
    @Override
    public String visit(가위 enemy) {
        return "패";
    }
 
    @Override
    public String visit(바위 enemy) {
        return "승";
    }
 
    @Override
    public String visit(보 enemy) {
        return "무승부";
    }
 
    @Override
    public String accept(StrategyVisitor strategyVisitor) {
        return strategyVisitor.visit(this);
    }
 
    @Override
    public String toString() {
        return "보";
   }
}
cs


accept 메소드를 모두 구현했으니 아래와 같이 클라이언트 코드를 작성해볼 수 있을 것 같습니다. 앞써, 제작된 코드와는 달리 다형화의 선택조건을 구현체 내부에서 구현하고 있기 때문에 아래와 같은 구현이 가능합니다.


1
2
3
4
5
6
7
8
9
10
11
12
List<StrategyVisitor> me = Arrays.asList(new 가위(), new 바위(), new 보());
List<Strategy> enemy = Arrays.asList(new 가위(), new 바위(), new 보());
 
for (StrategyVisitor visitor : me) {
    System.out.println("============================");
    System.out.println("게입 시작합니다! \n");
 
    for(Strategy strategy : enemy){
        System.out.println(String.format("me : %s,  enemy: %s", visitor, strategy));
        System.out.println(String.format("result : %s\n", strategy.accept(visitor)));
    }
}
cs


나름 신경을 써 볼 만한 문제 였던 것 같습니다. 다들 한 번이라도 신경을 써서 문제를 해결해줘서 고맙고, 다시 이 글을 봐주셔서 감사합니다. [꾸벅]


다음 주, Study 도 파이팅!!! 

반응형
Posted by N'