32

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

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

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()我真的很想避免编写这样的代码,因为必须分别实例化每个对象然后跨多行代码初始化每个字段(例如必须调用thensetHotelOnly(hotelOnly)和 then )有点麻烦add(offer)

我可以使用哪些优雅的方法来代替我拥有的方法?有没有Factories可以用的“”?您是否有任何参考/示例来避免编写这样的代码?

我真的对实现干净的代码很感兴趣。


语境:

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

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

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

谢谢你的帮助。

4

6 回答 6

48

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

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

了解您的示例:

可以说 anOffer只是 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:构建器模式

如果您希望选择为任何字段设置默认值,构建器模式很有用。

构建器模式解决的问题是以下凌乱的代码:

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

您还可以通过简单地使当前设置器返回相同的 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());

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


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

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

于 2015-10-13T15:35:58.163 回答
10

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

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

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

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

于 2015-10-19T08:37:12.313 回答
3

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

作为补充,我想到的是领域驱动设计。具体的Building blocks部分,包括EntityValue ObjectAggregateFactory等。

Domain Driven Design - Quickly (pdf)中给出了很好的介绍。

于 2015-10-21T08:23:08.647 回答
1

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


空对象设计模式

意图

Null Object 的目的是通过提供可替代的替代方案来封装对象的缺失,该替代方案提供合适的默认不做任何行为。简而言之,“一无所有”的设计

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

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

在这里您可以找到“空对象”设计模式的完整部分

于 2015-10-21T12:31:07.470 回答
1

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

这个想法很简单,您声明依赖项并让框架决定何时/如何/在何处创建它们并将其“注入”到您的类中。

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

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

于 2015-10-22T10:45:31.563 回答
0

Dozer 框架提供了将值从 ws 对象复制到 dto 的好方法。这是另一个例子。此外,如果两个类的 getter/setter 名称相同,则不需要自定义转换器

于 2015-10-22T14:54:01.727 回答