在我的应用程序中,我必须实例化许多不同类型的对象。每个类型都包含一些字段,需要添加到包含类型中。我怎样才能以一种优雅的方式做到这一点?
我当前的初始化步骤看起来像这样:
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
应用程序用于发送post请求到Webservice。
API表示为xsd schema
文件,我用JAXB
创建了所有对象
在发送请求之前,我必须实例化许多对象,因为它们彼此依赖。Offer有Hotels, Hotel有Rooms, Room有Persons…这些类是生成的
谢谢你的帮助。
您可以使用构造函数或构建器模式或构建器模式的变体来解决初始化步骤中字段过多的问题。
我将扩展一下你的例子来证明我的观点,为什么这些选项是有用的。
理解你的例子:
假设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);
甚至更好,如果你从不使用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
}
看到有多乱了吗?
构建器模式是另一个将放在类中的类。
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:Builder模式的变化
您还可以通过简单地使当前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,因为它提供了比builder模式的基本方法更多的功能。
但是当你想告诉用户她必须调用一个或另一个构建器方法,因为它对你试图构建的类至关重要时,会发生什么呢?
考虑一个URL组件的构建器。如何考虑封装URL属性访问的构建器方法,它们是否同样重要,它们是否相互交互,等等?虽然查询参数或片段是可选的,但主机名不是;你可以说协议也是必需的,但你可以有一个有意义的默认值,比如http对吧?
无论如何,我不知道这是否对你的特殊问题有意义,但我认为值得一提的是,其他人可以看看它。这里已经给出了一些不错的答案!
我想到的是领域驱动设计。指定构建块部分,使用实体、值对象、聚合、工厂等。
在领域驱动设计-快速(pdf)中给出了一个很好的介绍。
我只是提供这个答案,因为它在评论中被提到,我认为它也应该是这个设计模式枚举的一部分。
空对象设计模式
Null对象的目的是通过提供一个可替代的替代方案来封装对象的缺失,该替代方案提供合适的默认不做任何行为。简而言之,一个"无中生有"的设计
使用Null对象模式- 对象需要一个合作者。空对象模式不引入这种协作——它利用已经存在的协作
- 一些合作者实例应该什么都不做
- 你想从客户端抽象空的处理
在这里您可以找到"空对象"设计模式的完整部分
理想情况下,对象不应该关心实例化它的依赖项。它应该只担心它应该和它们一起做的事情。你考虑过依赖注入框架吗?Spring或谷歌的Juice功能齐全,占地面积小。
这个想法很简单,你声明依赖项,让框架决定何时/如何/在哪里创建它们,并将其"注入"到你的类中。
如果你不想使用任何框架,你可以从他们那里得到设计笔记,并尝试模仿他们的设计模式,并根据你的用例进行调整。
同样,您可以通过正确使用集合在一定程度上简化事情。例如,除了存储Offer
的集合之外,Offers
还有什么其他特性?我不确定你的约束是什么但是,如果你能让那部分更清晰一点你就会在实例化对象的所有地方获得巨大的收益。
Dozer框架提供了很好的方法来从ws对象复制值到dto。这是另一个例子。此外,如果两个类的getter/setter名称相同,则不需要自定义转换器