Josh Bloch의 Builder Pattern에 비해 크게 개선 된 부분을 만들었습니다. 어떤 방식 으로든 "더 나은"것이 아니라, 매우 구체적인 상황에서 , 어떤 장점을 제공합니다. 가장 큰 장점은 빌더를 빌드 할 클래스에서 분리시키는 것입니다.
나는이 대안을 아래에 철저히 문서화했으며, 이것을 블라인드 빌더 패턴이라고 부릅니다.
디자인 패턴 : 블라인드 빌더
Joshua Bloch의 Builder Pattern (Effective Java의 항목 2, 2 판)에 대한 대안으로 Bloch Builder 의 많은 이점을 공유하는 "블라인드 빌더 패턴"을 작성했습니다. 정확히 같은 방식으로 사용됩니다. 블라인드 빌더는
- 둘러싸는 클래스에서 빌더를 분리하여 순환 종속성을 제거합니다.
- 둘러싸는 클래스 의 소스 코드 크기를 크게 줄입니다 ( 더 이상 은 아닙니다 ).
- 허용
ToBeBuilt
클래스는 확장 할 의 빌더를 확장 할 필요없이 .
이 문서에서는 빌드중인 ToBeBuilt
클래스를 " "클래스라고합니다.
Bloch Builder로 구현 된 클래스
Bloch Builder는 public static class
빌드하는 클래스 내부 에 포함되어 있습니다. 예를 들면 :
공개 클래스 UserConfig {
개인 최종 문자열 sName;
개인 최종 정보;
개인 최종 문자열 sFavColor;
공개 UserConfig (UserConfig.Cfg uc_c) {// CONSTRUCTOR
//이전
{
sName = uc_c.sName;
} catch (NullPointerException rx) {
새로운 NullPointerException을 던집니다 ( "uc_c");
}
iAge = uc_c.iAge;
sFavColor = uc_c.sFavColor;
// 여기서 모든 필드 확인
}
공개 문자열 toString () {
"name ="+ sName + ", age ="+ iAge + ", sFavColor ="+ sFavColor를 반환합니다.
}
// 빌더 ... START
공개 정적 클래스 Cfg {
개인 문자열 sName;
개인 정보;
개인 문자열 sFavColor;
공개 Cfg (문자열 s_name) {
sName = s_name;
}
// 자기 귀환 세터 ... START
공개 Cfg 연령 (int i_age) {
iAge = i_age;
이것을 돌려줍니다;
}
공개 Cfg favoriteColor (문자열 s_color) {
sFavColor = s_color;
이것을 돌려줍니다;
}
// 자기 귀환 세터 ... END
공개 UserConfig build () {
반환 (신규 UserConfig (this));
}
}
// 빌더 ... END
}
Bloch Builder를 사용하여 클래스 인스턴스화
UserConfig uc = new UserConfig.Cfg ( "Kermit"). age (50) .favoriteColor ( "green"). build ();
블라인드 빌더로 구현 된 동일한 클래스
블라인드 빌더에는 세 가지 부분이 있으며 각 부분은 별도의 소스 코드 파일에 있습니다.
ToBeBuilt
(이 예 : 클래스 UserConfig
)
- "
Fieldable
"인터페이스
- 빌더
1. 건설 될 수업
빌드 될 클래스는 Fieldable
인터페이스를 유일한 생성자 매개 변수로 승인합니다 . 생성자는 모든 내부 필드를 설정하고 각 필드의 유효성을 검사 합니다. 가장 중요한 것은이 ToBeBuilt
클래스는 빌더에 대한 지식이 없다는 것입니다.
공개 클래스 UserConfig {
개인 최종 문자열 sName;
개인 최종 정보;
개인 최종 문자열 sFavColor;
공개 UserConfig (UserConfig_Fieldable uc_f) {// CONSTRUCTOR
//이전
{
sName = uc_f.getName ();
} catch (NullPointerException rx) {
새로운 NullPointerException을 던집니다 ( "uc_f");
}
iAge = uc_f.getAge ();
sFavColor = uc_f.getFavoriteColor ();
// 여기서 모든 필드 확인
}
공개 문자열 toString () {
"name ="+ sName + ", age ="+ iAge + ", sFavColor ="+ sFavColor를 반환합니다.
}
}
한 스마트 주석 작성자 (명확하게 답변을 삭제 한 사람)가 언급했듯이 ToBeBuilt
클래스가 클래스를 구현하는 Fieldable
경우 하나의 유일한 생성자를 기본 및 복사 생성자 로 사용할 수 있습니다 (단, 필드는 항상 유효성이 검사된다는 단점이 있지만 원본의 필드 ToBeBuilt
가 유효한 것으로 알려져 있습니다 ).
2. " Fieldable
"인터페이스
필드 가능한 인터페이스는 ToBeBuilt
클래스와 해당 빌더 사이의 "브리지" 이며 객체를 빌드하는 데 필요한 모든 필드를 정의합니다. 이 인터페이스는 ToBeBuilt
클래스 생성자에 필요 하며 빌더에 의해 구현됩니다. 이 인터페이스는 빌더 이외의 클래스로 구현 될 수 있으므로 모든 클래스는 ToBeBuilt
빌더를 사용하지 않고도 클래스를 쉽게 인스턴스화 할 수 있습니다 . 또한 ToBeBuilt
빌더 확장이 바람직하지 않거나 필요하지 않을 때 클래스를 쉽게 확장 할 수 있습니다 .
아래 섹션에서 설명했듯이이 인터페이스의 기능을 전혀 문서화하지 않았습니다.
공용 인터페이스 UserConfig_Fieldable {
문자열 getName ();
int getAge ();
문자열 getFavoriteColor ();
}
3. 빌더
빌더는 Fieldable
클래스를 구현합니다 . 그것은 전혀 검증되지 않으며,이 사실을 강조하기 위해 모든 분야는 공개적이고 변경 가능합니다. 이 공개 접근성은 필수 사항은 아니지만, ToBeBuilt
의 생성자가 호출 될 때까지 유효성 검사가 수행되지 않는다는 사실을 다시 강제하기 때문에이를 선호하고 권장합니다 . 이것은 다른 스레드가 빌더를 생성자에 전달하기 전에 빌더를 추가로 조작 할 수 있기 때문에 중요 ToBeBuilt
합니다. 필드가 유효하다는 것을 보증하는 유일한 방법은 빌더가 어떻게 든 상태를 "잠글"수 없다고 가정하면 ToBeBuilt
클래스가 최종 확인을 수행하는 것입니다.
마지막으로 Fieldable
인터페이스 와 마찬가지로 getter도 문서화하지 않습니다.
공개 클래스 UserConfig_Cfg는 UserConfig_Fieldable {
공개 문자열 sName;
공개 정보;
공개 문자열 sFavColor;
공개 UserConfig_Cfg (문자열 s_name) {
sName = s_name;
}
// 자기 귀환 세터 ... START
공개 UserConfig_Cfg 연령 (int i_age) {
iAge = i_age;
이것을 돌려줍니다;
}
공개 UserConfig_Cfg favoriteColor (String s_color) {
sFavColor = s_color;
이것을 돌려줍니다;
}
// 자기 귀환 세터 ... END
//getters...START
공개 문자열 getName () {
sName을 반환합니다.
}
공개 int getAge () {
귀국;
}
공개 문자열 getFavoriteColor () {
return sFavColor;
}
// 게터 ... END
공개 UserConfig build () {
반환 (신규 UserConfig (this));
}
}
블라인드 빌더로 클래스 인스턴스화
UserConfig uc = new UserConfig_Cfg ( "Kermit"). age (50) .favoriteColor ( "green"). build ();
유일한 차이점은 " UserConfig_Cfg
"대신 " UserConfig.Cfg
"입니다
노트
단점 :
- 블라인드 빌더는 해당
ToBeBuilt
클래스의 개인 멤버에 액세스 할 수 없습니다 .
- 게터가 이제 빌더와 인터페이스 모두에서 필요하므로 더 장황합니다.
- 단일 수업에 대한 모든 것이 더 이상 한 곳에 있지 않습니다 .
블라인드 빌더 컴파일은 간단합니다 :
ToBeBuilt_Fieldable
ToBeBuilt
ToBeBuilt_Cfg
Fieldable
인터페이스는 전적으로 선택 사항입니다
A에 대한 ToBeBuilt
몇 가지 필수 필드와 클래스 - 이와 같은 UserConfig
예를 들어, 클래스, 생성자는 단순히 수
공개 UserConfig (문자열 s_name, int i_age, 문자열 s_favColor) {
그리고 빌더와 함께 호출
공개 UserConfig build () {
반환 (새 UserConfig (getName (), getAge (), getFavoriteColor ()));
}
또는 (빌더에서) 게터를 모두 제거하여 :
반환 (새로운 UserConfig (sName, iAge, sFavoriteColor));
필드를 직접 전달함으로써 ToBeBuilt
클래스는 Fieldable
인터페이스 와 마찬가지로 "맹인"(빌더를 알지 못함)합니다 . 그러나위한 ToBeBuilt
의도하고 클래스 "확장 여러 번 서브 - 확장"(이 게시물의 제목에있는)에 대한 변경 어떤 필드를 필요로 변경 모든 서브 클래스, 모든 빌더와 ToBeBuilt
생성자를. 필드 및 서브 클래스 수가 증가함에 따라 유지 관리가 비실용적이됩니다.
(실제로 필요한 필드가 거의 없기 때문에 빌더를 사용하는 것은 전혀 과잉 일 수 있습니다. 관심있는 사람들을 위해 내 개인 라이브러리의 더 큰 Fieldable 인터페이스 중 일부를 샘플링했습니다 .)
하위 패키지의 보조 클래스
나는 Fieldable
모든 블라인드 빌더를 위해 모든 빌더와 클래스를 클래스의 하위 패키지로 선택했습니다 ToBeBuilt
. 하위 패키지의 이름은 항상 " z
"입니다. 이렇게하면이 보조 클래스가 JavaDoc 패키지 목록을 복잡하게 만들 수 없습니다. 예를 들어
library.class.my.UserConfig
library.class.my.z.UserConfig_Fieldable
library.class.my.z.UserConfig_Cfg
검증 예
위에서 언급했듯이 모든 유효성 검사는 ToBeBuilt
의 생성자 에서 발생합니다 . 다음은 유효성 검사 코드 예제가있는 생성자입니다.
공개 UserConfig (UserConfig_Fieldable uc_f) {
//이전
{
sName = uc_f.getName ();
} catch (NullPointerException rx) {
새로운 NullPointerException을 던집니다 ( "uc_f");
}
iAge = uc_f.getAge ();
sFavColor = uc_f.getFavoriteColor ();
// 유효성 확인 (실제로 패턴을 사전 컴파일해야합니다 ...)
{
if (! Pattern.compile ( "\\ w +"). matcher (sName) .matches ()) {
새로운 IllegalArgumentException 던지기 ( "uc_f.getName () (\" "+ sName +"\ ")는 비워 둘 수 없으며 문자 숫자와 밑줄 만 포함해야합니다.");
}
} catch (NullPointerException rx) {
새로운 NullPointerException을 던집니다 ( "uc_f.getName ()");
}
if (iAge <0) {
새 IllegalArgumentException을 던집니다 ( "uc_f.getAge () ("+ iAge + ")가 0보다 작습니다.");
}
{
if (! Pattern.compile ( "(? : red | blue | green | hot pink)"). matcher (sFavColor) .matches ()) {
새로운 IllegalArgumentException을 던집니다 ( "uc_f.getFavoriteColor () (\" "+ uc_f.getFavoriteColor () +"\ ")는 빨강, 파랑, 녹색 또는 핫 핑크가 아닙니다.");
}
} catch (NullPointerException rx) {
새 NullPointerException을 던집니다 ( "uc_f.getFavoriteColor ()");
}
}
문서 작성기
이 섹션은 Bloch Builder 및 Blind Builder 모두에 적용됩니다. 이 디자인에서 클래스를 문서화하는 방법을 보여줍니다. 세터 (빌더)와 게터 ( ToBeBuilt
클래스)는 마우스 클릭 한 번으로 사용자가 어디를 알 필요없이 서로 직접 상호 참조하도록합니다. 이러한 기능은 실제로 존재하며 개발자가 중복으로 문서를 작성하지 않아도됩니다.
게터 : ToBeBuilt
수업에서만
Getter는 ToBeBuilt
수업 에서만 문서화됩니다 . _Fieldable
및
_Cfg
클래스 에서 동등한 getter 는 무시됩니다. 나는 그것들을 전혀 문서화하지 않았다.
/ **
<P> 사용자의 나이 </ P>
@return 사용자의 나이를 나타내는 int입니다.
@See UserConfig_Cfg # age (int)
@ getName () 참조
** /
공개 int getAge () {
귀국;
}
첫 번째 @see
는 빌더 클래스에있는 해당 setter에 대한 링크입니다.
세터 : 빌더 클래스에서
세터는 문서화 가있는 것처럼 ToBeBuilt
클래스 , 또한 것처럼 이 (실제로 수행되는 검증 수행 ToBeBuilt
의 생성자를). 별표 ( " *
")는 링크의 대상이 다른 클래스에 있음을 나타내는 시각적 단서입니다.
/ **
<P> 사용자의 나이를 설정하십시오. </ P>
@param i_age 0보다 작을 수 없습니다. {@code UserConfig # getName () getName ()} *으로 가져옵니다.
@see #favoriteColor (문자열)
** /
공개 UserConfig_Cfg 연령 (int i_age) {
iAge = i_age;
이것을 돌려줍니다;
}
추가 정보
종합 : 전체 문서와 함께 Blind Builder 예제의 전체 소스
UserConfig.java
import java.util.regex.Pattern;
/ **
<P> 사용자 정보-<I> [빌더 : UserConfig_Cfg] </ I> </ P>
<P>이 클래스 생성자에서 모든 필드의 유효성 검사가 수행됩니다. 그러나 각 유효성 검증 요구 사항은 빌더의 세터 기능에만 문서화되어 있습니다. </ P>
<P> {@ code java xbn.z.xmpl.lang.builder.finalv.UserConfig} </ P>
** /
공개 클래스 UserConfig {
공개 정적 최종 무효 main (String [] igno_red) {
UserConfig uc = new UserConfig_Cfg ( "Kermit"). age (50) .favoriteColor ( "green"). build ();
System.out.println (uc);
}
개인 최종 문자열 sName;
개인 최종 정보;
개인 최종 문자열 sFavColor;
/ **
<P> 새 인스턴스를 만듭니다. 모든 필드를 설정하고 확인합니다. </ P>
@param uc_f {@code null}이 아닐 수 있습니다.
** /
공개 UserConfig (UserConfig_Fieldable uc_f) {
//이전
{
sName = uc_f.getName ();
} catch (NullPointerException rx) {
새로운 NullPointerException을 던집니다 ( "uc_f");
}
iAge = uc_f.getAge ();
sFavColor = uc_f.getFavoriteColor ();
// 확인
{
if (! Pattern.compile ( "\\ w +"). matcher (sName) .matches ()) {
새로운 IllegalArgumentException 던지기 ( "uc_f.getName () (\" "+ sName +"\ ")는 비워 둘 수 없으며 문자 숫자와 밑줄 만 포함해야합니다.");
}
} catch (NullPointerException rx) {
새로운 NullPointerException을 던집니다 ( "uc_f.getName ()");
}
if (iAge <0) {
새 IllegalArgumentException을 던집니다 ( "uc_f.getAge () ("+ iAge + ")가 0보다 작습니다.");
}
{
if (! Pattern.compile ( "(? : red | blue | green | hot pink)"). matcher (sFavColor) .matches ()) {
새로운 IllegalArgumentException을 던집니다 ( "uc_f.getFavoriteColor () (\" "+ uc_f.getFavoriteColor () +"\ ")는 빨강, 파랑, 녹색 또는 핫 핑크가 아닙니다.");
}
} catch (NullPointerException rx) {
새 NullPointerException을 던집니다 ( "uc_f.getFavoriteColor ()");
}
}
//getters...START
/ **
<P> 사용자 이름. </ P>
@return {@code null}, 비어 있지 않은 문자열입니다.
@See UserConfig_Cfg # UserConfig_Cfg (문자열)
@see #getAge ()
@see #getFavoriteColor ()
** /
공개 문자열 getName () {
sName을 반환합니다.
}
/ **
<P> 사용자의 나이 </ P>
@return 0보다 크거나 같은 숫자입니다.
@See UserConfig_Cfg # age (int)
@see #getName ()
** /
공개 int getAge () {
귀국;
}
/ **
<P> 사용자가 좋아하는 색상. </ P>
@return {@code null}, 비어 있지 않은 문자열입니다.
@See UserConfig_Cfg # age (int)
@see #getName ()
** /
공개 문자열 getFavoriteColor () {
return sFavColor;
}
// 게터 ... END
공개 문자열 toString () {
"getName () ="+ getName () + ", getAge () ="+ getAge () + ", getFavoriteColor () ="+ getFavoriteColor ();
}
}
UserConfig_Fieldable.java
/ **
<P> {@ link UserConfig} {@code UserConfig # UserConfig (UserConfig_Fieldable) 생성자}에 필요합니다. </ P>
** /
공용 인터페이스 UserConfig_Fieldable {
문자열 getName ();
int getAge ();
문자열 getFavoriteColor ();
}
UserConfig_Cfg.java
import java.util.regex.Pattern;
/ **
<P> {@ link UserConfig} 용 빌더. </ P>
<P> 모든 필드의 유효성 검사는 <CODE> UserConfig </ CODE> 생성자에서 발생합니다. 그러나 각 유효성 검사 요구 사항은이 클래스 세터 함수에서만 문서화됩니다. </ P>
** /
공개 클래스 UserConfig_Cfg는 UserConfig_Fieldable {
공개 문자열 sName;
공개 정보;
공개 문자열 sFavColor;
/ **
<P> 사용자 이름으로 새 인스턴스를 만듭니다. </ P>
@param s_name {@code null}이거나 비어있을 수 없으며 문자, 숫자 및 밑줄 만 포함해야합니다. {@code UserConfig # getName () getName ()} {@ code ()}로 얻으십시오 .
** /
공개 UserConfig_Cfg (문자열 s_name) {
sName = s_name;
}
// 자기 귀환 세터 ... START
/ **
<P> 사용자의 나이를 설정하십시오. </ P>
@param i_age 0보다 작을 수 없습니다. {@code UserConfig # getName () getName ()} {@ code ()}로 얻으십시오 .
@see #favoriteColor (문자열)
** /
공개 UserConfig_Cfg 연령 (int i_age) {
iAge = i_age;
이것을 돌려줍니다;
}
/ **
<P> 사용자가 선호하는 색상을 설정하십시오. </ P>
@param s_color {@code "red"}, {@code "blue"}, {@code green} 또는 {@code "hot pink"} 여야합니다. {@code UserConfig # getName () getName ()} {@ code ()} *로 가져옵니다.
@ 참조 # 나이 (int)
** /
공개 UserConfig_Cfg favoriteColor (String s_color) {
sFavColor = s_color;
이것을 돌려줍니다;
}
// 자기 귀환 세터 ... END
//getters...START
공개 문자열 getName () {
sName을 반환합니다.
}
공개 int getAge () {
귀국;
}
공개 문자열 getFavoriteColor () {
return sFavColor;
}
// 게터 ... END
/ **
<P> 구성된대로 UserConfig를 빌드하십시오. </ P>
@return <CODE> (신규 {@link UserConfig # UserConfig (UserConfig_Fieldable) UserConfig} (this)) </ CODE>
** /
공개 UserConfig build () {
반환 (신규 UserConfig (this));
}
}