Java 如何以优雅的方式初始化包含大量字段的类?

Java 如何以优雅的方式初始化包含大量字段的类?,java,coding-style,builder,code-cleanup,object-construction,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();

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

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

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
    } 
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
}
我真的希望避免编写这样的代码,因为必须单独实例化每个对象,然后跨多行代码初始化每个字段(例如,必须调用
new Offer()
,然后
setHotelOnly(hotelOnly)
然后
add(Offer)
)会有点麻烦

我可以用什么优雅的方法来代替我所拥有的?是否有可以使用的“
工厂”
?您是否有任何参考/示例来避免编写这样的代码

我对实现干净的代码非常感兴趣


背景:

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

API表示为一个
xsd模式
文件,我使用
JAXB

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


感谢您的帮助。

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

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

理解您的示例:

假设
提供
只是一个包含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.
}
在您的示例中,要为这些字段设置值,请使用setter:

    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()
)对于初始化类中字段的值非常有用

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);
        }
    }
}
使用构造函数的
Offer
版本如下所示:

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

如果希望为任何字段提供默认值的选项,则A非常有用

生成器模式解决的问题是以下混乱的代码:

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

}
看看那会变得多混乱

生成器模式是您放在类中的另一个类

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:生成器模式的变化

您还可以通过简单地使当前setter返回相同的Offer对象来使用builder模式。它完全相同,只是没有额外的
OfferBuilder

警告:由于此选项中断。所以,你不应该为了专业目的而使用它,而应该在你自己的实践中限制你的使用

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());
最后一个报价只是一个包含所有默认值的报价。除我设置的值外,其他值都是默认值


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


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

我一直倾向于使用,因为它提供的远不止构建器模式的基本方法

但是,当您想告诉用户必须调用一个或另一个构建器方法时,会发生什么情况,因为这对于您试图构建的类至关重要

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

不管怎样,我不知道这对你的特殊问题是否有意义,但我认为值得一提的是让其他人看看。

这里已经给出了一些很好的答案

我想到的是一个加法。指定零件,包括实体、值对象、集合、工厂等

(pdf)中给出了一个很好的介绍。

我之所以提供这个答案,是因为在一篇评论中提到了它,我认为它也应该是设计模式枚举的一部分


空对象设计模式 意图

空对象的目的是通过提供提供合适的默认不做任何事情行为的可替代替代方案来封装对象的缺失。简言之,一个“从无到有”的设计

在以下情况下使用空对象模式:

  • 对象需要协作者。空对象模式不引入这种协作——它使用已经存在的协作
  • 某些协作者实例不应执行任何操作
  • 您希望从客户机中抽象null的处理


理想情况下,对象不应该关心实例化其依赖项。它应该只关心我所关心的事情