IT이론 2019. 9. 8. 22:52

디메테르의 법칙은 객체 지향 디자인 원칙 중 하나이다. "최소 지식 원칙" 말 그대로 결합도가 낮은 설계를 위한 어떠한 원칙이다. 만약 메소드에 강한 결합도를 가진 로직이 들어가있다면 하나를 수정하면 많은 곳에서의 수정이 일어나는 대참사가 일어날 것이다. 그 중 디메테르의 법칙은 메소드 내의 다른 객체(API등)의 호출에 관련된 원칙이다. 간단히 글로써 정의를 보자.

 

디메테르의 법칙에서는 어떠한 객체 A의 메소드 m은 다음과 같은 종류의 객체에 있는 메소드들만 실행시킬 수 있다.

 

  1. A, 자기자신의 메소드
  2. m의 매개변수로 들어온 객체의 메소드
  3. m, 안에서 초기화된 객체(new 연산자)
  4. A의 인스턴스 변수(컴포넌트 객체)
  5. m의 스코프 안에서 O가 접근 가능한 전역변수

위의 내용을 조금 더 쉽게 설명하면,

 

  1. 클래스 자기 자신의 메소드 또는 인스턴스 변수의 메소드
  2. 메소드의 파라미터로 보낸진 객체의 메소드
  3. 메소드 또는 인스턴스 변수가 직접 초기화 시킨 객체
  4. 호출을 위한 메소드 또는 속성으로서 같은 클래스 안에서 선언된 객체
  5. 전역 객체(싱글톤과 같은 객체 포함)

 

사실 글로만 봤을 때는 이해하기 힘들다. 간단한 코드로 위의 내용을 살펴보자.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class A {
    private B b;
    public setA(B b) {
        b = b;
    }
    public myMethod(OtherObject other) {
        // ...
    }
    /* 디미터의 법칙을 잘 따른 예 */
    public okLawOfDemeter(Paramemter param) {
        myMethod();     // 자신의 메소드
        b.method();   // 자신의 멤버의 메소드
        Local local = new Local();
        local.method();    // 직접 생성한 객체의 메소드 
        param.method();    // 메소드의 인자로 넘어온 메소드
    }
    /* 디미터의 법칙을 어긴 예 */
    public violateLawOfDemeter(Paramemter param) {
        C c = param.getC();
        c.method();    // 인자로 받은 객체에서의 호출.
        param.getC().method();      // 위와 같음.
    }
}
cs

 

사실 모든 상황에서 해당 원칙을 따라야 하는지는 의문이다. 사실 모든 상황에 따라 다르지만 대부분은 지켜주는 것이 옳은 방법이긴 할 것 같다.

posted by 여성게
:

템플릿 메소드 패턴(template method pattern)은 소프트웨어 공학에서 동작 상의 알고리즘의 프로그램 뼈대를 정의하는 행위 디자인 패턴이다. 알고리즘의 구조를 변경하지 않고 알고리즘의 특정 단계들을 다시 정의할 수 있게 해준다.

 

템플릿(template)은 하나의 '틀'을 의미한다. 하나의 틀에서 만들어진 것들은 형태가 다 같다. 이런 틀 기능을 구현할 때는 template method 패턴을 이용할 수 있다. 이는 상속의 개념이 있는 상위 클래스와 하위 클래스의 구조에서 표현할 수 있다. 일반적으로 상위 클래스(추상 클래스)에는 추상 메서드를 통해 기능의 골격을 제공하고, 하위 클래스(구체 클래스)의 메서드에서는 세부 처리를 구체화한다.

이처럼 상위 클래스에서는 추상적으로 표현하고 그 구체적인 내용은 하위 클래스에서 결정되는 디자인 패턴을 template method 패턴이라고 한다. 상속의 개념처럼 template method 패턴도 코드 양을 줄이고 유지보수를 용이하게 만드는 역할을 한다. 따라서 유사한 서브 클래스가 존재할 때 template method 패턴을 사용하면 매우 유용하다.

 

예를 한번 들어보자. 음료를 만들기 위한 클래스가 하나 있으면 음료수를 만들기 위해서는 1)컵을 준비한다. 2)물을 붓는다 3)첨가물을 넣는다. 4)음료를 내어드린다. 이렇게 4가지의 음료 만드는 과정이 있다. 1,2,4번 과정은 모든 음료를 만드는 데 공통적인 과정이라면 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
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
/**
 * 공통 기능을 구현하고 세부 기능은 추상화한 추상클래스(음료제작)
 * @author yun-yeoseong
 * 
 */
public abstract class TemplateMethodPattern {
    
    public final void makeBeverage() {
        prepareCup();
        prepareWater();
        additive();
        finish();
    }
    
    /**
     * 공통 메소드
     */
    private void prepareCup() {
        System.out.println("컵을 준비한다.");
    }
    
    /**
     * 공통 메소드
     */
    private void prepareWater() {
        System.out.println("물을 붓는다.");
    }
    
    /**
     * 실제 구현이 필요한 부분
     */
    abstract void additive();
    
    /**
     * Hook 메소드, 서브클래스에서 구현이 필요하다면 오버라이드 해도된다.
     * 하지만 꼭 오버라이드가 강제는 아니다.
     */
    private void hookMethod() {
        System.out.println("hook method");
    }
    
    /**
     * 공통 메소드
     */
    private void finish() {
        System.out.println("음료를 내어드린다.");
    }
    
}
 
/**
 * 템플릿 추상 클래스를 상속하는 서브 클래스
 * 세부내용을 구현한다.
 * @author yun-yeoseong
 *
 */
public class SubClassA extends TemplateMethodPattern{
 
    @Override
    void additive() {
        System.out.println("커피가루를 넣는다.");
    }
    
    
}
 
/**
 * 템플릿 추상 클래스를 상속하는 서브 클래스
 * 세부내용을 구현한다.
 * @author yun-yeoseong
 *
 */
public class SubClassB extends TemplateMethodPattern{
 
    @Override
    void additive() {
        System.out.println("홍차가루를 넣는다.");
    }
    
    
}
 
cs

 

위와 같이 템플릿 메소드 패턴을 구현할 수 있다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class TestMain {
 
    public static void main(String[] args) {
        
        TemplateMethodPattern template1 = new SubClassA();
        TemplateMethodPattern template2 = new SubClassB();
        
        template1.makeBeverage();
        System.out.println("=========================");
        template2.makeBeverage();
        
    }
 
}
cs

 

posted by 여성게
:

퍼사드패턴 (facade pattern)

어떤 서브시스템의 일련의 인터페이스에 대한 통합된 인터페이스를 제공한다.
퍼사드에서 고수준 인터페이스를 정의하기 때문에 서브시스템을 더 쉽게 사용할수 있다.

 

퍼사드(Facede)는 '건물의 앞쪽 정면(전면)'이라는 사전적인 뜻을 가진다. 퍼사드패턴은 위의 그림과 같은 구조로 이루어지는 디자인패턴이다. 간단히 위의 그림을 설명하면 몇 개의 복잡한 서브시스템들과 클라이언트 사이에 Facede라는 객체를 세워놓음으로써 복잡한 관계를 정리 혹은 구조화하는 패턴이다. 

 

예를 들면 영화를 보기 위한 클라이언트가 있다. 조금은 억지스러운 예제이지만, 서브시스템으로는 Movie,Beverage라는 인터페이스와 이 인터페이스를 구현할 클래스가 있다.(영화를 보기 위해 음료를 구입하고 영화를 키고 음료를 다 마신다. 영화가 끝나면 영화를 끄고 다 먹은 음료컵을 버린다.) 물론 더 많은 서브시스템이 붙을 가능성도 있다. 퍼사드패턴을 적용하기 전에는 클라이언트가 영화를 보기위해서는 클라이언트 코드에 Movie,Beverage 등의 서브시스템을 의존하여 일일이 음료를 사고 영화를 보러 들어가고 등의 로직을 직접 호출해야한다.(만약 복잡한 서브시스템들이 더 많다면 더 많은 호출이 이루어질 것이다.) 그리고 하나의 기능을 수행하기 위해 여러개의 서브시스템들이 조합되어야 한다면 클라이언트는 여러개의 서비스를 직접 호출하여 로직을 만들어야 할 것이다.

 

그래서 퍼사드 패턴을 이용하여 모든 관계를 전면에 세워진 Facede 객체를 통해서만 이루어질 수 있게 단순한 인터페이스를 제공하는 것이다. 퍼사드 패턴을 이용하면 서브시스템 내부에서 작동하고 있는 많은 클래스들의 관계나 사용법을 의식하지 않고 퍼사드 객체에서 제공하는 단순화된 하나의 인터페이스만 사용하므로, 클래스 간의 의존 관계가 줄어들고 복잡성 또한 낮아지는 효과를 볼 수 있다.

 

여기서 퍼사드 객체는 클라이언트의 요청이 발생했을 때, 서브시스템 내의 특정한 객체에 요청을 전달하는 역할을 한다. 이 역할을 수행하려면 퍼사드 객체는 서브시스템의 클래스들에 어떤 기능이 있는지 알고 있어야 한다.(인스턴스 필드에 선언) 즉, 서브시스템은 자신의 기능을 구현하고 있으면 되고, 퍼사드 객체로부터 자신이 수행할 행위에 대한 요청을 받아 처리하면 되는 것이다. 또한 서브시스템은 퍼사드 객체의 어느 정보도 알고 있을 필요가 없다.

 

Facede Pattern의 장점

  • 퍼사드는 소프트웨어 라이브러리를 쉽게 사용할 수 있게 해준다. 또한 퍼사드는 소프트웨어 라이브러리를 쉽게 이해할 수 있게 해 준다. 퍼사드는 공통적인 작업에 대해 간편한 메소드들을 제공해준다.
  • 퍼사드는 라이브러리를 사용하는 코드들을 좀 더 읽기 쉽게 해준다.
  • 퍼사드는 라이브러리 바깥쪽의 코드가 라이브러리의 안쪽 코드에 의존하는 일을 감소시켜준다. 대부분의 바깥쪽의 코드가 퍼사드를 이용하기 때문에 시스템을 개발하는 데 있어 유연성이 향상된다.
  • 퍼사드는 좋게 작성되지 않은 API의 집합을 하나의 좋게 작성된 API로 감싸준다.

 

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
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
/**
 * 음료관련 서브시스템
 * @author yun-yeoseong
 *
 */
public interface BeverageService {
    
    public void prepare();
    public void takeout();
    public void gotoTrash();
    
}
 
public class BeverageServiceImpl implements BeverageService {
 
    @Override
    public void prepare() {
        System.out.println("음료준비");
    }
 
    @Override
    public void takeout() {
        System.out.println("음료주기");
    }
 
    @Override
    public void gotoTrash() {
        System.out.println("다먹은 음료컵 버리기");
    }
 
}
 
/**
 * 영화관련 서브 시스템
 * @author yun-yeoseong
 *
 */
public interface MovieService {
    
    public void prepare();
    public void turnOn();
    public void turnOff();
}
 
public class MovieServiceImpl implements MovieService {
 
    @Override
    public void prepare() {
        System.out.println("영화준비");
    }
 
    @Override
    public void turnOn() {
        System.out.println("영화틀기");
    }
 
    @Override
    public void turnOff() {
        System.out.println("영화끄기");
    }
 
}
 
 
/**
 * 영화,음료 시스템의 인터페이스를 하나의 인터페이스로
 * 몰아서 더 쉽게 서브 시스템들을 사용할 수 있게한다.
 * @author yun-yeoseong
 *
 */
public interface MovieFacede {
    
    public void prepareWatchMovie();
    public void watchMovie();
    public void endMovie();
    
}
 
public class MovieFacedeImpl implements MovieFacede {
    
    private MovieService movieService;
    private BeverageService beverageService;
    
    public MovieFacedeImpl(MovieService movieService,BeverageService beverageService) {
        
        this.movieService=movieService;
        this.beverageService=beverageService;
        
    }
    
    @Override
    public void prepareWatchMovie() {
        
        beverageService.prepare();
        beverageService.takeout();
        movieService.prepare();
        
    }
 
    @Override
    public void watchMovie() {
        
        movieService.turnOn();
        
    }
 
    @Override
    public void endMovie() {
        
        movieService.turnOff();
        beverageService.gotoTrash();
        
    }
 
}
cs

 

만약 Spring fw을 사용한다면 퍼사드 객체에 DI해주는 어노테이션을 붙이면 클라이언트에서 직접 의존 주입해줄 필요가 없어진다. 예제가 조금 억지스러운 면이 없지않지만 대략적으로 퍼사드패턴을 사용하는 이유와 사용법이 전달되었으면 좋겠다.

posted by 여성게
: