코딩의 시작은 객체의 생성 부터 입니다. 객체의 생성은 디자인패턴에서 생성패턴, 구조패턴, 행위패턴(Creational, Structural, Behavioral Patterns) 중 첫번째에 해당하는 것입니다. 생성패턴에 해당합니다. 개발 과정에서 객체의 생성은 매우 빈번하게 발생하며, 상태를 지정하고 변경하고 사용하는 방법을 디자인 하는 것은 매우 중요합니다. 객체지향 5원칙을 준수하는 생성자를 디자인하고 사용하는 방법에 대해서 다룹니다.

 

생성자를 디자인하다.

모든 시작은 생성자의 디자인으로 부터... 출처: 위키피디아, 천지창조 Creation Of Adam, 미켈란젤로


생성자의 책임

생성자는 최대한 가용상태임을 유지해야 하는데, 아래의 2가지 책임을 가집니다. 
1. 인스턴스를 생성한다. 
2. 인스턴스가 잘 못된 상태에 있지 않도록 초기화 되어야 할 항목을 사용자에게 알린다.


 

생성자 Constructor


생성자는 타입(type) 생성자 와 인스턴스(instance) 생성자 두가지가 있습니다.
public class Customer {
public Customer() { ... } // instance constructor
static Customer() { ... } // type constructor
}
타입 생성자는 static 이며 타입이 사용되기 전에 CLR(Common Language Runtime) 에서 실행 되며, 인스턴스 생성자는 타입의 인스턴스가 만들어 질때 실행 됩니다. 타입 생성자는 파라미터를 가질 수 없고 인스턴스 생성자는 가질 수 있습니다.


 

생성자의 디자인


1. 가능한 단순하게 하라. 이상적으로는 default 생성자를 제공한다. 
2. static factory method 형태를 제공하라. 이는 Loose Coupling을 지원한다. 
3. 메인 속성(Property)는 생성자 파라미터로 전달하라. 
4. 파라미터 이름은 속성이름과 동일하게 하라. 
5. 가장 최소한의 일을 하도록 하라. 가능한 파라미터를 속성에 할당하는 일만 하라.
6. 초기화에 문제가 있다면 생성자에서 예외를 발생시켜라. 생성자 범위를 벗어나면 잘 못 생성된 인스턴스가 사용될수 있는 상태에 놓여진다. 
7. default 생성자가 필요하다면 명시적으로 선언하라. default 생성자는 명시적으로 선언하지 않더라도 컴파일러가 암시적으로 만들어 놓는다. 파라미터가 있는 생성자를 나중에 추가 한다면 컴파일러는 암시적 default 생성자를 추가하지 않게되고, 이는 잠재적인 문제를 발생시킬 수 있다. 
8. 생성자 내부에서 virtual 멤버를 호출하지 마라. base 클래스의 생성자가 먼저 호출 되기 때문에 잘못된 초기화가 일어날 수 있다. virtual 메소드의 호출은 override 메소드가 담당하도록 해야 한다.


 

타입 생성자 디자인


타입 생성자란 타입을 초기화 하기 위한 static 생성자를 말합니다. 타입이 최초 사용되어 질때 CLR에 의해 실행됩니다. 
1. static 생성자는 private 으로 선언해야 한다. 타입 생성자는 CLR 이외의 곳에서 사용되지 않도록 설계 되어 있다. 
2. static 생성자에서는 예외를 던져서는 안된다. 현재 어플리케이션 도메인 내에서 의미가 없다. 
3. static fields 에 대한 초기화는 inline에서 수행하라. 생성자 내부에서 초기화 하는 field는 런타임이 최적화를 수행하지 못한다.


 

의존 주입 Dependancy Injection 관점으로 인스턴스 초기화 설명하기

(여기서 말하는 초기화는 일반적인 초기화 및 사용시의 가용상태를 만드는 것을 포함합니다.)

 

 

생성자에서 주입하기 Constructor Injection


생성자에서 파라미터를 전달하여 값(또는 객체, 이하 의존 dependency 이라고 칭함.)을 전달하는 것을 Constructor Injection 이라 정의한다. setter 메소드를 이용하는 방법을 Setter Injection 이라 정의 한다.
public class Emailer {
private SpellChecker spellChecker;
public Emailer(SpellChecker spellChecker) {
this.spellChecker = spellChecker;
}
}
[Constructor Injection]
public class Emailer {
private SpellChecker spellChecker;
public void setSpellChecker(SpellChecker spellChecker) {
this.spellChecker = spellChecker;
}
}
[Setter Injection] 

 생성자는 인스턴스 초기화의 책임이 있습니다. 초기화 할때는 속성에 의존을 할당하여 적절한 상태를 만들어야 하는데, 의존을 할당하는 방법은 생성자 파라미터, Setter 메소드 2가지가 있습니다. 

생성자와 일반 메소드와의 차이 : 
* 아무것도 반환하지 않는다.
 * 완전히 생성되기 전에 실행완료 되어야 한다. 
* final 필드를 최기화 할 수 있다. 메소드는 못한다. 
* 인스턴스 당 단 한번 호출된다. 
* 다른 이름을 지정할 수 없다. 클래스 이름과 같아야만 한다. 
* vitual 로 지정할 수 없다. 선언했으면 구현해야 한다.

 

 

Setter 메소드로 주입하기 Setter Injection


인스턴스가 완전히 생성된 후에 의존을 주입하는 것이 Constructor Injection과 다른 점입니다. Setter 메소드는 일반적인 메소드와 같은 방법으로 동작하면서 생성자의 제한을 극복하는 유연성(flexibility)을 가지고 있습니다. 하지만 유연성은 잇점을 가지고 있는 동시에 때때로 문제를 일으키는 원인이 되기도 합니다. Setter Injection은 일반적으로 매우 쉽게 사용됩니다. 많은 예제와 튜토리얼이 왜 사용되어야 하는지 아무 설명 없이 Setter Injection을 사용합니다. 사실 Setter Injection은 매우 유연하고, 편리합니다. 하지만 의존성 주입(Dependancy Injection) 관점에서 봤을때 완벽하지 않습니다. 

Setter Injection의 단점 : 
1. 인스턴스 초기화에 많은 setter가 필요할때 우리의 통제를 쉽게 벗어납니다. 
2. 클라이언트 코드 작성자는 setter 메소드가 사용되어야 할 적절한 시기에 대해서 알고 있어야 합니다. Setter Injection은 Information Hiding을 위반합니다. 클라이언트 코드 작성자는 사용하려는 타입에 대한 정보를 너무 많이 알아야 합니다.

 

인터페이스 주입 Interface Injection


Interface Injection은 인스턴스의 책임(role)을 주입해서 속성(property)에 대한 정보를 아는 것을 회피하자는 아이디어 입니다.
public class Rocket implements EngineMountable, Loadable, Startable,
Stoppable {
private Engine engine;
private Cargo cargo;
public void mount(Engine engine) {

this.engine = engine;
}
public void load(Cargo cargo) {
this.cargo = cargo;
}
public void start() {
//start this rocket's engine...
}
public void stop() {
//stop this rocket...
}
}
[Interface Injection] Interface Injection 의 장점: 
1. 목적에 맞는 이름을 붙일수 있다. setCargo(Cargo cargo) 보다는 load(Cargo cargo)라고 붙이는 것이 목적에 더욱 부합하며 이를 가능케 한다. 
2. 속성 정보에 대한 접근을 제한할 수 있다. 

단점: 
1. 모든 의존 마다 인터페이스를 정의하는 것은 낭비다. 
2. 많은 인터페이스를 나열하는 것은 class 정의를 깔끔하지 못하게 만든다. 
3. class signiture 를 지저분하게 만들며, 더 중요하게 처리해야 할 인터페이스로 부터 주의를 분산시킨다.

 

Method decoration (or AOP injection)


Method decoration은 의존 주입 방법의 변경에 흥미로워 합니다. 목적된 주입보다는 메소드 관점으로 접근합니다. 다시 설명하면, 원래 값이 아닌 injector-provided value를 반환하는 것입니다. 이 과정을 decorating 이라고 부르며 Decorator design pattern 으로 부터 이름을 따왔습니다.
public class Dispenser {
private Sugar sugar;
public Pez dispense() {
return new Pez(sugar);
}
}
[Method decoration]

 

 

Constructor vs. setter injection


Constructor 와 Setter injection 중에 선택하는 것은 인스턴스의 초기화에서 state 와 mutablility 두가지 요소에 의해서 좌우 됩니다. 우리는 대부분의 경우 필드가 딱 한번 할당되고, 변경되지 않길 바랍니다. 객체 생애 동안 의존이 변하지 않기를 바라는 것을 말하는 것이 아니라, 적시에 사용가능한 상태이어야 한다는 말입니다. Constructor Injection은 의존 불변을 제공해 줍니다. 고정된 의존 상태는 언제든지 사용가능하다는 것을 보증합니다. 프로그래밍에서 불변성(immutablility)은 매우 중요합니다. Setter Injection은 유연성을 가지는 대신에 immutablility를 훼손합니다. 그렇다면 Constructor Injection 만을 사용 해야 하느냐? 모든 의존이 결정되어 있고 불변하다면 Construction Injection 으로는 해결이 안되는 문제가 있습니다. 그것은 Reinjection Problem 이라고 합니다. 

 Reinjection problem : 의존하고 있는 인스턴스의 생애가 길고(long-lived) 의존 당하는 인스턴스의 생애가 짧으며(short-lived) 의존이 재 할당 되어야 하는 경우. 


의존이 예측되어 있고 불변한 것에는 Constructor Injection을 사용하고, Reinjection 이 요구되는 것에는 Setter Injection을 사용하는 것이 방어적이며 견고한 프로그래밍이라고 할 수 있습니다.

 

 

참고

Framework Design Guide Lines 2nd, 
Dependency Injection, Manning, by Dhanji R. Prasanna
저작자 표시
신고

이 글을 Twitter / Facebook 에 공유하기
이 글이 유익하다면 아래의 트위터 버튼을 눌러 공유해 주시거나, 페이스북 "좋아요" 버튼을 눌러 주세요.

   


Posted by 반더빌트


티스토리 툴바