Web/Spring Cloud 2019. 8. 25. 18:38

 

2019/02/24 - [Web/Spring Cloud] - Spring Cloud - Eureka를 이용한 마이크로서비스 동적등록&탐색&부하분산처리

 

Spring Cloud - Eureka를 이용한 마이크로서비스 동적등록&탐색&부하분산처리

Spring Cloud - Eureka를 이용한 마이크로서비스 동적등록&탐색&부하분산처리 스프링 클라우드 유레카는 넷플릭스 OSS에서 유래됐다. 자가 등록, 동적 탐색 및 부하 분산에 주로 사용되며, 부하 분산을 위해 내부..

coding-start.tistory.com

우리는 이전 포스팅들에서 Spring Cloud를 다루어보면서 동적인 서비스 등록과 서버사이드 로드밸런싱에 중대한 역할을 하게 되는 Eureka에 대해 다루어 봤었다. 이러한 유레카를 이용하여 우리는 애플리케이션의 무중단 배포도 가능하다. 새로운 애플리케이션을 올리고 이전 버전의 애플리케이션을 죽이는 단순한 과정에서 우리는 중요한 개념을 생각해야 한다. 만약 이전 버전의 애플리케이션이 사용자의 요청을 받아 처리 중이라면? 그냥 애플리케이션을 죽이면 처리중인 요청을 끝까지 처리하지 못하고 데이터 유실이 발생할 것이다. 이럴때 우리는 우아하게 종료할 수 있는 방안이 필요하다. 예를 들면, Apache에서도 프로세스를 재시작하는 명령에 restart / graceful 명령이 존재한다. 전자는 단순히 stop&start이고 후자는 받은 요청을 모두 처리하고 종료하게 된다. 이러한 기능을 Spring boot는 어떻게 제공할까?

 

Actuator를 사용하면 된다. 스프링 액츄에이터는 다양하게 실행 중인 애플리케이션의 모니터링 정보 및 유용한 기능을 제공한다. 이중 shutdown 기능이 있는데, 액츄에이터의 shutdown은 graceful 하게 shutdown을 시켜준다 ! 즉, 데이터 유실 없이 안전하고 우아한 애플리케이션 종료를 제공한다.

posted by 여성게
:

adapter의 의미는 '접속 소켓', '확장 카드', '(물건을 다른 것에) 맞추어 붙이다', '맞추다'이다. 그림처럼 전원에서 나오는 전기는 대개 교류 200V이지만 노트북은 직류 120V이다. 그러나 우리는 노트북을 사용할 때 아무런 불편 없이 노트북 선을 전원에 그대로 꽃아 사용한다. 이는 중간에 교류 200V를 직류 120V로 바꾸어 주는 무엇인가가 존재하기 때문이다. 이것이 노트북 선 중간에 붙어 있는 어댑터이다.

 

 

  1. 클래스 adapter 패턴 : 상속을 이용한 어댑터 패턴
  2. 인스턴스 adapter 패턴 : 구성(위임)을 이용한 어댑터 패턴

그림에서 client target 인터페이스를 사용하여 메서드를 호출한다. adapter에서는 adaptee 인터페이스를 사용하여 concreteMethod 호출로 변경한다. 이때 client는 중간에 adapter가 존재한다는 것을 인식하지 못한다.

 

<클래스 Adapter 패턴>

<인스턴스(객체) Adapter 패턴>

 

클래스 어댑터 패턴은 다중 상속을 허용하는 프로그래밍 언어에서만 가능한 패턴이다.

 

콘센트를 예를 들어 사용해보자. 우리나라는 기본적으로 220V 플러그인을 사용하는 나라이다. 하지만 외국은 대부분 110V를 사용하는 나라가 많기 때문에 중간에 어댑터를 이용하여 사용한다. 이것을 객체로 디자인하여 사용해보자.

 

인스턴스(객체) 어댑터 패턴

 

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
public interface Plugin {
    
    public void connect();
    public void disconnect();
    
}
 
public class Plugin_220V implements Plugin {
    @Override
    public void connect() {
        System.out.println("220V connect");
    }
    @Override
    public void disconnect() {
        System.out.println("220V disconnect");
    }
}
 
public interface PluginAdapter {
    public void connect();
    public void disconnect();
}
 
public class Adapter_110V implements PluginAdapter {
    
    private Plugin plugin;
    
    public Adapter_110V(Plugin plugin) {
        this.plugin=plugin;
    }
    @Override
    public void connect() {
        System.out.println("110V->220V convert");
        this.plugin.connect();
    }
    @Override
    public void disconnect() {
        System.out.println("110V->220V convert");
        this.plugin.disconnect();
    }
}
 
public class AdapterMain {
    public static void main(String[] args) {
        PluginAdapter plugin = new Adapter_110V(new Plugin_220V());
        plugin.connect();
        plugin.disconnect();
    }
}
=>result
110V->220V convert
220V connect
110V->220V convert
220V disconnect
 
cs

 

객체 어댑터 패턴은 위와 같이 구성을 이용하여 어댑터 클래스를 구성한다. 클라이언트는 어댑터 인터페이스로만 의존하고 실제적인 어댑티는 알지 못한다.

posted by 여성게
:

composite의 의미는 '합성의', '합성물', '혼합 양식'이다. 이를 통해 composite 패턴이 뭔가 합쳐진 형태임을 짐작할 수 있다. 또 composite 패턴의 구성을 보면 일반적인 트리 구조를 하고 있는데, [그림 5-34]처럼 부분-전체의 상속 구조이다. 이와 같이 표현되는 조립 객체를 컴포지트 객체(composite object)라고 한다.

composite 패턴은 사용자가 단일 객체와 복합 객체 모두 동일하게 다루도록 한 것이다. 이런 형태는 재귀적인 구조로서, 마치 파일 구조에서 디렉토리 안에 파일이 존재할 수도 있고, 또 다른 디렉토리(서브 디렉토리)가 존재할 수 있는 것과 같다. 즉 composite 패턴은 그릇(디렉토리)과 내용물(파일)을 동일시해서 재귀적인 구조를 만들기 위한 설계 패턴이다.

 

 

컴포지트 패턴을 가장 잘 설명할 수 있는 예제는 파일과 디렉토리 관계이다. 가장 간단하게 파일과 디렉토리를 구현해보자.

 

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
public class File {
    
    private String name;
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
    
    
}
 
public class Directory {
    
    private String name;
    private List<File> files;
    
    public String getName() {
        return name;
    }
    
    public void setName(String name) {
        this.name = name;
    }
    
    public void addFile(File file) {
        if(!files.contains(file)) files.add(file);
        else System.out.println("동일한 파일이 존재합니다.");
    }
    
}
cs

 

위와 같이 구현했다고 생각해보자. 얼핏보면 괜찮은 것 같다. 하지만 만약 디렉토리 밑에 디렉토리를 넣고 싶다면? 어떻게 해야할까? 여기서 생각해보자. 디렉토리 안에는 파일도 들어갈 수 있고 또 다른 디렉토리가 들어갈 수도 있다. 여기서 파일하나가 단수 객체라면 디렉토리는 복수 객체가 될 수 있다. 또한 최상위는 하나의 디렉토리로 이루어지고 그 밑으로 디렉토리 및 파일이 들어간다. 즉, 전체 관계가 하나의 디렉토리이고 그 밑에 부분이 디렉토리 또는 파일이 될 수 있다. 이렇게 단수 혹은 복수를 동일한 인터페이스로 다룰 수 있게 하는 전체-부분관계를 구현할때 가장 유용한 것이 컴포지트 패턴이다. 

 

컴포지트 패턴을 다시 클래스다이어그램으로 나타내면 아래와 같다.

 

 

Component - 구체적인 부분, 즉 Leaf 클래스와 전체에 해당하는 Composite 클래스에 공통 인터페이스를 제공한다.

Leaf - 구체적인 부분 클래스로 Composite 객체의 부품으로 설정한다.

Composite - 전체 클래스로 복수 개의 Component를 갖도록 정의한다. 그러므로 복수 개의 Leaf, 심지어 복수 개의 Composite 객체를 부분으로 가질 수 있다.

 

우리는 맨 처음의 그림과는 다르게 파일 시스템을 설계할 것이다. 파일 시스템은 추상적으로 보면 트리와 같은 구조를 가지고 있다. 즉, Component를 Node로 표현하고 Leaf로 File Composite로 Directory를 표현할 것이다. 바로 구현해보자.

 

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
public abstract class Node {
    
    private String name;
    private int depth=0;
    
    public Node(String name) {
        this.name=name;
    }
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
    public int getDepth() {
        return depth;
    }
 
    public void setDepth(int depth) {
        this.depth = depth;
    }
    
    public abstract int getSize();
    public abstract void print();
}
 
public class File extends Node{
    
    private int size;
    
    public File(String name, int size) {
        super(name);
        this.size=size;
    }
 
    @Override
    public int getSize() {
        return this.size;
    }
 
    @Override
    public void print() {
        System.out.println("File - "+getName());
    }
    
}
 
public class Directory extends Node{
    
    private List<Node> nodes = new ArrayList<>();
    
    public Directory(String name) {
        super(name);
    }
    
    public void addNode(Node node) {
        nodes.add(node);
    }
    
    public void removeNode(Node node) {
        nodes.remove(node);
    }
    
    public int getSize() {
        int size = nodes.size();
        System.out.println("Directory size - "+size);
        return size;
    }
    
    public void print() {
        
        for(Node node : nodes) {
            node.print();
        }
        
    }
    
}
cs

 

완벽한 구현은 아니지만 파일 시스템을 컴포지트 패턴으로 구현 가능하다라는 정도만 알자!

posted by 여성게
:

abstract factory는 '추상적인 공장'이란 뜻으로, 밑의 그림과 같이 여러 개의 concrete Product 추상화시킨 것이다. 따라서 구체적인 구현은 concrete Product 클래스에서 이루어진다. abstract factory에서는 사용자에게 인터페이스(API)를 제공하고, 인터페이스만 사용해서 부품을 조립하여 만든다. 즉 추상적인 부품을 조합해서 추상적인 제품을 만든다.

 

 

2019/08/19 - [디자인패턴] - 디자인패턴 - 팩토리 메소드 패턴(Factory Method pattern)

 

디자인패턴 - 팩토리 메소드 패턴(Factory Method pattern)

factory는 '공장'이란 뜻이고, 공장은 물건을 만드는 곳이다. 여기서 물건에 해당되는 것이 바로 인스턴스이다. factory method 패턴은 상위 클래스에서 객체를 생성하는 인터페이스를 정의하고, 하위 클래스에서..

coding-start.tistory.com

 

이전 포스팅에서 다루어봤던 팩토리 메소드 패턴의 확장판이라고 볼수 있는 것이 추상 팩토리 패턴이다. 팩토리 메소드 패턴을 다시 떠올려보자. 엘리베이터는 특정 스케쥴링 방식에 따라 다른 방식으로 동작한다. 즉, 스케쥴러 클래스의 선택에 따라 엘리베이터의 동작이 달라지는 것이다. 하지만 이런 경우를 생각해보자 !

 

엘리베이터 제조업체가 여러개가 있다. LG,Samsung,Hundai 등의 많은 제조업체가 있고 해당 업체의 엘리베이터를 사용하려면 각각 제조업체의 모터,문,렘프 등을 사용해야한다.(이번 예제에서는 각 업체마다 모터, 문만 다룬다.) 경우에 따라 부품의 수가 더 많아 질 수도 있고 제조업체도 더욱 많아 질 수도 있다. 

 

만약 기존에 사용했던 팩토리 메소드 패턴을 사용한다면 어떻게 될까? Motor,Door라는 인터페이스가 있고 각각을 구현한 LGMotor,LGDoor,SamsungMotor,SamsungDoor 등 업체마다 다른 부품에 해당하는 클래스를 생성해주어야 하고 각각을 생성해주는 팩토리 클래스를 만들어주어야 한다.(LGMotorFactor,SamsungMotorFactory,HundaiFactory...) 3개의 업체라면 객체 생성을 담당하는 팩토리 클래스가 6개가 생성된다.(MotorFactory - LG,Samsung,Hundai / DoorFactory - LG,Samsung,Hundai) 딱 봐도 비효율적일 것같다라는 느낌이 든다.

 

여기서 핵심은 특정 제조업체의 엘리베이터를 이용하면 그들의 부품만 사용한다는 뜻이다. 즉, 관련있는 객체(모터,문)들이 제조업에 따라 같이 생성이 된다는 것이다. 팩토리 메소드 패턴은 한종류의 객체를 생성하는 롤을 갖고 있다면 추상 팩토리 패턴은 다수의 연관있는 객체들의 일련의 생성을 담당하고 있다고 보면 된다. 바로 구현해보자.

 

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
public abstract class AbstractFactory {
    
    public abstract Motor createMotor();
    public abstract Door createDoor();
    
    enum Vendor{
        LG,SAMSUNG,HUNDAI;
    }
    
    public static AbstractFactory getFactory(Vendor vendor) {
        
        AbstractFactory factory = null ;
        
        switch (vendor) {
        case LG:
            factory = LGElevatorFactory.getInstance();
            break;
        case SAMSUNG:
            factory = SamsungElevatorFactory.getInstance();
            break;
        case HUNDAI:
            factory = HundaiElevatorFactory.getInstance();
            break;
        default:
            break;
        }
        
        return factory;
        
    }
    
}
 
public class LGElevatorFactory extends AbstractFactory{
    
    private static final LGElevatorFactory FACTORY = new LGElevatorFactory();
    
    public static AbstractFactory getInstance() {
        return FACTORY;
    }
 
    @Override
    public Motor createMotor() {
        return new LGMotor();
    }
 
    @Override
    public Door createDoor() {
        return new LGDoor();
    }
    
}
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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
public abstract class Door {
    
    enum DoorStatus{
        OPEN,CLOSE;
    }
    
    public Door() {
        this.status=DoorStatus.CLOSE;
    }
    
    private DoorStatus status;
 
    public DoorStatus getStatus() {
        return status;
    }
 
    public void setStatus(DoorStatus status) {
        this.status = status;
    }
    
    public void open() {
        if(this.status.equals(DoorStatus.OPEN)) return;
        this.status=DoorStatus.OPEN;
        System.out.println("문을 엽니다.");
    }
    
    public void close() {
        if(this.status.equals(DoorStatus.CLOSE)) return;
        this.status=DoorStatus.CLOSE;
        System.out.println("문을 닫습니다.");
    }
    
}
 
public abstract class Motor {
    
    private Door door;
    private MotorStatus status;
    
    public Motor() {
        this.status=MotorStatus.STOP;
    }
    
    enum MotorStatus{
        MOVING,STOP;
    }
    
//템플릿 메소드 패턴 적용.
    public void move() {
        //모터가 이미 움직이고 있다면 아무런 행동을 하지 않는다.
        if(getStatus().equals(MotorStatus.MOVING)) return;
        //문이 열려있다면 닫는다.
        if(door.getStatus().equals(DoorStatus.OPEN)) door.close();
        doMove();
        //모터를 이동중으로 설정한다.
        setStatus(MotorStatus.MOVING);
    }
    
    /*
     * 업체마다 doMove의 행동은 다르다.
     */
    public abstract void doMove();
 
    public MotorStatus getStatus() {
        return status;
    }
 
    public void setStatus(MotorStatus status) {
        this.status = status;
    }
 
    public Door getDoor() {
        return door;
    }
 
    public void setDoor(Door door) {
        this.door = door;
    }
    
    
    
}
cs

 

각 부품에 대한 추상클래스이다. 각 제조사 부품마다 동일한 행동을 하지만 자세한 구동 프로세스가 다를 수 있으므로 모터 추상클래스는 템플릿 메소드 패턴을 이용하여 상속받게 하였다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
public class LGMotor extends Motor{
 
    @Override
    public void doMove() {
        System.out.println("LG motor 가동");
    }
    
    
}
 
public class LGDoor extends Door{
 
}
cs

 

LG모터와 문 코드이다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class AbstractFactoryMain {
 
    public static void main(String[] args) {
        
        AbstractFactory factory = AbstractFactory.getFactory(Vendor.LG);
        
        Motor motor = factory.createMotor();
        Door door = factory.createDoor();
        motor.setDoor(door);
        
        door.open();
        motor.move();
        door.open();
    }
 
}
cs

 

모터와 문에 대한 클래스의 구현체를 생각할 필요가 없어졌다. 단순히 추상 팩토리 클래스에 벤더값만 전달하면 모든 연관있는 부품에 대한 생성은 각 업체마다의 팩토리 클래스가 담당하게 된다.

 

어떻게 보면 팩토리 메소드 패턴의 확장판이라고 볼수 있다. 추상 팩토리 패턴은 일련의 관련있는 객체의 생성을 캡슐화 혹은 추상화 하기 위해 사용하는 패턴이다. 이제는 클라이언트 코드에서 업체가 바뀌어도 코드의 변경을 필요 없게 되었다. 단순히 어떠한 업체인지 벤더 값만 전달해주고 팩토리 클래스가 생성해주는 모터와 문의 구현체를 몰라도되고 단순히 사용만 하면 된다.

posted by 여성게
:

factory는 '공장'이란 뜻이고, 공장은 물건을 만드는 곳이다. 여기서 물건에 해당되는 것이 바로 인스턴스이다. factory method 패턴은 상위 클래스에서 객체를 생성하는 인터페이스를 정의하고, 하위 클래스에서 인스턴스를 생성하도록 하는 방식이다. 즉 상위 클래스에서는 인스턴스를 만드는 방법만 결정하고, 구체적인 클래스 이름은 뒤로 미룬다. 따라서 객체를 생성하는 인터페이스와 실제 객체를 생성하는 클래스를 분리할 수 있다. 예를 들어 그림처럼 추상 단계에서는 생성하려는 객체의 클래스를 정확히 지정하지 않고, concreteCreator 클래스에서 인스턴스를 생성한다.

 

 

다시 쉽게 정리하면 특정 클래스에서 사용할 클래스를 직접 new로 생성하면 강한 결합이 생기게 된다. 즉, 무엇인가 전략이 바뀔때마다 코드의 변경이 생기게 된다. 여기서 핵심은 사용할 인스턴스를 생성하는 코드를 특정 클래스가 생성하도록 함으로써 클래스 간의 결합도를 낮추게 된다는 것이다. 위의 그림에서 전략은 Product로 볼 수 있고, concreteCreator가 적절한 Product를 생성하는 역할을 담당하게 된다.

 

하나 예를 들어보자. 건물 내의 엘리베이터는 시간대에 따라 작동 방식이 달라져야한다. 오전 출근시간과 점심시간에는 많은 사람들이 엘리베이터를 사용하게 된다. 그렇다면 이 시간대에는 처리량이 극대화될 수 있는 엘리베이터 스케쥴링방식을 사용해야한다. 그 이후 시간대에는 단순히 기다리는 시간이 최소화되도록 하는 스케쥴링방식을 사용한다. 여기서 또 하나의 조건은 특정 스케쥴링 방식을 직접 선택할 수도 있어야한다.

 

아래와 같이 단순한 구현이 가능하다.

 

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
public class ElevatorManager {
    
    List<Elevator> elevators;
    
    public ElevatorManager(int count) {
        elevators = new ArrayList<>(count);
        
        IntStream.range(0, count).forEach(i->elevators.add(new Elevator()));
        
    }
    
    public void requestElevator(int destination, Scheduler schedule) {
        
        ElevatorScheduler scheduler = null ;
        
        int hour = LocalDateTime.now().getHour();
        
        if(schedule.equals(Scheduler.DYNAMIC)) {
            System.out.println("스케줄링방식 - "+schedule);
            if( (9<=hour && hour<=10|| (12<=hour && hour<=1)) {
                scheduler = new ThroughputScheduler();
            }else {
                scheduler = new ResponseTimeScheduler();
            }
        }else if(schedule.equals(Scheduler.THROUGHPUT)) {
            System.out.println("스케줄링방식 - "+schedule);
            scheduler = new ThroughputScheduler();
        }else if(schedule.equals(Scheduler.RESPONSETIME)) {
            System.out.println("스케줄링방식 - "+schedule);
            scheduler = new ResponseTimeScheduler();
        }
        
        int elevatorIndex = scheduler.selectElevator(elevatorCount());
        
        elevators.get(elevatorIndex).gotoElevator(destination, elevatorIndex);
    }
    
    private int elevatorCount() {
        return this.elevators.size();
    }
    
    enum Scheduler{
        THROUGHPUT,RESPONSETIME,DYNAMIC;
    }
}
 
public class Elevator {
    
    public void gotoElevator(int destination, int eleNum) {
        System.out.println(eleNum+"번 엘리베이터가 "+destination+"층으로 가는 중입니다.");
    }
}
 
public interface ElevatorScheduler {
    
    public int selectElevator(int count);
    
}
 
public class ThroughputScheduler implements ElevatorScheduler {
 
    @Override
    public int selectElevator(int count) {
        return (int)(Math.random()*count)+1;
    }
 
}
 
public class ResponseTimeScheduler implements ElevatorScheduler {
 
    @Override
    public int selectElevator(int count) {
        return (int)(Math.random()*count)+1;
    }
 
}
cs

 

문제가 무엇일까? 만약에 스케쥴링방식이 추가되거나 혹은 스케쥴링 선택 정책이 변경되었다면 ElevatorManager의 requestEleveator 메소드가 변경되어야 할 것이다. 즉, 특정 상황에 따라 알고리즘을 수행하게 되는 객체(스케쥴러)가 달라지기 때문에 혹은 정책이 바뀌면서 알고리즘을 수행하는 객체가 바뀌게 된다. 그렇다면 이러한 상황에서는 어떻게 하면 좋을까? 바로 팩토리 메소드 패턴이다. 팩토리 메소드 패턴으로 객체 생성을 담당하도록 별도 클래스 및 메소드로 분리하는 것이다. 그렇다면 ElevatorManager는 객체 생성을 직접적으로 신경쓸 필요가 없고 책임을 팩토리 클래스로 위임해버리는 것이다. 

 

팩토리 메소드 패턴으로 변경해보자.

 

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
public class ElevatorManager {
    
    List<Elevator> elevators;
    
    public ElevatorManager(int count) {
        elevators = new ArrayList<>(count);
        
        IntStream.range(0, count).forEach(i->elevators.add(new Elevator()));
        
    }
    
    public void requestElevator(int destination, Scheduler schedule) {
        
        Factory factory = new SchedulerFactory();
        
        ElevatorScheduler scheduler = factory.createScheduler(schedule);
        
        int elevatorIndex = scheduler.selectElevator(elevatorCount());
        
        elevators.get(elevatorIndex).gotoElevator(destination, elevatorIndex);
    }
    
    private int elevatorCount() {
        return this.elevators.size();
    }
    
    enum Scheduler{
        THROUGHPUT,RESPONSETIME,DYNAMIC;
    }
}
 
public abstract class Factory {
    
    public abstract ElevatorScheduler createScheduler(Scheduler scheduler);
    
}
 
public class SchedulerFactory extends Factory{
 
    @Override
    public ElevatorScheduler createScheduler(Scheduler schedule) {
        
        ElevatorScheduler scheduler = null ;
        
        int hour = LocalDateTime.now().getHour();
        
        if(schedule.equals(Scheduler.DYNAMIC)) {
            System.out.println("스케줄링방식 - "+schedule);
            if( (9<=hour && hour<=10|| (12<=hour && hour<=1)) {
                scheduler = new ThroughputScheduler();
            }else {
                scheduler = new ResponseTimeScheduler();
            }
        }else if(schedule.equals(Scheduler.THROUGHPUT)) {
            System.out.println("스케줄링방식 - "+schedule);
            scheduler = new ThroughputScheduler();
        }else if(schedule.equals(Scheduler.RESPONSETIME)) {
            System.out.println("스케줄링방식 - "+schedule);
            scheduler = new ResponseTimeScheduler();
        }
        
        return scheduler;
        
    }
    
    
}
cs

 

위와 같이 코드를 변경하였다. 이제 ElevatorManager는 자신의 일만 신경쓰면 된다. 스케쥴러 선택은 팩토리 클래스에게 맡겨버리면 되기 때문이다. 혹시나 스케쥴링 정책 혹은 새로운 스케쥴링 방식이 생겨나도 다른 곳은 아무곳도 신경쓸 필요가 없다. 단순히 팩토리 클래스에만 변경해주면 되기 때문이다. 

 

다시 한번 팩토리 메서드 패턴의 정의를 보자. 

 

객체를 생성하는 코드를 별도의 클래스/메서드로 분리함으로써 객체 생성 방식의 변화에 대비하는데 유용한 패턴이다.
posted by 여성게
:

decoration은 '장식(포장)'이란 뜻이다. 빵집에서 케이크를 만들 때 먼저 둥근 모양의 빵을 만든다. 이 위에 초콜릿을 바르면 초콜릿 케이크가 되고, 치즈를 바르면 치즈 케이크가 된다. 또 생크림을 바르고 과일을 많이 올려놓으면 과일 생크림 케이크가 된다.

 

이처럼 기존에 구현되어 있는 클래스(둥근 모양의 빵)에 그때그때 필요한 기능(초콜릿, 치즈, 생크림)을 추가(장식, 포장)해나가는 설계 패턴을 decorator 패턴이라고 한다. 이것은 기능 확장이 필요할 때 상속의 대안으로 사용한다.

그림에서 decorator 클래스가 기존에 구현되어 있는 클래스(둥근 모양의 빵)에 해당되고, concreteDecorator클래스는 그때그때 필요한 기능(초콜릿, 치즈, 생크림)을 추가(장식, 포장)해나가는 것에 해당된다.

 

카페 음료 가격을 계산하기 위해 우리는 클래스를 설계해야 한다고 하자. 보통 가장 많이 생각할 수 있는 방안이 상속을 이용한 구현이다. 아래와 같이 쉽게 구현할 수 있다.

 

기본적으로 커피를 제조할때 에스프레소를 내리고 해당 에스프레소에 다른 첨가물을 첨가하여 커피를 제조한다. 예를 들어 카페모카는 에스프레소에 우유,초코가 들어가므로 에스프레소 가격(2)+우유,초코(2)로 총 4라는 가격이 책정된다고 생각해보자. 그렇다면 상속을 이용하여 아래와 같이 구현할 수 있다.

 

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
public abstract class Beverage {
    
    private String description;
    
    public String getDescription() {
        return description;
    }
    
    public void setDescription(String description) {
        this.description = description;
    }
 
    public abstract int cost();
}
 
public class Espresso extends Beverage{
    
    public Espresso() {
        super.setDescription("에스프레쏘");
    }
 
    @Override
    public int cost() {
        return 2;
    }
    
}
 
public class CafeMocha extends Beverage{
    
    public CafeMocha() {
        super.setDescription("카페모카");
    }
    
    @Override
    public int cost() {
        return 2+2;
    }
    
    
}
cs

 

위와 같이 새로운 음료가 추가되었으므로, 새로운 클래스가 추가된다. 만약 휘핑크림이 추가된 카페모카라면? 아래와 같이 추가적인 클래스가 생성되어야 할 것이다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
public class CafeMochaWithWhip extends Beverage{
 
    public CafeMochaWithWhip() {
        super.setDescription("카페모카 휘핑크림 추가");
    }
    
    @Override
    public int cost() {
        return 2+2+1;
    }
    
    
}
cs

 

얼핏보면 괜찮은 것 같지만, 만약 음료의 종류가 엄청나게 많고 선택옵션이 엄청나게 많다면? 클래스수는 기하급수적으로 늘어날 것이다. 이것은 역시 디자인 원칙중 OCP를 위반하고 있는 것이다.(개방-폐쇄 원칙)

 

이러한 상황에서 사용하는 디자인 패턴이 데코레이터 패턴이다!

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public abstract class Beverage {
    
    private String description;
    
    public String getDescription() {
        return description;
    }
    
    public void setDescription(String description) {
        this.description = description;
    }
 
    public abstract int cost();
}
cs

 

데코레이터 패턴은 구성이라는 방법을 사용한다. 즉, 추상 타입을 가지는 데코레이터들 혹은 기본구성 클래스들이어야만 데코레이터를 이용할 수 있다. 왜냐하면 뒤에서 설명하겠지만 데코레이터 역할을 클래스들이 모두 추상타입의 구현체들을 감싸게 되는 구조이기 때문이다. 즉, 위 추상클래스는 기본구성(에스프레쏘) 그리고 데코레이터(선택옵션,모카,휘핑이 들어간 모카 등)들의 타입을 마춰주기 위한 추상타입의 역할을 할 것이다.

 

1
2
3
4
5
6
7
8
9
10
11
12
public class Espresso extends Beverage{
    
    public Espresso() {
        super.setDescription("에스프레쏘");
    }
 
    @Override
    public int cost() {
        return 2;
    }
    
}
cs

 

추상타입의 Beverage를 상속하는 기본구성을 구현한 예제이다. 데코레이터 패턴의 핵심은 기본 구성으로부터 출발하여 이것저것 데코레이터 옵션을 덧붙여나가는 것이다. 즉, 데코레이터 패턴의 시작이 되는 기본구성은 바로 추상타입의 Beverage를 상속받아 구현한다.

 

1
2
3
public abstract class BeverageDecorator extends Beverage {
    
}
cs

 

데코레이터(추가옵션)들이 상속하게 될 추상클래스이다. 데코레이터들의 모든 타입 그리고 기본구성과 데코레이터들의 타입이 동일해야하므로 Beverage를 상속한다. 만약 데코레이터들의 추가적인 행위를 구현하려면 이쪽에 추상메소드로 선언해도 무방할듯하다.

 

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
public class Mocha extends BeverageDecorator{
 
    private Beverage beverage;
    
    public Mocha(Beverage beverage) {
        this.beverage=beverage;
    }
    
    @Override
    public int cost() {
        return 2+this.beverage.cost();
    }
    
}
 
public class Whipping extends BeverageDecorator{
    
    private Beverage beverage;
    
    public Whipping(Beverage beverage) {
        this.beverage=beverage;
    }
    
    @Override
    public int cost() {
        return 1+this.beverage.cost();
    }
    
}
cs

 

데코레이터 패턴의 핵심인듯하다. 데코레이터 객체들의 구현체이다. 잘보면 기본구성 혹은 데코레이터 객체들을 래핑하기 위해 하나의 인스턴스 변수를 가지고 있다는 것이 핵심이며 기본구성과 동일한 행위(가격계산,cost())를 오버라이드하여 구현하였으며 래핑한 객체의 동일한 행위 메소드를 호출하고 있는 것을 볼 수 있다. 이렇게 기본구성과 데코레이터 객체들의 상위타입을 마춰줌으로써 계속해서 생성자로 객체를 래핑하여 옵션을 추가 할 수 있다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class DecoratorMain {
 
    public static void main(String[] args) {
        
        Beverage espresso = new Espresso();
        System.out.println("description : "+espresso.getDescription() +" cost : "+espresso.cost());
        
        Beverage mocha = new Mocha(new Espresso());
        mocha.setDescription("카페모카");
        System.out.println("description : "+mocha.getDescription() +" cost : "+mocha.cost());
        
        Beverage mochaWithWhip = new Whipping(new Mocha(new Espresso()));
        mochaWithWhip.setDescription("카페모카 휘핑추가");
        System.out.println("description : "+mochaWithWhip.getDescription() +" cost : "+mochaWithWhip.cost());
        
    }
 
}
 
=>result
description : 에스프레쏘 cost : 2
description : 카페모카 cost : 4
description : 카페모카 휘핑추가 cost : 5
 
cs

 

메인클래스에서 데코레이터 패턴이 적용된 클래스를 사용하고 있는 것을 보니 많이 낯이 익다. 무엇일까? 바로 Java I/O들을 구현할 때, 데코레이터 패턴이 적용되었다! 

 

InputStream is = new BufferedInputStream(new FileInputStream("filepath"));

 

지금까지 간단히 데코레이터 패턴을 다루어보았다.

posted by 여성게
:
Web/Spring 2019. 8. 17. 19:31

 

스프링에서 빈을 생성할 때, 기본 전략은 모든 빈이 싱글톤으로 생성된다. 즉, 어디에서든지 빈을 주입받는 다면 동일한 빈을 주입받는 것을 보장한다. 하지만 필요에 따라서 빈 주입마다 새로운 빈을 생성해야할 필요가 있을 경우도 있다. 이럴 경우에는 빈 생성시 Scope를 prototype으로 주면 빈 주입마다 새로운 인스턴스가 생성되는 것을 보장한다. 하지만 프로토타입 빈을 사용할 경우 주의해야 할 상황이 있다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Component
public class ABean {
    
    @Autowired
    private BBean b;
 
    public void bMethod() {
        b.print();
    }
    
}
 
@Component
@Scope("prototype")
public class BBean {
    
    public void print() {
        System.out.println("BBean !");
    }
}
cs

 

이런식으로 사용한다면 어떻게 될까? 이것은 사실 프로토타입을 쓰나마나이다. 싱글톤으로 생성된 A빈에 프로토타입 B빈을 주입해봤자 A빈은 더이상 생성되지 않기 때문에 항상 동일한 B빈을 사용하게 되는 것이다.

 

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
@Component
public class ABean {
    
    @Autowired
    private BBean b;
    
    public void print() {
        System.out.println(b.hashCode());
    }
    
}
 
@Component
@Scope("prototype")
public class BBean {
    
}
 
@Component
public class CBean {
    
    @Autowired
    private ABean a;
    
    public void print() {
        a.print();
    }
}
 
@Component
public class DBean {
    
    @Autowired
    private ABean a;
    
    public void print() {
        a.print();
    }
}
 
@SpringBootApplication
public class CircularReferenceApplication implements CommandLineRunner{
    
    @Autowired private CBean c;
    @Autowired private DBean d;
 
    public static void main(String[] args) {
        SpringApplication.run(CircularReferenceApplication.class, args);
    }
 
    @Override
    public void run(String... args) throws Exception {
        c.print();
        d.print();
    }
    
}
cs

 

위 코드를 실행시켜보자. 프로토타입 빈으로 등록된 B빈이지만 항상 어디서든 동일한 해시코드를 반환한다. 즉, 프로토타입빈을 사용하기 위해서는 아래와 같이 사용하자.

 

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
@Service
public class BeanUtil implements ApplicationContextAware {
 
    private static ApplicationContext context;
    
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        context = applicationContext;
    }
 
    public static <T> T getBean(Class<T> beanClass) {
        return context.getBean(beanClass);
    }
}
 
@Component
public class ABean {
    
    public void print() {
        BBean b = BeanUtil.getBean(BBean.class);
        System.out.println(b.hashCode());
    }
    
}
 
cs

 

ApplicationContext 객체에서 직접 빈을 가져와서 메소드 내부에서 사용하도록 하자. 이제는 매번 다른 B빈의 해시코드를 반환할 것이다.

 

2019/02/25 - [Web/Spring] - Spring - ApplicationContext,ApplicationContextAware, 빈이 아닌 객체에 빈주입할때!

 

Spring - ApplicationContext,ApplicationContextAware, 빈이 아닌 객체에 빈주입할때!

Spring - ApplicationContext,ApplicationContextAware, 빈이 아닌 객체에 빈주입할때! @Autuwired,@Inject 등의 어노테이션으로 의존주입을 하기 위해서는 해당 객체가 빈으로 등록되어 있어야만 가능하다. 사실..

coding-start.tistory.com

 

posted by 여성게
:
Web/Spring 2019. 8. 17. 18:47

 

오늘 포스팅할 내용은 필드,생성자,세터 의존주입에 대한 내용이다. 우리가 보통 생각하는 의존주입은 무엇인가? 혹은 우리가 평소에 사용하는 의존주입의 방식은 무엇인가? 한번 생각해보고 각각에 대한 내용을 다루어보자.

 

<Field Injection>

 

1
2
3
4
5
6
7
8
9
10
11
@Component
public class ABean {
    
    @Autowired
    private BBean b;
    
    public void bMethod() {
        b.print();
    }
    
}
cs

 

보통 위와 같이 필드에 의존주입할 빈을 선언하고 @Autowired를 붙여 빈 주입을 한다.

 

<Constructor Injection>

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Component
public class ABean {
    
    private BBean b;
    
    public ABean(BBean b) {
        this.b=b;
    }
    
    public void bMethod() {
        b.print();
    }
    
}
cs

 

생성자를 위한 빈 주입은 위와 같이 생성자의 매개변수로 의존 주입할 빈을 매개변수로 넣어준다. 스프링 4.3 버전 이후로는 생성자 의존주입에 @Autowired를 넣을 필요는 없다.

 

<Setter Injection>

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Component
public class ABean {
    
    private BBean b;
    
    @Autowired
    public void setB(BBean b) {
        this.b = b;
    }
 
    public void bMethod() {
        b.print();
    }
    
}
cs

 

세터를 이용한 빈주입이다. 의존 주입할 빈 객체에 대한 Setter를 만들어주고 @Autowired를 붙여준다.

 

지금까지 3개의 의존주입 방법을 다루어보았다. 아직은 똑같은 결과물을 내는 다른 방법이라는 것만 느껴진다. 그렇다면 특정 상황을 연출해보자. 바로 Circular Reference(순환참조)인 경우이다. 3가지 의존주입 방법을 모두 활용하여 순환참조 상황을 재연해보자.

 

순환참조

-A빈이 있고 B빈이 있는데, 각각 서로가 서로를 참조하고 있는 상황에서 발생한다. 이러한 상황에서 A빈이 메모리에 올라가기 전에 B빈이 A빈을 의존주입하는 상황이나 혹은 그 반대의 경우 문제가 발생한다.

 

<Field Injection>

 

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
@Component
public class ABean {
    
    @Autowired
    private BBean b;
 
    public void bMethod() {
        b.print();
    }
    
    public void print() {
        System.out.println("ABean !");
    }
    
}
 
@Component
public class BBean {
    
    @Autowired
    private ABean a;
    
    public void aMethod() {
        a.print();
    }
    
    public void print() {
        System.out.println("BBean !");
    }
}
 
@SpringBootApplication
public class CircularReferenceApplication implements CommandLineRunner{
    
    @Autowired
    private ABean a;
    @Autowired
    private BBean b;
 
    public static void main(String[] args) {
        SpringApplication.run(CircularReferenceApplication.class, args);
    }
 
    @Override
    public void run(String... args) throws Exception {
        a.bMethod();
        b.aMethod();
    }
    
}
 
cs

 

위는 순환참조 상황을 필드 의존주입으로 재연해본 것이다. 과연 결과를 어떻게 나올 것인가?

 

<Constructor Injection>

 

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
@Component
public class ABean {
    
    private BBean b;
    
    public ABean(BBean b) {
        this.b=b;
    }
 
    public void bMethod() {
        b.print();
    }
    
    public void print() {
        System.out.println("ABean !");
    }
    
}
 
@Component
public class BBean {
    
    private ABean a;
    
    public BBean(ABean a) {
        this.a=a;
    }
    
    public void aMethod() {
        a.print();
    }
    
    public void print() {
        System.out.println("BBean !");
    }
}
 
@SpringBootApplication
public class CircularReferenceApplication implements CommandLineRunner{
    
    @Autowired
    private ABean a;
    @Autowired
    private BBean b;
 
    public static void main(String[] args) {
        SpringApplication.run(CircularReferenceApplication.class, args);
    }
 
    @Override
    public void run(String... args) throws Exception {
        a.bMethod();
        b.aMethod();
    }
    
}
cs

 

순환참조 상황을 생성자 의존주입으로 재연해보았다. 이 또한 어떠한 결과가 발생할까?

 

<Setter Injection>

 

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
@Component
public class ABean {
    
    private BBean b;
    
    @Autowired
    public void setB(BBean b) {
        this.b = b;
    }
 
    public void bMethod() {
        b.print();
    }
    
    public void print() {
        System.out.println("ABean !");
    }
    
}
 
@Component
public class BBean {
    
    private ABean a;
    
    @Autowired
    public void setA(ABean a) {
        this.a = a;
    }
 
    public void aMethod() {
        a.print();
    }
    
    public void print() {
        System.out.println("BBean !");
    }
}
 
@SpringBootApplication
public class CircularReferenceApplication implements CommandLineRunner{
    
    @Autowired
    private ABean a;
    @Autowired
    private BBean b;
 
    public static void main(String[] args) {
        SpringApplication.run(CircularReferenceApplication.class, args);
    }
 
    @Override
    public void run(String... args) throws Exception {
        a.bMethod();
        b.aMethod();
    }
    
}
cs

 

마지막으로 세터 의존주입을 이용하여 순환참조 상황을 재연하였다. 3가지 상황에서의 각각의 결과는 어떻게 될까?

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
=>Field Injection
BBean !
ABean !
 
=>Constructor Injection
***************************
APPLICATION FAILED TO START
***************************
 
Description:
 
The dependencies of some of the beans in the application context form a cycle:
 
   circularReferenceApplication (field private com.example.demo.ABean com.example.demo.CircularReferenceApplication.a)
┌─────┐
|  ABean defined in file [/Users/yun-yeoseong/eclipse-study/circular-reference/target/classes/com/example/demo/ABean.class]
↑     ↓
|  BBean defined in file [/Users/yun-yeoseong/eclipse-study/circular-reference/target/classes/com/example/demo/BBean.class]
└─────┘
 
=>Setter Injection
BBean !
ABean !
cs

 

결과는 위와 같다. 어떻게 된것일까? 다 같은 결과를 내는 의존주입인데, 단 하나 생성자 의존주입에서는 애플리케이션이 기동되지 못하고 순환참조 관련 예외가 발생하였다. 이유는 3가지의 객체의 라이프사이클을 떠올려보자. 필드,세터 의존주입은 필드&세터 메소드를 이용하여  의존주입을 하게 된다. 그렇다면 전제가 무엇일까? 바로 해당 객체가 메모리에 적재된 후에 빈을 주입하게 되는 것이다. 그렇다면 생성자 의존주입은 어떨까? 생성자 의존주입은 객체를 생성자로 생성하는 시점에 필요한 빈들을 의존주입한다. 즉, 객체를 생성하는 동시에 빈을 주입하는 것 그리고 객체를 이미 생성한 이후에 빈을 주입하는 것의 차이가 되는 것이다. 위에서 순환참조에 대해 간단히 설명을 하였다. 다시 한번 떠올려보자. 

 

1
2
3
4
5
┌─────┐
|  ABean defined in file [/Users/yun-yeoseong/eclipse-study/circular-reference/target/classes/com/example/demo/ABean.class]
↑     ↓
|  BBean defined in file [/Users/yun-yeoseong/eclipse-study/circular-reference/target/classes/com/example/demo/BBean.class]
└─────┘
cs

 

위와 같은 예외가 발생한 이유는 A는 B를 필요로하고 B도 A를 필요로 할때 발생하는 문제인데, 단순 서로를 참조하기 때문의 문제가 아니라 서로 참조하는 객체가 생성되지도 않았는데, 그 빈을 참조하기 때문에 발생하는 예외인 것이다. 즉, 순환참조는 당연히 생성자 의존주입에서만 문제가 될 수 밖에 없는 것이다. 생성자 의존주입은 빈 주입을 객체 생성시점에 주입하기 때문이다.

 

여기서 생각해 볼 것이 있다. 그러면 순환참조를 피하기 위해 필드 혹은 세터 의존주입을 사용해야 되는 것인가? 답은 아니다. 순환참조를 유발하는 객체 설계 자체가 잘못 설계된 객체임을 생각해볼 수 있다. 순환참조를 필드,세터 의존주입으로 피하는 것은 단순히 잘못 설계된 객체를 억지로 문제를 회피하여 사용하는 것은 아닌가 생각해볼 필요가 있다는 것이다.

 

객체지향 설계에서 객체의 의존에 순환관계가 있다면 잘못 설계된 객체인지 살펴볼 필요가 있다. 아니다. 왠만하면 리팩토링하자 !

 

<Field Injection vs Constructor Injection>

 

1.단일 책임의 원칙 위반 
의존성을 주입하기가 쉽다. @Autowired 선언 아래 3개든 10개든 막 추가할 수 있으니 말이다. 여기서 Constructor Injection을 사용하면 다른 Injection 타입에 비해 위기감 같은 걸 느끼게 해준다. Constructor의 파라미터가 많아짐과 동시에 하나의 클래스가 많은 책임을 떠안는다는 걸 알게된다. 이때 이러한 징조들이 리팩토링을 해야한다는 신호가 될 수 있다. 

 

2.의존성이 숨는다.
DI(Dependency Injection) 컨테이너를 사용한다는 것은 클래스가 자신의 의존성만 책임진다는게 아니다. 제공된 의존성 또한 책임진다. 그래서 클래스가 어떤 의존성을 책임지지 않을 때, 메서드나 생성자를 통해(Setter나 Contructor) 확실히 커뮤니케이션이 되어야한다. 하지만 Field Injection은 숨은 의존성만 제공해준다.

 

3.DI 컨테이너의 결합성과 테스트 용이성
DI 프레임워크의 핵심 아이디어는 관리되는 클래스가 DI 컨테이너에 의존성이 없어야 한다. 즉, 필요한 의존성을 전달하면 독립적으로 인스턴스화 할 수 있는 단순 POJO여야한다. DI 컨테이너 없이도 유닛테스트에서 인스턴스화 시킬 수 있고, 각각 나누어서 테스트도 할 수 있다. 컨테이너의 결합성이 없다면 관리하거나 관리하지 않는 클래스를 사용할 수 있고, 심지어 다른 DI 컨테이너로 전환할 수 있다. 
하지만, Field Injection을 사용하면 필요한 의존성을 가진 클래스를 곧바로 인스턴스화 시킬 수 없다.

4.불변성(Immutability)
Constructor Injection과 다르게 Field Injection은 final을 선언할 수 없다. 그래서 객체가 변할 수 있다.

5.순환 의존성
Constructor Injection에서 순환 의존성을 가질 경우 BeanCurrentlyCreationExeption을 발생시킴으로써 순환 의존성을 알 수 있다.

 

<Setter Injection vs Construct Injection>

 

1.Setter Injection

Setter Injection은 선택적인 의존성을 사용할 때 유용하다. 상황에 따라 의존성 주입이 가능하다. 스프링 3.x 다큐멘테이션에서는 Setter Injection을 추천했었다.


2.Constructor Injection
Constructor Injection은 필수적인 의존성 주입에 유용하다. 게다가 final을 선언할 수 있으므로 객체가 불변하도록 할 수 있다. 또한 위에서 언급했듯이 순환 의존성도 알 수 있다. 그로인해 나쁜 디자인 패턴인지 아닌지 판단할 수 있다. 
스프링 4.3버전부터는 클래스를 완벽하게 DI 프레임워크로부터 분리할 수 있다. 단일 생성자에 한해 @Autowired를 붙이지 않아도 된다.(완전 편한데?!) 이러한 장점들 때문에 스프링 4.x 다큐멘테이션에서는 더이상 Setter Injection이 아닌 Constructor Injection을 권장한다. 굳이 Setter Injection을 사용한다면, 합리적인 디폴트를 부여할 수 있고 선택적인(optional) 의존성을 사용할 때만 사용해야한다고 말한다. 그렇지 않으면 not-null 체크를 의존성을 사용하는 모든 코드에 구현해야한다.

 

posted by 여성게
: