Recent Posts
Recent Comments
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
Tags
- lower_bound
- 성화봉송
- multiset
- yolo
- 백트레킹
- upper_bound
- 영어회화 100일의 기적
- 다이나믹 프로그래밍
- 외판원 순회
- 다음 API
- BFS
- 창훈쓰다
- 인간이 그리는 무늬
- 비트마스크
- 그리디 알고리즘
- 성화봉송주자
- 언어의 온도
- BOJ 2098
- MST
- 평창동계올림픽
- 다음 지도 api
- 삼성 코딩테스트
- 생활코딩
- Segment Tree
- DP
- boj
- 이분탐색
- 안드로이드 스튜디오
- 캘리그라피
- 위상정렬
Archives
- Today
- Total
Hoon222y
[1227-3] 디자인 패턴 / 리팩토링 본문
디자인 패턴 : 반복적으로 발생하는 문제들을 설명하고, 이에 대한 해결방안을 설명하는 학문
리팩토링 : 프로그램이 동작을 변경하지 않고, 내부 구조를 변경하는 유지보수의 개념
-> 디자인 패턴은 궁극적인 목표이고, 리팩토링은 이에 대한 방법론이라고 볼 수 있다.
예제를 통해서 진행하자
<서버예시>
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 |
'코딩 > 교육' 카테고리의 다른 글
[1226-3] 추상 팩토리 패턴(abstractFactory) (0) | 2018.12.27 |
---|---|
[1226-2] 컴포지트 패턴 (0) | 2018.12.27 |
[1227-2] 옵저버 패턴 (0) | 2018.12.27 |
[1227-1] 어댑터 패턴 / 팩토리 메서드 패턴 (0) | 2018.12.27 |
[1226-1] 추상화 / 인터페이스 /강결합/약결합/ (1) | 2018.12.26 |
Comments