web-dev-qa-db-ja.com

エレガントな方法で多くのフィールドを持つクラスを初期化するにはどうすればよいですか?

私のアプリケーションでは、さまざまな種類のオブジェクトをインスタンス化する必要があります。各タイプにはいくつかのフィールドが含まれ、包含タイプに追加する必要があります。エレガントな方法でこれを行うにはどうすればよいですか?

現在の初期化手順は次のようになります。

public void testRequest() {

        //All these below used classes are generated classes from xsd schema file.

        CheckRequest checkRequest = new CheckRequest();

        Offers offers = new Offers();
        Offer offer = new Offer();
        HotelOnly hotelOnly = new HotelOnly();
        Hotel hotel = new Hotel();
        Hotels hotels = new Hotels();
        Touroperator touroperator = new Touroperator();
        Provider provider = new Provider();
        Rooms rooms = new Rooms();
        Room room = new Room();
        PersonAssignments personAssignments = new PersonAssignments();
        PersonAssignment personAssignment = new PersonAssignment(); 
        Persons persons = new Persons();
        Person person = new Person();
        Amounts amounts = new Amounts();

        offers.getOffer().add(offer);
        offer.setHotelOnly(hotelOnly);

        room.setRoomCode("roomcode");
        rooms.getRoom().add(room);

        hotels.getHotel().add(hotel);
        hotel.setRooms(rooms);

        hotelOnly.setHotels(hotels);

        checkRequest.setOffers(offers);

        // ...and so on and so on
    } 

各オブジェクトを個別にインスタンス化し、複数のコード行にわたって各フィールドを初期化するのは少し面倒なので(たとえば、new Offer()を呼び出してからsetHotelOnly(hotelOnly)次にadd(offer))。

私が持っているものの代わりにどのようなエレガントな方法を使用できますか?使用できる「Factories」はありますか?このようなコードを書くことを避けるための参照/例はありますか?

きれいなコードを実装することに本当に興味があります。


環境:

Webサービスに投稿リクエストを送信するためのRestClientアプリケーションを開発しています。

APIはxsd schemaファイルとして表され、JAXBですべてのオブジェクトを作成しました

リクエストを送信する前に、多くのオブジェクトをインスタンス化する必要があります。それらは互いに依存関係があるためです。 (提供にはホテルがあり、ホテルには部屋があり、部屋には人がいます...これらのクラスは生成されたものです)

ご協力いただきありがとうございます。

25
Patrick

コンストラクターまたは builder pattern またはビルダーパターンのバリエーションを使用して、初期化ステップでフィールドが多すぎるという問題を修正できます。

これらのオプションが有用である理由を説明するために、例を少し拡張します。

あなたの例を理解する:

Offerは単に4つのフィールドのコンテナクラスであるとしましょう:

_public class Offer {
    private int price;
    private Date dateOfOffer;
    private double duration;
    private HotelOnly hotelOnly;
    // etc. for as many or as few fields as you need

    public int getPrice() {
        return price;
    }

    public Date getDateOfOffer() {
        return dateOfOffer;
    }

    // etc.
}
_

あなたの例では、これらのフィールドに値を設定するには、セッターを使用します:

_    public void setHotelOnly(HotelOnly hotelOnly) {
        this.hotelOnly = hotelOnly;
    }
_

残念ながら、これは、すべてのフィールドに値を持つオファーが必要な場合は、次のことを実行する必要があることを意味します。

_Offers offers = new Offers();
Offer offer = new Offer();
offer.setPrice(price);
offer.setDateOfOffer(date);
offer.setDuration(duration);
offer.setHotelOnly(hotelOnly);
offers.add(offer);
_

これを改善してみましょう。

オプション1:コンストラクター!

デフォルトのコンストラクター以外のコンストラクター(デフォルトのコンストラクターは現在Offer())は、クラスのフィールドの値を初期化するのに役立ちます。

コンストラクタを使用するOfferのバージョンは次のようになります。

_public class Offer {
    private int price;
    private Date dateOfOffer;
    //etc.

    // CONSTRUCTOR
    public Offer(int price, Date dateOfOffer, double duration, HotelOnly hotelOnly) {
        this.price = price;
        this.dateOfOffer = dateOfOffer;
        //etc.
    }

    // Your getters and/or setters
}
_

これで、1行で初期化できます!

_Offers offers = new Offers();
Offer offer = new Offer(price, date, duration, hotelOnly);
offers.add(offer);
_

さらに良いことに、その単一行以外のofferを使用しない場合は、offers.add(offer);を変数に保存する必要さえありません!

_Offers offers = new Offers();
offers.add( new Offer(price, date, duration, hotelOnly) ); // Works the same as above
_

オプション2:ビルダーパターン

ビルダーパターン は、任意のフィールドにデフォルト値を設定するオプションが必要な場合に便利です。

ビルダーパターンが解決する問題は、次の厄介なコードです。

_public class Offer {
    private int price;
    private Date dateOfOffer;
    // etc.

    // The original constructor. Sets all the fields to the specified values
    public Offer(int price, Date dateOfOffer, double duration, HotelOnly hotelOnly) {
        this.price = price;
        this.dateOfOffer = dateOfOffer;
        // etc.
    }

    // A constructor that uses default values for all of the fields
    public Offer() {
        // Calls the top constructor with default values
        this(100, new Date("10-13-2015"), 14.5, new HotelOnly());
    }

    // A constructor that uses default values for all of the fields except price
    public Offer(int price) {
        // Calls the top constructor with default values, except price
        this(price, new Date("10-13-2015"), 14.5, new HotelOnly());
    }

    // A constructor that uses default values for all of the fields except Date and HotelOnly
    public Offer(Date date, HotelOnly hotelOnly) {
        this(100, date, 14.5, hotelOnly);
    }

    // A bunch more constructors of different combinations of default and specified values

}
_

それがどれほど厄介なのかをご覧ください。

ビルダーパターンは、クラスにinsideを配置する別のクラスです。

_public class Offer {
    private int price;
    // etc.

    public Offer(int price, ...) {
        // Same from above
    }

    public static class OfferBuilder {
        private int buildPrice = 100;
        private Date buildDate = new Date("10-13-2015");
        // etc. Initialize all these new "build" fields with default values

        public OfferBuilder setPrice(int price) {
            // Overrides the default value
            this.buildPrice = price;

            // Why this is here will become evident later
            return this;
        }

        public OfferBuilder setDateOfOffer(Date date) {
            this.buildDate = date;
            return this;
        }

        // etc. for each field

        public Offer build() {
            // Builds an offer with whatever values are stored
            return new Offer(price, date, duration, hotelOnly);
        }
    }
}
_

これで、それほど多くのコンストラクターを持つ必要はありませんが、デフォルトのままにする値と初期化する値を選択できます。

_Offers offers = new Offers();
offers.add(new OfferBuilder().setPrice(20).setHotelOnly(hotelOnly).build());
offers.add(new OfferBuilder().setDuration(14.5).setDate(new Date("10-14-2015")).setPrice(200).build());
offers.add(new OfferBuilder().build());
_

この最後のオファーは、すべてデフォルト値を持つものです。その他は、私が設定したものを除くデフォルト値です。

それが物事を簡単にする方法をご覧ください。

オプション3:ビルダーパターンのバリエーション

現在のセッターが同じOfferオブジェクトを返すようにするだけで、ビルダーパターンを使用することもできます。余分なOfferBuilderクラスがないことを除いて、まったく同じです。

警告: ユーザーWWは以下に述べます このオプションは、 JavaBeans-Offerなどのコンテナクラスの標準プログラミング規則 を破ります。したがって、これを専門的な目的で使用することは避けてください。また、自分の慣行での使用を制限する必要があります。

_public class Offer {
    private int price = 100;
    private Date date = new Date("10-13-2015");
    // etc. Initialize with default values

    // Don't make any constructors

    // Have a getter for each field
    public int getPrice() {
        return price;
    }

    // Make your setters return the same object
    public Offer setPrice(int price) {
        // The same structure as in the builder class
        this.price = price;
        return this;
    }

    // etc. for each field

    // No need for OfferBuilder class or build() method
}
_

そして、新しい初期化コードは

_Offers offers = new Offers();
offers.add(new Offer().setPrice(20).setHotelOnly(hotelOnly));
offers.add(new Offer().setDuration(14.5).setDate(new Date("10-14-2015")).setPrice(200));
offers.add(new Offer());
_

この最後のオファーは、すべてデフォルト値を持つものです。その他は、私が設定したものを除くデフォルト値です。


したがって、多くの作業が必要になりますが、初期化手順をクリーンアップする場合は、フィールドを持つクラスごとにこれらのオプションのいずれかを使用する必要があります。次に、各メソッドに含まれている初期化メソッドを使用します。

幸運を!これにはさらに説明が必要ですか?

44
snickers10m

builder-pattern-with-a-twist を使用することは、ビルダーパターンの基本的なアプローチよりもはるかに優れているため、常に好まれています。

しかし、構築しようとしているクラスにとって重要であるため、ユーザーに1つのビルダーメソッドを呼び出す必要があることをユーザーに伝えたい場合はどうなりますか。

URLコンポーネントのビルダーについて考えてください。 URL属性へのアクセスをカプセル化するためのビルダーメソッドについて、どのように考えますか?それらは同様に重要ですか、互いにやり取りしますか?クエリパラメータまたはフラグメントはオプションですが、ホスト名はありません。プロトコルも必要と言えますが、そのためにはhttpのような意味のあるデフォルトを使用できますか?

とにかく、これがあなたの特定の問題に意味があるかどうかはわかりませんが、他の人がそれを見ることは言及する価値があると思いました。

10
Filip

いくつかの素敵な答えがすでにここに与えられています!

追加として頭に浮かんだのは Domain Driven Design です。特定の Building blocks パート、EntityValue ObjectAggregateFactoryなど.

Domain Driven Design-Quickly (pdf)に素敵な紹介があります。

3
Verhagen

コメントで言及されたため、この回答を提供しましたが、デザインパターンのこの列挙の一部であるべきだと思います


ヌルオブジェクトデザインパターン

意図

Nullオブジェクトの目的は、適切なデフォルトの何もしない動作を提供する代替可能な代替手段を提供することにより、オブジェクトの不在をカプセル化することです。一言で言えば、「何もなくなることはない」デザイン

Null Objectパターンを使用するのは

  • オブジェクトには共同編集者が必要です。 Null Objectパターンは、このコラボレーションを導入しません。既存のコラボレーションを利用します
  • 一部のコラボレーターインスタンスは何もしません
  • nullの処理をクライアントから抽象化したい

ここでは、「Null Object」デザインパターンのすべての部分が見つかります

1
Patrick

理想的には、オブジェクトは依存関係のインスタンス化について心配するべきではありません。それは彼らとすることになっていることだけを心配するべきです。依存性注入フレームワークを検討しましたか? Springまたは Google's Juice は非常に用途が広く、設置面積が小さいです。

考え方は単純です。依存関係を宣言し、フレームワークに依存関係をいつ/どのように/どこで作成し、それをクラスに「注入」するように決定させます。

フレームワークを使用したくない場合は、フレームワークから設計メモを取り、設計パターンをエミュレートして、ユースケースに合わせて微調整することができます。

また、コレクションを適切に使用することにより、物事をある程度簡素化できます。たとえば、OffersにはOfferのコレクションを保存する以外にどのような追加機能がありますか?制約があるかどうかはわかりませんが、その部分をもう少しきれいにすることができれば、オブジェクトをインスタンス化するすべての場所で大きな利益を得られます。

1
Prasoon Joshi

Dozer framework は、wsオブジェクトからdtoに値をコピーする素晴らしい方法を提供します。別の があります。さらに、ゲッター/セッター名が両方のクラスで同じ場合、カスタムコンバーターは不要です

0
HRgiger