私は、Josh BlochのBuilderパターンを大きく改善するものを作成しました。それが「より良い」とは言いませんが、非常に特定の状況では、いくつかの利点を提供します-最大のことは、ビルダーをビルドされるクラスから切り離すことです。
この代替案を以下に完全に文書化しました。これをブラインドビルダーパターンと呼びます。
デザインパターン:ブラインドビルダー
Joshua Blochのビルダーパターン(Effective Javaのアイテム2、第2版)の代替として、私は「ブラインドビルダーパターン」と呼んでいるものを作成しました。まったく同じ方法で使用されます。ブラインドビルダーには次の利点があります。
- ビルダーをその包含クラスから分離し、循環依存関係を排除し、
- 囲んでいるクラスのソースコードのサイズを大幅に削減します(もはやではありません)。
- ビルダーを拡張することなく
ToBeBuilt
クラスを拡張できます。
このドキュメントでは、作成中のクラスを「ToBeBuilt
」クラスと呼びます。
Bloch Builderで実装されたクラス
Bloch Builderは、public static class
ビルドするクラス内に含まれています。例:
パブリッククラスUserConfig {
プライベート最終文字列sName;
private final int iAge;
private final String sFavColor;
public UserConfig(UserConfig.Cfg uc_c){// CONSTRUCTOR
//転送
{
sName = uc_c.sName;
} catch(NullPointerException rx){
throw NullPointerException( "uc_c");
}
iAge = uc_c.iAge;
sFavColor = uc_c.sFavColor;
//すべてのフィールドをここで検証
}
public String toString(){
return "name =" + sName + "、age =" + iAge + "、sFavColor =" + sFavColor;
}
//ビルダー... START
パブリックスタティッククラスCfg {
プライベート文字列sName;
private int iAge;
private String sFavColor;
public Cfg(String s_name){
sName = s_name;
}
//自己復帰セッター... START
public Cfg age(int i_age){
iAge = i_age;
これを返す;
}
public Cfg favoriteColor(String s_color){
sFavColor = s_color;
これを返す;
}
//自己復帰セッター... END
public UserConfig build(){
return(新しいUserConfig(this));
}
}
//ビルダー... END
}
Bloch Builderを使用してクラスをインスタンス化する
UserConfig uc = new UserConfig.Cfg( "Kermit")。age(50).favoriteColor( "green")。build();
ブラインドビルダーとして実装された同じクラス
ブラインドビルダーには3つの部分があり、それぞれが個別のソースコードファイルにあります。
ToBeBuilt
(この例では:クラスUserConfig
)
- その「
Fieldable
」インターフェース
- ビルダー
1.作成予定のクラス
作成されるクラスは、そのFieldable
インターフェイスを唯一のコンストラクタパラメータとして受け入れます。コンストラクターは、コンストラクターからすべての内部フィールドを設定し、それぞれを検証します。最も重要なことは、このToBeBuilt
クラスにはそのビルダーに関する知識がないことです。
パブリッククラスUserConfig {
プライベート最終文字列sName;
private final int iAge;
private final String sFavColor;
public UserConfig(UserConfig_Fieldable uc_f){// CONSTRUCTOR
//転送
{
sName = uc_f.getName();
} catch(NullPointerException rx){
新しいNullPointerException( "uc_f");をスローします。
}
iAge = uc_f.getAge();
sFavColor = uc_f.getFavoriteColor();
//すべてのフィールドをここで検証
}
public String toString(){
return "name =" + sName + "、age =" + iAge + "、sFavColor =" + sFavColor;
}
}
1人のスマートコメンター(答えが説明できないほど削除された)が指摘したように、ToBeBuilt
クラスがそのを実装しているFieldable
場合、その唯一のコンストラクターをプライマリコンストラクターとコピーコンストラクターの両方として使用できます(欠点は、フィールドが常に検証されることです)オリジナルのフィールドToBeBuilt
が有効であることがわかっています)。
2.「Fieldable
」インターフェース
フィールド化可能なインターフェイスは、ToBeBuilt
クラスとそのビルダー間の「ブリッジ」であり、オブジェクトの構築に必要なすべてのフィールドを定義します。このインターフェイスは、ToBeBuilt
クラスコンストラクターに必要であり、ビルダーによって実装されます。このインターフェイスは、ビルダー以外のクラスによって実装される可能性があるため、どのクラスでもToBeBuilt
、そのビルダーを使用せずに、クラスを簡単にインスタンス化できます。またToBeBuilt
、ビルダーを拡張することが望ましくない場合や必要でない場合に、クラスを簡単に拡張できます。
以下のセクションで説明するように、このインターフェイスの機能についてはまったく説明しません。
パブリックインターフェイスUserConfig_Fieldable {
文字列getName();
int getAge();
文字列getFavoriteColor();
}
3.ビルダー
ビルダーはFieldable
クラスを実装します。検証はまったく行われず、この事実を強調するために、そのフィールドはすべてパブリックで変更可能です。このパブリックアクセシビリティは要件ではありませんが、ToBeBuilt
のコンストラクタが呼び出されるまで検証が行われないという事実を強化するため、私はそれを好み、推奨します。これは重要です。なぜなら、別のスレッドがのコンストラクターに渡される前にビルダーをさらに操作する可能性があるからToBeBuilt
です。フィールドが有効であることを保証する唯一の方法は、ビルダーが何らかの形でその状態を「ロック」できないと仮定して、ToBeBuilt
クラスが最終チェックを行うことです。
最後に、Fieldable
インターフェースと同様に、そのゲッターについては文書化していません。
パブリッククラスUserConfig_CfgはUserConfig_Fieldableを実装します{
public String sName;
public int iAge;
public String sFavColor;
public UserConfig_Cfg(String s_name){
sName = s_name;
}
//自己復帰セッター... START
public UserConfig_Cfg age(int i_age){
iAge = i_age;
これを返す;
}
public UserConfig_Cfg favoriteColor(String s_color){
sFavColor = s_color;
これを返す;
}
//自己復帰セッター... END
//ゲッター... START
public String getName(){
return sName;
}
public int getAge(){
iAgeを返します。
}
public String getFavoriteColor(){
return sFavColor;
}
//ゲッター... END
public UserConfig build(){
return(新しいUserConfig(this));
}
}
ブラインドビルダーでクラスをインスタンス化する
UserConfig uc = new UserConfig_Cfg( "Kermit")。age(50).favoriteColor( "green")。build();
唯一の違いは、「UserConfig_Cfg
」ではなく「UserConfig.Cfg
」です
ノート
短所:
- ブラインドビルダーは、その
ToBeBuilt
クラスのプライベートメンバーにアクセスできません。
- ビルダーとインターフェースの両方でゲッターが必要になったため、これらはより冗長です。
- 単一クラスのすべてが1つの場所にあるのではありません。
ブラインドビルダーのコンパイルは簡単です。
ToBeBuilt_Fieldable
ToBeBuilt
ToBeBuilt_Cfg
Fieldable
インターフェースは完全に任意です
以下のためにToBeBuilt
いくつかの必要なフィールドを持つクラス-このようなUserConfig
例クラス、コンストラクタは単に可能性があり
public UserConfig(String s_name、int i_age、String s_favColor){
そして、ビルダーで呼び出されます
public UserConfig build(){
return(new UserConfig(getName()、getAge()、getFavoriteColor()));
}
または、(ビルダーで)ゲッターを完全に削除することによっても:
return(新しい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
のコンストラクターで行われます。検証コードの例を示したコンストラクターを次に示します。
public 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()){
throw new IllegalArgumentException( "uc_f.getName()(\" "+ sName +" \ ")は空ではなく、数字とアンダースコアのみを含める必要があります。");
}
} catch(NullPointerException rx){
throw new NullPointerException( "uc_f.getName()");
}
if(iAge <0){
throw new IllegalArgumentException( "uc_f.getAge()(" + iAge + ")はゼロ未満です。");
}
{
if(!Pattern.compile( "(?:red | blue | green | hot pink)")。matcher(sFavColor).matches()){
throw new IllegalArgumentException( "uc_f.getFavoriteColor()(\" "+ uc_f.getFavoriteColor()+" \ ")は赤、青、緑、またはホットピンクではありません。");
}
} catch(NullPointerException rx){
throw NullPointerException( "uc_f.getFavoriteColor()");
}
}
ビルダーの文書化
このセクションはBloch BuilderとBlind Builderの両方に適用されます。このデザインでクラスを文書化し、セッター(ビルダー内)とそのゲッター(ToBeBuilt
クラス内)を相互に直接相互参照する方法を示します。マウスを1回クリックするだけで、ユーザーはどこを知る必要もありません。これらの機能は実際に存在し、開発者が何も冗長に文書化する必要はありません。
ゲッター:ToBeBuilt
クラスのみ
ゲッターはToBeBuilt
クラスでのみ文書化されます。クラス_Fieldable
と
_Cfg
クラスの両方の同等のゲッターは無視されます。私はそれらをまったく文書化しません。
/ **
<P>ユーザーの年齢。</ P>
@returnユーザーの年齢を表すint。
@see UserConfig_Cfg#age(int)
@see getName()
** /
public int getAge(){
iAgeを返します。
}
1つ@see
は、ビルダークラスにあるセッターへのリンクです。
セッター:ビルダークラス
セッターは、文書化され、それがであるかのようにToBeBuilt
クラスかのようにも、そしてそれが(本当にによって行われ、検証んToBeBuilt
のコンストラクタ)。アスタリスク( " *
")は、リンクのターゲットが別のクラスにあることを示す視覚的な手がかりです。
/ **
<P>ユーザーの年齢を設定します。</ P>
@param i_ageゼロより小さくすることはできません。{@code UserConfig#getName()getName()} *で取得します。
@see #favoriteColor(String)
** /
public UserConfig_Cfg age(int i_age){
iAge = i_age;
これを返す;
}
さらに詳しい情報
すべてをまとめる:ブラインドビルダーのサンプルの完全なソースと完全なドキュメント
UserConfig.java
import java.util.regex.Pattern;
/ **
<P>ユーザーに関する情報-<I> [builder:UserConfig_Cfg] </ I> </ P>
<P>すべてのフィールドの検証は、このクラスコンストラクターで行われます。ただし、各検証要件は、ビルダーのセッター関数のドキュメントのみです。</ P>
<P> {@ code java xbn.z.xmpl.lang.builder.finalv.UserConfig} </ P>
** /
パブリッククラスUserConfig {
public static final void main(String [] igno_red){
UserConfig uc = new UserConfig_Cfg( "Kermit")。age(50).favoriteColor( "green")。build();
System.out.println(uc);
}
プライベート最終文字列sName;
private final int iAge;
private final String sFavColor;
/ **
<P>新しいインスタンスを作成します。これにより、すべてのフィールドが設定および検証されます。</ P>
@param uc_fは{@code null}にはできません。
** /
public 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()){
throw new IllegalArgumentException( "uc_f.getName()(\" "+ sName +" \ ")は空ではなく、数字とアンダースコアのみを含める必要があります。");
}
} catch(NullPointerException rx){
throw new NullPointerException( "uc_f.getName()");
}
if(iAge <0){
throw new IllegalArgumentException( "uc_f.getAge()(" + iAge + ")はゼロ未満です。");
}
{
if(!Pattern.compile( "(?:red | blue | green | hot pink)")。matcher(sFavColor).matches()){
throw new IllegalArgumentException( "uc_f.getFavoriteColor()(\" "+ uc_f.getFavoriteColor()+" \ ")は赤、青、緑、またはホットピンクではありません。");
}
} catch(NullPointerException rx){
throw NullPointerException( "uc_f.getFavoriteColor()");
}
}
//ゲッター... START
/ **
<P>ユーザーの名前。</ P>
@return非{@code null}、空でない文字列。
@see UserConfig_Cfg#UserConfig_Cfg(String)
@see #getAge()
@see #getFavoriteColor()
** /
public String getName(){
return sName;
}
/ **
<P>ユーザーの年齢。</ P>
@returnゼロ以上の数値。
@see UserConfig_Cfg#age(int)
@see #getName()
** /
public int getAge(){
iAgeを返します。
}
/ **
<P>ユーザーの好きな色。</ P>
@return非{@code null}、空でない文字列。
@see UserConfig_Cfg#age(int)
@see #getName()
** /
public String getFavoriteColor(){
return sFavColor;
}
//ゲッター... END
public String toString(){
return "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を実装します{
public String sName;
public int iAge;
public String sFavColor;
/ **
<P>ユーザーの名前で新しいインスタンスを作成します。</ P>
@param s_name {@code null}または空であってはならず、文字、数字、アンダースコアのみを含める必要があります。{@code UserConfig#getName()getName()} {@ code()}で取得します。
** /
public UserConfig_Cfg(String s_name){
sName = s_name;
}
//自己復帰セッター... START
/ **
<P>ユーザーの年齢を設定します。</ P>
@param i_ageゼロより小さくすることはできません。{@code UserConfig#getName()getName()} {@ code()}で取得します。
@see #favoriteColor(String)
** /
public UserConfig_Cfg age(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()} *で取得します。
@see #age(int)
** /
public UserConfig_Cfg favoriteColor(String s_color){
sFavColor = s_color;
これを返す;
}
//自己復帰セッター... END
//ゲッター... START
public String getName(){
return sName;
}
public int getAge(){
iAgeを返します。
}
public String getFavoriteColor(){
return sFavColor;
}
//ゲッター... END
/ **
<P>構成に従ってUserConfigをビルドします。</ P>
@return <CODE>(新しい{@link UserConfig#UserConfig(UserConfig_Fieldable)UserConfig}(this))</ CODE>
** /
public UserConfig build(){
return(新しいUserConfig(this));
}
}