如何以优雅的方式初始化具有大量字段的类?

Pat*_*ick 26 java coding-style builder code-cleanup object-construction

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

我当前的初始化步骤看起来像这样:

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
    } 
Run Code Online (Sandbox Code Playgroud)

我真的想避免编写这样的代码,因为它必须分别实例化每个对象然后跨多行代码初始化每个字段(例如必须调用new Offer()然后setHotelOnly(hotelOnly)然后add(offer)).

我可以用什么优雅的方法代替我拥有的方法?有没有Factories可用的" "?你有没有任何参考/例子来避免编写这样的代码?

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


语境:

我正在开发一个RestClient应用程序,用于将发布请求发送到Web服务.

API表示为xsd schema文件,我创建了所有对象JAXB

在发送请求之前,我必须实例化许多对象,因为它们彼此之间存在依赖关系. (优惠有酒店,酒店有房间,房间有人......这些类是生成的)

谢谢你的帮助.

sni*_*10m 45

您可以使用构造函数或构建器模式构建器模式的变体来解决初始化步骤中包含太多字段的问题.

我将扩展你的例子以证明我为什么这些选项有用的观点.

理解你的例子:

让我们说一个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.
}
Run Code Online (Sandbox Code Playgroud)

在您的示例中,要为这些字段设置值,您可以使用setter:

    public void setHotelOnly(HotelOnly hotelOnly) {
        this.hotelOnly = hotelOnly;
    }
Run Code Online (Sandbox Code Playgroud)

不幸的是,这意味着如果您需要在所有字段中提供值的商品,您必须执行以下操作:

Offers offers = new Offers();
Offer offer = new Offer();
offer.setPrice(price);
offer.setDateOfOffer(date);
offer.setDuration(duration);
offer.setHotelOnly(hotelOnly);
offers.add(offer);
Run Code Online (Sandbox Code Playgroud)

现在让我们来看看改进这一点.

选项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
}
Run Code Online (Sandbox Code Playgroud)

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

Offers offers = new Offers();
Offer offer = new Offer(price, date, duration, hotelOnly);
offers.add(offer);
Run Code Online (Sandbox Code Playgroud)

更好的是,如果您从未使用过offer该单行以外的其他内容:offers.add(offer);您甚至不需要将其保存在变量中!

Offers offers = new Offers();
offers.add( new Offer(price, date, duration, hotelOnly) ); // Works the same as above
Run Code Online (Sandbox Code Playgroud)

选项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

}
Run Code Online (Sandbox Code Playgroud)

看看它有多乱?

Builder模式是,你把另一个类的内部类.

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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

现在,您不必拥有这么多构造函数,但仍然可以选择要保留默认值以及要初始化的值.

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());
Run Code Online (Sandbox Code Playgroud)

最后一个报价只是一个包含所有默认值的报价.其他是默认值,除了我设置的值.

看看它如何让事情变得更容易?

选项3:构建器模式的变化

您还可以通过简单地使当前setter返回相同的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
}
Run Code Online (Sandbox Code Playgroud)

你的新初始化代码是

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());
Run Code Online (Sandbox Code Playgroud)

最后一个报价只是一个包含所有默认值的报价.其他是默认值,除了我设置的值.


因此,虽然这是很多工作,但如果要清理初始化步骤,则需要为每个包含字段的类使用其中一个选项.然后使用我在每个方法中包含的初始化方法.

祝好运!这有什么需要进一步解释的吗?

  • 好答案.选项2是最好的.选项1失控并迫使您在可以使用默认设置时进行设置.选项3打破了java-bean约定.在为测试设置复杂对象时,我们广泛使用选项2. (3认同)
  • 选项2是我的最爱.如果您正在使用Eclipse并让它生成getter/setter方法,您可以将Java代码模板更改为:$ {field} = $ {param}; 归还这个; (2认同)

Fil*_*lip 10

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

但是当你想告诉用户她必须调用一个构建器方法或另一个构建器方法时会发生什么,因为它对于你正在尝试构建的类是至关重要的.

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

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