스트레티지 패턴(Strategy Pattern,전략패턴)은 알고리즘군을 정의하고 각각을 캡슐화하여 교환해서 사용할 수 있도록 만들어준다. 스트레티지 패턴을 활용하면 알고리즘을 사용하는 클라이언트와는 독립적으로 알고리즘을 변경할 수 있다.

 

바로 예제를 살펴보자.

 

Duck이라는 추상클래스가 있다고 생각해보자. 해당 추상클래스는 다양한 오리라는 서브클래스를 만들기 위해 여러가지 공통 메소드와 확장을 위한 추상 메소드를 가진 추상클래스이다. 만약 아래와 같은 요청이 있다고 생각해보자.

 

"모든 오리에게 날 수 있는 기능과 오리소리를 내는 기능을 추가해라. 하지만 구현체마다 해당 행동들은 모두 달라질 수 있다"

 

어떤 식으로 구현을 하면 좋을까? 두개의 행동이 모든 구현체마다 달라질 수 있다는데, 추상클래스에서 추상메소드로 만들어줘서 모든 서브 클래스에서 오버라이드 할 수 있게 할까? 그렇다면 어떠한 문제가 생길수 있을까 생각해보자. 문제는 이것이다. 서브 클래스에서 나는 행동과 오리소리를 내는 행동을 오버라이드 한다면 과연 이러한 코드는 재사용이 가능할까? 문제를 다시 한번보자. 

 

"구현체마다 행동들은 모두 달라질 수 있다"

 

달라질 수 있다지, 모든 구현체마다 다르다는 아니다. 이 말은 종류가 다른 오리지만 행동이 같은 교집합이 존재할 수 있다는 것이다. 그렇다면 서브클래스에서 슈퍼클래스의 나는 행동과 오리소리를 내는 행동을 오버라이드한다면 같은 행동을 갖는 다른 서브 클래스들이 동일한 코드가 중복되서 나타날 것이다. 그러면 다음 해결책을 보자.

 

"달라지는 행동들을 별도의 인터페이스로 분리하여 해당 기능이 필요한 오리 서브 클래스에 구현을 강제하자."

 

이 해결책 또한 재활용하기 힘들다. 서브 클래스에서 인터페이스의 구현을 강제하여 나는 행동과 오리소리 행동을 구현한다면 이 역시 재활용할 수 없는 코드가 될 것이다. 즉, 구현 클래스에서만 구현코드가 나타나기 때문에 행동이 같은 다른 서브 클래스에서 해당 코드를 재활용할 수 없다.

 

여기서 사용할 수 있는 디자인 패턴이 스트래티지 패턴(Strategy Pattern)이다. 이 패턴의 핵심은 달라지는 행동부분을 분리하고 캡슐화하여 클라이언트와는 독립적으로 행동을 주입할 수 있게 하는 것이다. 바로 구현 코드를 살펴보자.

 

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
public abstract class Duck {
    
    private FlyBehavior fly;
    private QuackBehavior quack;
    
    public void swim() {
        System.out.println("헤엄친다.");
    }
    
    public void fly() {
        fly.fly();
    }
    
    public void quack() {
        quack.quack();
    }
 
    public void setFly(FlyBehavior fly) {
        this.fly = fly;
    }
 
    public void setQuack(QuackBehavior quack) {
        this.quack = quack;
    }
    
}
cs

 

위의 추상 클래스를 살펴보면 이 추상클래스는 다양한 오리의 구현체의 공통 행동들을 담은 추상클래스이다. 그리고 오리 구현체들마다 달라질 수 있는 행동을 인터페이스로 분리하여 캡슐화하고 있다. 이 말은 상속이나 구현을 강제하는 것이 아니고 인스턴스 변수로 달라지는 행동을 구성(composition)하였다. 그리고 달라지는 행동들은 인터페이스를 구현하여 특정 클래스의 군(알고리즘군)으로 만들어지기 때문에 같은 행동을 하는 다른 오리들이 같은 클래스를 사용하므로 재사용성이 높아진다.

 

1
2
3
4
5
6
7
public interface FlyBehavior {
    public void fly();
}
 
public interface QuackBehavior {
    public void quack();
}
cs

 

달라지는 행동들은 인터페이스로 분리하였다.

 

1
2
3
4
5
6
7
public class ADuck extends Duck{
 
}
 
public class BDuck extends Duck{
 
}
cs

 

추상클래스를 상속받은 오리서브 클래스들은 만들었다. 특징은 ADuck은 헤엄은 칠 수 있지만 날지 못하고 오리소리를 내지 못한다. BDuck은 수영도 가능하고 날수도 있고 울수도 있다. 과연 이러한 달라진 행동들을 실제로 어떻게 사용할까?

 

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
public class NotFlyBehavior implements FlyBehavior {
 
    @Override
    public void fly() {
        System.out.println("못 날아요");
    }
 
}
 
public class DoFlyBehavior implements FlyBehavior {
 
    @Override
    public void fly() {
        System.out.println("난다.");
    }
 
}
 
public class NotQuackBehavior implements QuackBehavior {
 
    @Override
    public void quack() {
        System.out.println("못 운다.");
    }
 
}
 
public class DoQuackBehavior implements QuackBehavior {
 
    @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
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
/*
 * ADuck은 날지못하고 울지도 못한다.
 * BDuck은 날수 있고 울 수도 있다.
 */
public class DuckMain {
 
    public static void main(String[] args) {
        
        Duck a = new ADuck();
        a.setFly(new NotFlyBehavior());
        a.setQuack(new NotQuackBehavior());
        
        
        Duck b = new BDuck();
        b.setFly(new DoFlyBehavior());
        b.setQuack(new DoQuackBehavior());
        
        System.out.println("===========ADuck===========");
        
        a.swim();
        a.fly();
        a.quack();
        a.setFly(new DoFlyBehavior());
        a.fly();
        
        System.out.println("===========BDuck===========");
        
        b.swim();
        b.fly();
        b.quack();
    }
 
}
 
=>결과
===========ADuck===========
헤엄친다.
못 날아요
못 운다.
난다.
===========BDuck===========
헤엄친다.
난다.
운다.
 
cs

 

이렇게 사용 시점에 알고리즘을 set 메소드로 주입하여 사용한다. 만약 추후에 ADuck이 나는 기능이 추가된다면 위 코드와 같이 날수 있는 구현 클래스를 주입해주기만 하면 ADuck은 날 수 있게 된다. 즉, ADuck은 행동을 인터페이스로 의존만 하고 있고(구성,composition) 구체적인 행동을 몰라도 되는 것이다.

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 여성게
:

싱글톤(singleton)은 '단독 개체', '독신자'라는 뜻 말고도 '정확히 하나의 요소만 갖는 집합' 등의 의미가 있다. singleton 패턴은 객체의 생성과 관련된 패턴으로서 특정 클래스의 객체가 오직 한 개만 존재하도록 보장한다. 즉 클래스의 객체를 하나로 제한한다. 프로그램에서 이런 개념이 필요할 때는 언제일까? 프린터 드라이버의 예를 들어보자.

 

여러 컴퓨터에서 프린터 한 대를 공유하는 경우, 한 대의 컴퓨터에서 프린트하고 있을 때 다른 컴퓨터가 프린트 명령을 내려도 현재 프린트하는 작업을 마치고 그다음 프린트를 해야지 두 작업이 섞여 나오면 문제가 될 것이다. 즉 여러 클라이언트(컴퓨터)가 동일 객체(공유 프린터)를 사용하지만 한 개의 객체(프린트 명령을 받은 출력물)가 유일하도록 상위 객체가 보장하지 못한다면 singleton 패턴을 적용해야 한다. 이처럼 동일한 자원이나 데이터를 처리하는 객체가 불필요하게 여러 개 만들어질 필요가 없는 경우에 주로 사용한다.

 

사실 싱글톤을 구현하는 방법은 몇 가지 존재한다. 하지만 오늘 소개할 싱글톤 패턴 코드는 가장 많이 사용하고 안전한 싱글톤 객체를 만드는 코드 한가지만 다루어 볼것이다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Singleton {
    
    private Singleton() {}
    
    private static class LazyHolder{
        public static final Singleton SINGLETON=new Singleton();
    }
    
    public static Singleton getInstance() {
        return LazyHolder.SINGLETON;
    }
    
}
cs

 

위 코드는 싱글톤으로 생성될 객체를 Lazy Loading 하는 코드이다. 클래스가 로딩될때 LazyHolder라는 내부 클래스는 로딩되지 않는다. 이유는 LazyHolder라는 객체를 참조하여 호출하는 코드가 존재하지 않기 때문이다. 그 말은 진짜 Singleton 객체가 필요하여 getInstance()를 호출할때 LazyHolder 클래스가 로딩되는 것이다. 바로 필요할때 객체를 생성한다는 장점이 있다.

 

또 하나의 장점이 있다. 해당 코드는 다른 싱글톤 생성 코드와 달리 싱글톤 인스턴스가 NULL인지 체크하는 것과 synchronized 블럭을 사용하여 동기화하는 부분도 존재하지 않는다. 왜 일까? 그러면 과연 멀티 스레드 환경에서 안전할까? 안전하다! JVM에서 클래스를 로딩하는 과정에서는 멀티 스레드 환경에서도 안전한 동기화 환경을 제공하기 때문이다! 즉, LazyHolder 클래스가 로딩되는 과정에 static 한 SINGLETON 인스턴스를 생성하기 때문에 싱글톤 객체 생성과정에서 별도의 동기화 처리를 하지 않아도 클래스 로딩되는 과정 자체가 JVM에서 동기화 환경을 제공하기 때문에 멀티 스레드 환경에서도 안전한 것이다!

 

LazyHolder를 이용한 싱글톤 객체 생성 시 장점

  1. 싱글톤 객체가 진짜 필요할 때까지 초기화를 늦춘다.(Lazy Loading)
  2. JVM 클래스 로딩 타임때 싱글톤 객체를 생성하므로 멀티 스레드 환경에 안전한 동기화 이슈를 JVM이 직접 해결해준다.

싱글톤 객체를 생성할 때는 위의 방법을 이용하자!

 

 

기타 싱글톤 객체 생성 코드

나머지 방법들은 멀티 스레드에 안전하지 않으면서도 불필요한 Lock을 잡고 있는 방법들이기 때문에 사용을 권장하지 않는다.

 

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
public class Singleton_methodSync {
    
    private static Singleton_methodSync instance;
 
    private Singleton_methodSync() {}
    
    public static synchronized Singleton_methodSync getInstance() {
        if(instance == null) {
            instance = new Singleton_methodSync();
        }
        return instance;
    }
    
}
 
//Double Checked Lock
public class Singleton_methodSync2 {
    
    private static Singleton_methodSync2 instance;
    
    private Singleton_methodSync2() {}
    
    public static Singleton_methodSync2 getInstance() {
        if(instance == null) {
            synchronized (Singleton_methodSync2.class) {
                if(instance==null) instance = new Singleton_methodSync2();
            }
        }
        return instance;
    }
}
 
//이른 초기화
public class Singleton_precreate {
    
    private static Singleton_precreate instance = new Singleton_precreate();
    
    private Singleton_precreate() {    }
    
    public static Singleton_precreate getInstance() {
        return instance;
    }
    
}
 

public class Singleton_notsafety {        

private static Singleton_methodSync instance;

    private Singleton_methodSync() {}    

    public static Singleton_methodSync getInstance() {

        if(instance == null) {

            instance = new Singleton_methodSync();

        }

        return instance;

    }    

}

 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 여성게
:


SOLID 원칙이란?



S : SRP(Single Responsibility Principle) - 단일 책임 원칙


O : OCP(Open Closed Principle) - 개방-폐쇄 원칙


L : LSP(Liskov Substitution Principle) - 리스코프 치환 원칙


I : ISP(Interface Segregation Principle) - 인터페이스 분리 원칙


D : DIP(Dependency Inversion Principle) - 의존 역전 원칙




S : SRP(Single Responsibility Principle) - 단일 책임 원칙



단일 책임 원칙이란 말 그대로, 하나의 객체는 하나의 책임만 가져야 한다는 원칙이다. 만약 많은 기능을 한 객체에 다 쑤셔 넣는다면? 그만큼 그 객체와 강하게 결합된 객체들이 많아질 것이다. 또한 많은 기능들이 과연 변경이 하나도 없을 수가 있을까? 아니다. 변경이 있을 때마다 객체에 변경이 요해지는 것이다. 결론적으로 단일 책임 원칙을 지킨다면 변경에 있어서 유연한 대처가 가능 해진다.(결합도가 낮아진다.) 그러면서 회귀 테스트 비용 또한 줄일 수 있다. 여기서 회귀 테스트란(regression test) 시스템에 변경이 발생하였을 때, 기존 기능에 영향을 주는지 평가하는 테스트이다. 만약 단일 책임 원칙을 지켜서 변경에 유연한 코드를 만든다면 회귀 테스트 비용을 대폭 줄일 수 있는 효과까지 나타난다. 


단일 책임 원칙과 연관되는 단어에 산탄총 수술이라는 것이 있다. 만약 하나의 기능(책임)이 여러개의 클래스들로 분산된 경우 그 책임에 변경이 있다면 그 책임을 의존하고 있는 여러 클래스에 대해 변경이 요해진다. 여기서 나온 것이 산탄총 수술이라는 용어이다. 만약 산탄총을 이용해 동물을 쐈다면? 하나의 총알(책임)에서 여러개의 총알이 산탄되어 박힐 것이다. 그렇다면 그 동물을 수술하려면 흩어진 모든 총알이 박힌 환부(책임을 의존하고 있는 클래스들)를 치료해야 할것이다.(여기에서 산탄총 수술이라는 용어가 나왔다.) 여기서 해결 할 방법이란? 하나의 책임을 한 클래스로 분리해서 흩어진 공통 책임을 한 곳에 모아 응집도를 높히는 일이다. 이것은 관점 지향 프로그래밍 기법으로 해결한다. 여러 조인포인트(쉽게 이벤트발생시점) 중에서 특정 포인트 컷에서(특정 이벤트)  실행하는 어드바이스(책임)를 가진 애스팩트(포인트컷 + 어드바이스)를 만들어 위빙(특정 시점에 어드바이스를 실행시키는 역할)하여 해결하는 것이다. 





O : OCP(Open Closed Principle) - 개방-폐쇄 원칙


개방-폐쇄 원칙이란 간단히 확장에는 열려있고 변경에는 닫혀있는 형태로 개발하는 원칙이다. 즉, 기존 코드에는 변경이 없으면서 기능을 추가 할 수 있도록 설계하는 것이다. 여기서 이용하는 것이 인터페이스이다. 어떠한 기능을 사용하는 클라이언트(여기서 말하는 클라이언트는 사용자가 아닌 기능을 사용하는 클래스)는 인터페이스 타입으로 의존 주입을 받는다. 그렇다면 클라이언트 코드에는 변경 없이 인터페이스의 메소드를 이용해 구체적인 구현 클래스를 이용하고 개발자 입장에서는 인터페이스를 실체화한 클래스를 만들어 계속 해서 기능을 확장하면 되므로 확장에는 열려있고(기능추가) 변경에는 닫혀있는(클라이언트코드) 형태의 원칙을 지키게 되는 것이다. 이런 개방-폐쇄 원칙은 단위 테스트에도 많이 이용된다. 테스트 더블(테스트를 위한 가짜객체)을 구현하기 위해 실제 인터페이스를 상속받아서 더미 객체, 테스트스텁, 테스트 스파이, 가짜 객체, 목객체 등을 간단히 구현하여 객체간의 관계, 정보전달 여부등을 확인 할때 사용한다.   





L : LSP(Liskov Substitution Principle) - 리스코프 치환 원칙



리스코프 치환 원칙이란 일반화관계(is a kind)에서 "자식클래스는 최소한 자신의 부모클래스에서 가능한 행위는 수행할 수 있어야한다."라는 원칙이다. 즉, 코드에서 부모클래스 인스턴스에서 자식 클래스 인스턴스로 변경이 되어도 프로그램의 의미는 변화되지 않아야 한다. 여기서 중요한 것이 "일관성"이다. 일관성을 지키는 코드를 작성하는 가장 쉬운 방법이 무엇일까? 바로 슈퍼클래스에서 상속받은 메소드들이 서브 클래스에서 오버라이드, 즉 재정이 되지 않도록 하면 되는 것이다. 추가 기능이 필요할 때는 절대 부모클래스에서 상속받은 메소드를 오버라이드하는 것이 아니라 별도의 메소드로 정의해 사용하는 것이다. 






I : ISP(Interface Segregation Principle) - 인터페이스 분리 원칙


인터페이스 분리 원칙이란 클라이언트는 여러 기능을 가진 클래스를 사용할 때 특정 기능만을 사용하는 경우가 많기 때문에 특정 기능만을 위한 인터페이스를 만들어 놓고 분리해 다른 기능이 변경되도 사용자의 기능에 아무런 영향을 받지 않도록 하는 방법이다. 


만약 한 객체에 많은 기능이 들어가 있다면 기능들 사이에 연관이 있을 확률이 아주 높아지게 된다. 그렇다면 만약 팩스를 사용하는 클라이언트가 copy()메소드의 변경으로 인해 fax()에 이상이 생겨서 팩스를 사용하는데 오류가 발생할 수도 있게 되는 것이다. 여기서 나온 원칙이 인터페이스 분리 원칙이다.


이런 식으로 인터페이스로 핵심 기능을 분리하게 되면 프린트를 사용하는 클라이언트는 다른 기능의 변경에 대해 아무런 영향을 받지 않고 자신이 사용할 기능을 안전히 사용가능 하게된다.






D : DIP(Dependency Inversion Principle) - 의존 역전 원칙


의존 역전 원칙이란 의존 관계를 맺을 때 변화하기 쉬운 것보다는 변화하기 어려운 것에 의존하라는 원칙이다. 여기서 변화하기 쉬운 것은 실체화된 클래스를 이야기하면 변화하기 어려운 것은 추상적인 인터페이스를 이야기 한다. 



이런식으로 아이가 가지고 놀 장난감을 구체적인 구현 클래스로 두는 것이 아니라 인터페이스 타입으로 두어서 구체적으로 가지고 놀 장난감을 외부에서 의존 주입받는 방식으로 개발하는 원칙이다. 즉, 변화를 의존주입으로 쉽게 받아 드릴 수 있는 것이다.


1
2
3
4
5
6
7
8
9
10
11
12
public class kid{
    private Toy toy;
 
    public void setToy(Toy toy){
        this.toy=toy;
    }
    public void playing(){
        toy.play();
    }    
}
 
 
cs


이렇게 인터페이스 타입의 인스턴스 변수를 선언하면 결합도도 느슨해지고 변화에 유연하게 대처할 수 있는 코드가 될 수 있다. 이렇게 외부에서 의존주입을 받는 형식이 "역전"이 되었다고 표현한다.

posted by 여성게
: