如何以优雅的方式用很多字段初始化 类?

How do I initialize classes with lots of fields in an elegant way?

在我的应用程序中,我必须实例化许多不同类型的对象。每种类型都包含一些字段,需要添加到包含类型中。我怎样才能优雅地做到这一点?

我当前的初始化步骤如下所示:

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”?你有什么办法 references/examples 可以避免写这样的代码吗?

我真的很想实现干净的代码。


上下文:

我正在开发一个 RestClient 应用程序,用于向 Web 服务发送 post 请求。

API 表示为 xsd schema 文件,我用 JAXB

创建了所有对象

在发送请求之前,我必须实例化许多对象,因为它们相互依赖。 (一个Offer有Hotels,一个Hotel有Rooms,一个Room有Persons...这些类是生成的)

感谢您的帮助。

您可以使用构造函数或 builder pattern 或构建器模式的变体来解决初始化步骤中字段过多的问题。

我将对您的示例进行一些扩展,以证明这些选项为何有用的观点。

理解你的例子:

假设 Offer 只是一个包含 4 个字段的容器 class:

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() )之外的构造函数对于初始化 class.

中字段的值很有用

使用构造函数的 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
}

现在,我们可以在一行中初始化它!

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:构建器模式

如果您想为任何字段选择默认值,builder pattern 很有用。

构建器模式解决的问题是下面的乱码:

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

}

看看会变得多乱?

构建器模式是另一个 class,您将 放在 中 class。

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:构建器模式的变体

您也可以使用构建器模式,只需让您当前的设置器 return 成为相同的 Offer 对象即可。它完全一样,除了没有额外的 OfferBuilder class.

警告:如 this option breaks JavaBeans - a standard programming convention for container classes such as 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());

最后一个报价只是一个具有所有默认值的报价。其他都是默认值,除了我设置的那些。


因此,虽然工作量很大,但如果您想清理初始化步骤,则需要为每个包含字段的 classes 使用这些选项之一。然后使用我在每个方法中包含的初始化方法。

祝你好运!这些是否需要进一步解释?

我一直更喜欢使用 builder-pattern-with-a-twist,因为它提供的不仅仅是构建器模式的基本方法。

But what happens when you want to tell the user that she must call one builder method or the other, since it is crucial for the class you’re trying to build.

考虑 URL 组件的构建器。人们会如何考虑用于封装对 URL 属性的访问的构建器方法,它们是否同样重要,它们是否相互交互等等?虽然查询参数或片段是可选的,但主机名不是;你可以说协议也是必需的,但为此你可以有一个有意义的默认值,比如 http 对吗?

无论如何,我不知道这对您的特定问题是否有意义,但我认为值得一提,让其他人看看。

这里已经给出了一些不错的答案!

我想到的补充是 Domain Driven Design. Specific the Building blocks 部分,有 EntityValue Object聚合工厂

Domain Driven Design - Quickly (pdf) 中有很好的介绍。

我只是提供这个答案,因为它在评论中被提及,我认为它也应该是设计模式枚举的一部分。


空对象设计模式

意图

空对象的目的是通过提供可替换的替代方案来封装对象的缺失,该替代方案提供合适的默认不做任何行为。简而言之,"nothing will come of nothing"

的设计

时使用空对象模式
  • 一个对象需要一个协作者。空对象模式不引入这种协作——它利用已经存在的协作
  • 一些协作者实例应该什么都不做
  • 您想从客户端抽象出对 null 的处理

Here you find the full part of "Null Object" Design Pattern

理想情况下,对象不应该关心实例化它的依赖项。它应该只担心它应该用它们做的事情。 您是否考虑过任何依赖注入框架? Spring 或 Google's Juice 用途广泛,占用空间小。

想法很简单,您声明依赖项并让框架决定 when/how/where 创建它们,然后 'inject' 将其放入您的 类。

如果您不想使用任何框架,您可以从他们那里获取设计笔记并尝试模仿他们的设计模式并针对您的用例进行调整。

另外,适当使用Collections也可以在一定程度上简化事情。例如,Offers 除了存储 Offer 的集合之外还有什么附加功能?我不确定您的限制是什么,但是,如果您可以使该部分更清晰一些,您将在实例化对象的所有地方获得巨大收益。

Dozer framework provides nice way to do copy values from ws object to your dto. Here is another example。此外,如果 getter/setter 名称与 class 相同,则不需要自定义转换器