Properties, Interface를 이용하여 IoC를 구현한 HelloWorld

아직 책을 다 보지는 않았지만, 이 책에서 가장 좋은 예제는 이 Hello Wolrd! 예제 인 것 같다. 단순한 System.out.println에서의 문제점을 분석하여

인터페이스를 통해 의존성 감소 > FWK 없이 DI패턴을 구현 > Spring을 이용하여 DI패턴 구현 의 과정을 차근히 살펴 볼 수 있다.

아마도, JAVA 초급자(혹은 오래동안 사용했더라도 OOP 개념부족)는 이해할 수 없는 부분(Factory패턴, Properties 사용)이 조금 포함되어 있지만 구구절절 이러한 설명일 생략한 부분이 더욱 마음에 든다. DI와 IoC 개념에 대해서 빠르게 이해할 수 있는 좋은 예제! 

 

예제 'Hello World!' 어플리케이션 개발


요구조건

  1. 메시지 render(출력)하는 기능과 메시지를 provider(제공) 하는 기능 분리  (의존성감소)
  2. render와 provider를 유연하게(소스코드 수정 없이) 변경가능 하여야 함 (IoC and DI)

 

전형적인 Hello World! 예제

[helloworld main]

1
2
3
4
5
public class HelloWorld {    
    public static void main(String[] args) {
            System.out.println("Hello Wolrd");
    }
}

cs


결함내역 : 요구조건 1, 2를 모두 만족하지 못 함.





arguments를 이용한 Hello Wolrd! 예제

[helloworld main]

1
2
3
4
5
6
7
8
9
public class HelloWorld {    
    public static void main(String[] args) {
        if(args.length > 0) {
            System.out.println(args[0]);
        } else {
            System.out.println("Hello Wolrd");
        }
    }
}

cs

 

개선사항 : argument를 이용하여 메시지 내용을 외부화

결함내역 : 요구조건 1, 2를 모두 만족하지 못 함.

* 아직까지 main()에서 메시지 조회와 출력을 모두 담당하고 있는 구조. 기능 분리가 필요 함.






Interface를 이용하여 Provider, Render구현 한 Hello World! 예제

[provider interface]

1
2
3
4
5
package com.ljs.provider;
 
public interface IMessageProvider {
    public String getMessage();
}

cs

 

 [provider interface implements]

1
2
3
4
5
6
7
8
9
10
11
12
package com.ljs.provider.impl;
 
import com.ljs.provider.IMessageProvider;
 
public class StandardMessageProvider implements IMessageProvider {
 
    @Override
    public String getMessage() {
        return "Hello World";
    }
 
}

cs


[render interface]

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.ljs.render;
 
import com.ljs.provider.IMessageProvider;
 
public interface IMessageRender {
 
    // message를 출력하는 기능 
    public void Render();
    
    // message provider에 관한 getter setter
    public void setMessageProvider(IMessageProvider provider);
    public IMessageProvider getMessageProvider();
}

cs


[render interface implements]

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
package com.ljs.render.impl;
 
import com.ljs.provider.IMessageProvider;
import com.ljs.render.IMessageRender;
 
public class StandardMessageRender implements IMessageRender {
    
    private IMessageProvider provider = null;
    
    @Override
    public void Render() {
        if(provider == null) {
            throw new RuntimeException("message provider를 지정한 이후에 호출하세요 : "
                + StandardMessageRender.class.getName());
        } else {
            System.out.println(provider.getMessage());
        }
    }
 
    @Override
    public void setMessageProvider(IMessageProvider provider) {
        this.provider = provider;
 
    }
 
    @Override
    public IMessageProvider getMessageProvider() {
        return this.provider;
    }
 
}

cs

 

* getter setter가 정의되어 있음을 보면 render interface를 구현하는 클래스는 맴버변수로 provider를 갖는 것을 알 수 있다.

파라메터 및 리턴타입으로 StandardmessageProvider가 아니라 IMessageProvider를 선언 한 게 중요하다.

이로 인해 추후에 IMessageProvider를 구현하는 어떤 class도 ImessageRender에서 이용 가능(의존성 감소 효과)


[HelloWorld main]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import com.ljs.provider.IMessageProvider;
import com.ljs.provider.impl.StandardMessageProvider;
import com.ljs.render.IMessageRender;
import com.ljs.render.impl.StandardMessageRender;
 
 
public class HelloWorld {    
    public static void main(String[] args) {
        IMessageRender      renderer     = new StandardMessageRender();        // IMessageRender 생성
        IMessageProvider  provider     = new StandardMessageProvider();    // IMessageProvider 생성
        renderer.setMessageProvider(provider);    // render에 provider setting
        renderer.render();    // render 수행
    }
}

cs

 


개선사항 : Interface를 사용하여 의존성 감소-> 추후에 render와 provider 변경 수월

결함내역 :

  1. 메시지 render 기능과 provider 기능 분리 (의존성감소)    (요구사항만족)
  2. 소스코드 수정없이 render와 provider 변경 (IoC and DI

interface구현으로 어플리케이션의 의존성은 감소하여 1번 요구사항은 만족

하지만, MessageRender와 MessageProvider의 인터페이스 구현체를 바꾸려면 매번 코드를 변경하여야 하는 문제가 남아 아직 2번 요구사항은 만족하지 못하였다. (10번 라인과 11번 라인)

 





Properties사용으로 설정파일 외부화 하여 동적 instance생성 예제

* 바로 위에 작성된 예제에서, MessageSupportFactory 클래스 추가, HelloWorld 클래스 main 메소드만 변경 됨.


[config.properties]

render.class=com.ljs.render.impl.StandardMessageRender

provider.class=com.ljs.provider.impl.StandardMessageProvider


[MessageSupportFactory]

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
package com.ljs.message;
 
import java.io.FileInputStream;
import java.util.Properties;
import com.ljs.provider.IMessageProvider;
import com.ljs.render.IMessageRender;
 
public class MessageSupportFactory {
    
    private static MessageSupportFactory instance = null;
    private Properties             props = null;
    private IMessageRender         renderer = null;
    private IMessageProvider    provider = null;
 
    static {
        instance = new MessageSupportFactory();
    }
    
    private MessageSupportFactory() {
        props = new Properties();
        
        try {
            // load properties file
            props.load(new FileInputStream("src/config/config.properties"));
            
            // load된 프로퍼티 파일에 지정된 클래스로 구현체 할당
            // 설정파일 외부화를 통해 IOC구현. DI구현 (런타임시점에서 의존성 결정 : 컴파일 타임에서는 알 수 없음)
            renderer = (IMessageRender) Class.forName(props.getProperty("render.class")).newInstance();
            provider = (IMessageProvider) Class.forName(props.getProperty("provider.class")).newInstance();
            
        } catch (Exception e) {
            e.printStackTrace();
        }
        
    }
    
    // single tone 패턴 Fatory instace는 1개만 생성 되도록
    public static MessageSupportFactory getInstance() {
        return instance;
    }
    
    // factory 패턴으로 관리하는 bean을 리턴
    public IMessageRender getRenderer() {
        return renderer;
    }
 
    public IMessageProvider getProvider() {
        return provider;
    }    
}

cs


[HelloWorld main]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import com.ljs.message.MessageSupportFactory;
import com.ljs.provider.IMessageProvider;
import com.ljs.provider.impl.StandardMessageProvider;
import com.ljs.render.IMessageRender;
import com.ljs.render.impl.StandardMessageRender;
 
public class HelloWorld {    
    public static void main(String[] args) {
        /*
        IMessageRender      renderer     = new StandardMessageRender();      // IMessageRender 생성
        IMessageProvider  provider     = new StandardMessageProvider();    // IMessageProvider 생성
        */
        
        // use factory pattern
        IMessageRender      renderer = MessageSupportFactory.getInstance().getRenderer();
        IMessageProvider  provider = MessageSupportFactory.getInstance().getProvider();
        renderer.setMessageProvider(provider);    // render에 provider setting
        renderer.render();    // render 수행
    }
}

cs


개선사항 : 

  1. Interface를 사용하여 의존성 감소-> 추후에 render와 provider 변경 수월
  2. properties파일을 사용하여 구현체를 생성 -> 의존성관리 외부화, 소스코드 변경없이 properties 변경으로 인스턴스 변경가능

 

결함내역 :

  1. 메시지 render 기능과 provider 기능 분리 (의존성감소)    (요구사항만족)
  2. 소스코드 수정없이 render와 provider 변경 (IoC and DI) (요구사항만족)

요구사항도 모두 만족하였고, 인터페이스를 통해서 유연한 구조를 갖추게 되었다. 구현체DI 부분은 외부 properties 파일로 관리하여 추후에는 properties 파일 내용만 바꾸게 되면 반영된다. 단순히 helloworld가 이렇게 까지 구조를 갖추어 발전할 수 있다는게 되게 놀랍다.


책에서는 이 단계에서 남은 문제는 아래 두 가지 라고 말한다.

  1. 컴포넌트의 느슨한 연결구조 때문에 어플리케이션 각 부분을 연결하는 접착코드가 많은 점.
  2. MessageRender 구현체는 MessageProvider 인스턴스를 통해 직접 제공해야 한다는 점. 

(2번 항목이 조금 이해가 안되지만, 아마도 Render 인스턴스를 생성후 setProvider를 하는 과정을 말하는 것 같다.)

이러한 문제는 다음 포스팅에서 helloWorld를 스프링으로 구성해보고 해결하여 보기로 한다.



다운로드


'프로그래밍 > Spring FWK' 카테고리의 다른 글

Spring Setter DI  (0) 2015.12.13
Spring Bean생성 XML, Annotation예제  (0) 2015.12.13
IoC in Spring : ApplicationContext  (0) 2015.12.13
IoC in Spring : BeanFactory  (0) 2015.12.13
Spring IoC  (0) 2015.12.13
Spring DI가 적용된 HelloWorld  (0) 2015.12.13
Spring설치, CORE Jar파일, 서드파티 Jar 파일 용도설명  (0) 2015.12.13
Spring 소개  (0) 2015.12.13

이 글을 공유하기

댓글

Email by JB FACTORY