Hoon222y

[1227-3] 디자인 패턴 / 리팩토링 본문

코딩/교육

[1227-3] 디자인 패턴 / 리팩토링

hoon222y 2018. 12. 27. 21:14

디자인 패턴 : 반복적으로 발생하는 문제들을 설명하고, 이에 대한 해결방안을 설명하는 학문

리팩토링 : 프로그램이 동작을 변경하지 않고, 내부 구조를 변경하는 유지보수의 개념

 -> 디자인 패턴은 궁극적인 목표이고, 리팩토링은 이에 대한 방법론이라고 볼 수 있다.


예제를 통해서 진행하자

<서버예시>

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
package refactoring;
 
public class test1 {
    public static void main(String[] args) {
        new Server().run();
    }
}
 
// 어떤 기능을 제공하는 서버가 있다고 합니다.
class Server{
    // 서버를 구동하려면 아래의 메서드를 호출하면 됩니다.
    public void run() {
        // 일반적으로 서버는 종료되지 않고 계쏙 실행되면서 요청을 처리해야 하므로 아래와 값이
        // 무한 루프를 수행한다.
        while(true) {
            try{Thread.sleep(1000);} catch(InterruptedException e) {}
            System.out.println(".");
 
            //에러가 발생한 경우
            System.err.println("에러 1이 발생하였습니다. ");
            try{Thread.sleep(1000);} catch(InterruptedException e) {}
 
            //에러가 발생한 경우
            System.err.println("에러 2이 발생하였습니다. ");
        }
    }
}
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
37
38
39
40
41
package refactoring;
 
public class test1 {
    public static void main(String[] args) {
        new Server().run();
    }
}
 
// 발생된 에러에 대하여 정보를 모니터가 아닌 다른 곳을 출력하기 위해
// 로그를 기록하는 클래스를 도입한다.
// 아래의 클래스는 로그를 파일에 기록한다고 가정합니다.
class Logger{
    // ..
    public void errer(String msg) {System.err.println("[ERR] "+ msg);}
    public void warn(String msg) {System.err.println("[WARN] "+ msg);}
    public void info(String msg) {System.err.println("[INFO] "+ msg);}
 
 
}
 
// 이제 서버는 로그를 출력하기 위해 로그 객체를 포함하도록 합니다.
class Server{
    private Logger logger = null;
 
    // 로그 설정 정책을 사용자가 결정할 수 있도록 아래와 같이 생성자를 2개 추가합니다.
    public Server(Logger l) {this.logger = l;}
    public Server() {}
 
    public void run() {
        while(true) {
            try{Thread.sleep(1000);}catch(InterruptedException e) {}
            System.out.println(".");
 
            if(logger != null)
                logger.errer("에러 1이 발생하였습니다. ");
            try{Thread.sleep(1000);}catch(InterruptedException e) {}
            if(logger != null)
                logger.errer("에러 2이 발생하였습니다. ");
        }
    }
}
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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
 
// 이전 코드는 로그를 출력할 때 마다 로그 객체에 대하여 널 체크를 수행하고 있다.
// 이는 코드의 복잡도를 높이고 널 첨참조 인한 예외가 발생할 수 있기 떄문에 좋은 방법이 아니다.
// 이를 해결하기 위해 아무것도 하지 않는 객체인 널 객체를 도입한다.
// refactoring : introduce Null object
package refactoring;
 
public class test1 {
    public static void main(String[] args) {
        new Server().run();
    }
}
 
// 널 객체와 로그 객체를 하나의 타입으로 처리하고 로그 인터페이스를
// 자식이 구현하도록 인터페이스를 도입하도록 합니다.
 
interface Logger{
    public void errer(String msg);
    public void warn(String msg);
    public void info(String msg);
}
 
//이제 로그 객체를 설계하는 사람은 Logger 객체를 구현하도록 한다.
class LoggerImpl implements Logger{
    // ..
    public void errer(String msg) {System.err.println("[ERR] "+ msg);}
    public void warn(String msg) {System.err.println("[WARN] "+ msg);}
    public void info(String msg) {System.err.println("[INFO] "+ msg);}
}
 
// 아래와 같이 인터페이스만 있을 뿐 아무런 동작을 하지 않는 객체를 널 객체라고 합니다.
class NullLogger implements Logger{
    public void errer(String msg) {}
    public void warn(String msg) {}
    public void info(String msg) {}
}
 
class Server{
    private Logger logger;
    public Server(Logger l) {this.logger = l;}ㅋ
    //사용자가 로그 정책을 설정하지 않은경우, 널 객체를 사용하도록 합니다.
    public Server() {this.logger = new NullLogger();}
    
    public void run() {
        while(true) {
            try{Thread.sleep(1000);}catch(InterruptedException e) {}
            System.out.println(".");
            
            // if(logger != null)
            logger.errer("에러 1이 발생하였습니다. ");
            try{Thread.sleep(1000);}catch(InterruptedException e) {}
            
            // if(logger != null)
            logger.errer("에러 2이 발생하였습니다. ");
        }
    }
}
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
package refactoring;
 
import java.util.ArrayList;
import java.util.Scanner;
 
public class Test2 {
    public static void main(String[] args) {
        new PowerPoint().run();
    }
}
// 일단 프로그램 초기에는 사각형만 그릴 수 있다고 가정합니다.
// 이제 사각형 뿐만 아니라 원도 추가하고 출력할 수 있도록 코드를 변경해 보세요 :D
class Rect {
    // 모든 도형은 자신이 화면에 어떻게 그려질지에 대한 기능이 있습니다.
    public void draw() { System.out.println("draw rect"); }
}
// 도형을 생성 및 출력을 하는 파워포인트 클래스를 설계합니다.
class PowerPoint {
    private static Scanner keyboard = new Scanner(System.in);
    // 도형을 저장하는 컨네이터를 추가합니다.
    ArrayList<Rect> shapes = new ArrayList<>();
 
    public void run() {
        while (true) {
            String cmd = keyboard.nextLine();
            switch(cmd) {
            case "0":
                for(Rect e : shapes)
                    e.draw();
                break;
            case "1"// 사각형을 추가
                shapes.add(new Rect());
            }
        }
    }
}
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
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
package refactoring;
 
 
public class Test2 {
    public static void main(String[] args) {
        new PowerPoint().run();
    }
}
 
// 1. 이제 서로 다른 타입을 동종의 타입으로 처리하도록 부모 클래스를 도입합니다.
class Shape {
    // 3. 부모 타입으로는 자식 객체의 타입을 알 수 없으므로 아래와 같이 타입 코드를 도입하여
    // 자식 객체의 타입을 식별합니다.
    public static enum TypeCode { RECT, CIRCLE }
    public TypeCode type;    // private TypeCode type;
}
 
// 2. 그리고 모든 도형 개발자는 Shape 클래스를 상속하기로 약속합니다.
class Rect extends Shape {
    // 4. 자식 객체 생성 시, 타입 코드를 설정하기 위해 아래의 생성자를 추가합니다.
    public Rect(TypeCode type) { this.type = type; }
    public void draw() { System.out.println("draw rect"); }
}
 
class Circle extends Shape {
    public Circle(TypeCode type) { this.type = type; }
    public void draw() { System.out.println("draw circle"); }
}
 
class PowerPoint {
    private static Scanner keyboard = new Scanner(System.in);
 
    // 도형을 저장하는 컨네이터를 추가합니다.
    ArrayList<Shape> shapes = new ArrayList<>();
    // ArrayList<Rect> shapes = new ArrayList<>();
 
    public void run() {
        while (true) {
            String cmd = keyboard.nextLine();
            switch(cmd) {
            case "0":
                for(Shape e : shapes) {
                    // 부모 클래스에 객체의 타입이 존재하므로 이를 사용하여 자식 객체의
                    // 타입으로 변경하여 호출합니다.
                    switch(e.type) {
                    case RECT: ((Rect)e).draw(); break;
                    case CIRCLE: ((Circle)e).draw(); break;
                    }
                }
                break;
            case "1"// 사각형을 추가
                shapes.add(new Rect(Shape.TypeCode.RECT));
                break;
            case "2"// 원을 추가
                shapes.add(new Circle(Shape.TypeCode.CIRCLE));
                break;
            }
        }
    }
}
 
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
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
// Refactoring을 이용해보자
// 이제 polymorphism을 이용해서 구현해보자.
 
 package refactoring;
 
 import java.util.ArrayList;
 import java.util.Scanner;
 
 public class Test2 {
     public static void main(String[] args) {
         new PowerPoint().run();
     }
 }
 
 // Refactoring : replace type code with polymorphism
 abstract class Shape{
     // LSP(리스코프의 치환 원칙): 자식의 공통의 기능은 반드시 부모가 제공해야 한다.
     public abstract void draw();
 }
 
 class Rect extends Shape{
     public void draw() { System.out.println("draw rect"); }
 }
 
 class Circle extends Shape{
     public void draw() { System.out.println("draw circle"); }
 }
 
 // 새로운 도형을 추가할 경우, Shape만 상속하면 된다.
 class Triangle extends Shape{
     public void draw() { System.out.println("draw triangle"); }
 }
 
 class PowerPoint{
     private static Scanner keyboard = new Scanner(System.in);
 
 // 도형을 저장하는 컨테이너를 추가한다.
 ArrayList<Shape> shapes = new ArrayList<>();
 // ArrayList<Rect> shapes = new ArrayList<>();
 
 
 // 현재 코드는 객체 생성 코드에서 분기문이 사용되고있다.
 // 이 경우, 새로운 도형이 추가되면 코드의 수정이 발생할 수 밖에 없다.
 // 이 문제를 해결해보세요:)
 public void run() {
     while(true) {
         String cmd = keyboard.nextLine();
         switch(cmd) {
             case "0":    //도형을 출력
                 for(Shape e: shapes)
    // 부모 클래스에 객체의 타입이 존재하므로 이를 사용하여 자식 객체의 타입으로 변경하여 호출한다.
                     e.draw();
                 // 모양이 하나이지만 상황에 따라 사각형, 원 등 다양한 도형 그리기 가능
                 // 다형성(Polymorphism): 하나의 모양이 상황에 따라 다르게 동작하는 개념
                 break;
 
 // 변하는 것과 변하지 않는 것을 분리하자!!!!!!!! => 도형을 생성하는 팩토리 객체를 도입한다!
             case "1": shapes.add(new Rect()); break;
             case "2": shapes.add(new Circle()); break;
             case "3": shapes.add(new Triangle()); break;
         }
     }
    }
 }
 refactoring -> typecode -> polymorphism
 // 결론: typecode보다는 polymorphism을 쓰자!
 
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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
package refactoring;
 
import java.util.ArrayList;
import java.util.Scanner;
 
public class Test2 {
    public static void main(String[] args) {
        new PowerPoint().run();
    }
}
 
// Refactoring : replace type code with polymorphism
abstract class Shape{
    public abstract void draw();
}
 
class Rect extends Shape{
    public void draw() { System.out.println("draw rect"); }
}
 
class Circle extends Shape{
    public void draw() { System.out.println("draw circle"); }
}
 
// 새로운 도형을 추가할 경우, Shape만 상속하면 된다.
class Triangle extends Shape{
    public void draw() { System.out.println("draw triangle"); }
}
 
// 도형을 생성하는 팩토리 객체를 도입한다.
class ShapeFactory{
    public static Shape createShape(String cmd) {
        // shape factory가 실제로 내부적으로 상태정보를 갖고 있지 않기
     //  때문에 여러 개를 만들어줄 필요가 없으므로 클래스 함수로 만들어준것
        switch(cmd) {
            case "1"return new Rect();
            case "2"return new Circle();
            case "3"return new Triangle();
            default: throw new RuntimeException("Wrong command"); 
        // unchecked exception이다.
        }
    }
}
//도형을 생성 및 출력을 하는 파워포인트 클래스를 설계합니다.
class PowerPoint{
    private static Scanner keyboard = new Scanner(System.in);
    ArrayList<Shape> shapes = new ArrayList<>();
    public void run() {
        while(true) {
            String cmd = keyboard.nextLine();
            if(cmd.equals("0")) {
                for(Shape e: shapes)
                    e.draw();
                continue;
            }
            shapes.add(ShapeFactory.createShape(cmd));
        }
    }
}
 
cs


Comments