如何以优雅的方式初始化具有大量字段的类?
在我的应用程序中,我必须实例化许多不同类型的对象。 每种类型都包含一些字段,需要添加到包含类型中。 我怎样才能以优雅的方式做到这一点?
我当前的初始化步骤看起来像这样:
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
”? 你有没有任何参考/例子来避免编写这样的代码?
我真的很想实现干净的代码。
语境:
我正在开发一个RestClient
应用程序,用于将发布请求发送到RestClient
。
API表示为xsd schema
文件,我使用JAXB
创建了所有对象
在发送请求之前,我必须实例化许多对象,因为它们彼此之间存在依赖关系。 (优惠有酒店,酒店有房间,房间有人……这些类是生成的)
谢谢你的帮助。
您可以使用构造函数或构建器模式或构建器模式的变体来解决初始化步骤中包含太多字段的问题。
我将扩展你的例子,以certificate我为什么这些选项有用的观点。
理解你的例子:
可以说, 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. }
在您的示例中,要为这些字段设置值,请使用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()
)对于初始化类中字段的值很有用。
使用构造函数的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);
更好的是,如果你从不使用除单一行offers.add(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 }
看看它有多乱?
构建器模式是您放入类中的另一个类。
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对象来使用构建器模式。 除了没有额外的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());
最后一个报价只是一个包含所有默认值的报价。 其他是默认值,除了我设置的值。
因此,虽然这是很多工作,但如果要清理初始化步骤,则需要为每个包含字段的类使用其中一个选项。 然后使用我在每个方法中包含的初始化方法。
祝你好运! 这有什么需要进一步解释的吗?
我总是喜欢使用builder-pattern-with-a-twist,因为它提供的不仅仅是构建器模式的基本方法。
但是当你想告诉用户她必须调用一个构建器方法或另一个构建器方法时会发生什么,因为它对于你正在尝试构建的类是至关重要的。
考虑一个URL组件的构建器。 如何考虑封装对URL属性的访问的构建器方法,它们同样重要,它们是否相互交互等等? 虽然查询参数或片段是可选的,但主机名不是; 你可以说协议也是必需的,但为此你可以有一个有意义的默认值,比如http对吗?
无论如何,我不知道这对你的特定问题是否有意义,但我认为值得一提的是让其他人看看它。
这里已经给出了一些很好的回答!
我想到的另一个问题是Domain Driven Design 。 具体的构建块部分,包括实体 , 值对象 , 聚合 , 工厂等。
域驱动设计 – 快速 (pdf)给出了一个很好的介绍。
我只是提供这个答案,因为它在评论中提到过,我认为它也应该是这个枚举设计模式的一部分。
空对象设计模式
意图
Null对象的意图是通过提供可替代的替代方法来封装缺少对象,该替代方案提供合适的默认不执行任何行为。 简而言之,一个“没有任何东西将无所作为”的设计
使用Null对象模式时
- 一个对象需要一个协作者。 Null对象模式不会引入此协作 – 它使用已存在的协作
- 一些协作者实例应该什么都不做
- 你想要从客户端抽象出null的处理
在这里,您可以找到“Null Object”设计模式的完整部分
理想情况下,对象不应该关心实例化其依赖关系。 它应该只担心它应该对它们做的事情。 你考虑过任何dependency injection框架吗? Spring或Google’s Juicefunction多样,占地面积小。
这个想法很简单,你声明了依赖关系并让框架决定何时/如何/在哪里创建它们并将它“注入”你的类。
如果您不想使用任何框架,您可以从中获取设计说明,并尝试模拟其设计模式并根据您的用例进行调整。
此外,您可以通过正确使用集合来在一定程度上简化事物。 例如,除了存储商品集合之外,商品还有哪些附加function? 我不确定你的约束是什么,但是,如果你可以使那部分更清洁,你将在实例化对象的所有地方获得巨大的收益。
Dozer框架提供了将值从ws对象复制到dto的好方法。 这是另一个例子 。 另外,如果getter / setter名称与两个类相同,则不需要自定义转换器