In my application, I have to instantiate many different types of objects. Each type contains some fields and needs to be added to a containing type. How can I do this in an elegant way?
My current initialization step looks something like this:
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 }
I really want to avoid writing code like this, because it's a little messy having to instantiate each object separately and then initialize each field across multiple lines of code (e.g. having to call new Offer()
and then setHotelOnly(hotelOnly)
and then add(offer)
).
What elegant methods can I use instead of what I have? Are there any "Factories
" that can be used? Do you have any references/examples to avoid writing code like this?
I'm really interested in implementing clean code.
Context:
I'm developing a RestClient
Application for sending post requests to a Webservice.
The API is represented as a xsd schema
file and I created all the Objects with JAXB
Before sending a request I have to instantiate many Objects because they have dependencies with each other. (An Offer has Hotels, a Hotel has Rooms, a Room has Persons... And these Classes are the generated ones)
Thanks for your help.
There are two ways to initialize a class object: Using a parenthesized expression list. The compiler calls the constructor of the class using this list as the constructor's argument list. Using a single initialization value and the = operator.
The class file format contains a list of field declarations whose size is an unsigned short, hence could allow 65535 declarations, but it is not possible to declare 65535 fields in practice.
To initialize a class member variable, put the initialization code in a static initialization block, as the following section shows. To initialize an instance member variable, put the initialization code in a constructor.
Note: It is not necessary to declare fields at the beginning of the class definition, although this is the most common practice. It is only necessary that they be declared and initialized before they are used.
You can either use a constructor or a builder pattern or a variation of the builder pattern to fix the problem of having too many fields in your initialization step.
I'm going to extend your example a bit to prove my point of why these options are useful.
Understanding your example:
Lets say an Offer
is simply a container class for 4 fields:
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. }
As it stands in your example, to set values to these fields, you use setters:
public void setHotelOnly(HotelOnly hotelOnly) { this.hotelOnly = hotelOnly; }
Unfortunately, this means if you need an offer with values in all of the fields, you have to do what you have:
Offers offers = new Offers(); Offer offer = new Offer(); offer.setPrice(price); offer.setDateOfOffer(date); offer.setDuration(duration); offer.setHotelOnly(hotelOnly); offers.add(offer);
Now let's look at improving this.
Option 1: Constructors!
A constructor other than the default constructor (the default constructor is currently Offer()
) is useful for initializing the values of the fields in your class.
A version of Offer
using constructors would look like this:
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 }
Now, we can initialize it in one line!
Offers offers = new Offers(); Offer offer = new Offer(price, date, duration, hotelOnly); offers.add(offer);
Even better, if you never use offer
other than that single line: offers.add(offer);
you don't even need to save it in a variable!
Offers offers = new Offers(); offers.add( new Offer(price, date, duration, hotelOnly) ); // Works the same as above
Option 2: Builder Pattern
A builder pattern is useful if you want the option of having default values for any of your fields.
The problem a builder pattern solves is the following messy code:
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 }
See how messy that can get?
The builder pattern is another class that you put inside your class.
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); } } }
Now, you can not have to have so many constructors, but still are able to choose which values you want to leave default, and which you want to initialize.
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());
That last offer is simply one with all default values. The others are default values except the ones that I set.
See how that makes things easier?
Option 3: Variation of Builder Pattern
You can also use the builder pattern by simply making your current setters return the same Offer object. It's exactly the same, except without the extra OfferBuilder
class.
Warning: As user WW states below, this option breaks JavaBeans - a standard programming convention for container classes such as Offer. So, you shouldn't use this for professional purposes, and should limit your use in your own practices.
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 }
And your new initialization code is
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());
That last offer is simply one with all default values. The others are default values except the ones that I set.
So, while it's a lot of work, if you want to clean up your initialization step, you need to use one of these options for each of your classes that have fields in them. Then use the initialization methods that I included with each method.
Good luck! Does any of this need further explanation?
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With