Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

An Unusual Way to Write Cleaner Code With Instance Variables

DZone's Guide to

An Unusual Way to Write Cleaner Code With Instance Variables

Are getters and setters always necessary? Sometimes you need to go off the beaten path for more readable, cleaner Java code.

· Java Zone
Free Resource

The single app analytics solutions to take your web and mobile apps to the next level.  Try today!  Brought to you in partnership with CA Technologies

Generally, I see projects in the Java platform that have some classes define data to be transfered between abstractions (high-level to low-level and vice versa), layers (upstream to downstream and vice versa), or boundaries (subsystems). These kinds of classes are called data structure classes and they are characterized by not having behaviors. That is, they only store data and makes it accessible to be read.

First Implementation: Mutable Through Getters/Setters

The most common implementation of the data structure class uses the famous Java Beans convention, where we declare getters and setters associated with the private instance variables. Below is an example of this implementation:

public class AccessoryDTO {
    private Integer quantity;
    private String description;
    public AccessoryDTO(Integer quantity, String description) {
        this.quantity = quantity;
        this.description = description;
    }

    public Integer getQuantity() {
        return quantity;
    }
    public void setQuantity(Integer quantity) {
        this.quantity = quantity;
    }
    public String getDescription() {
        return description;
    }
    public void setDescription(String description) {
        this.description = description;
    }
}

public class OwnerDTO {
    private String firstName;
    private String lastName;
    private Integer age;
    public OwnerDTO(String firstName, String lastName, Integer age) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.age = age;
    }

    public String getFirstName() {
        return firstName;
    }
    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }
    public String getLastName() {
        return lastName;
    }
    public void setLastName(String lastName) {
        this.lastName = lastName;
    }
    public Integer getAge() {
        return age;
    }
    public void setAge(Integer age) {
        this.age = age;
    }
}

public class CarDTO {
    private Long id;
    private String make;
    private Integer year;
    private String model;
    private BigDecimal price;
    private OwnerDTO owner;
    private List<AccessoryDTO> accessories;
    public CarDTO(Long id, String make, Integer year, String model, 
            BigDecimal price, OwnerDTO owner, List<AccessoryDTO> accessories) {
        this.id = id;
        this.make = make;
        this.year = year;
        this.model = model;
        this.price = price;
        this.owner = owner;
        this.accessories = accessories;
    }

    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public String getMake() {
        return make;
    }
    public void setMake(String make) {
        this.make = make;
    }
    public Integer getYear() {
        return year;
    }
    public void setYear(Integer year) {
        this.year = year;
    }
    public String getModel() {
        return model;
    }
    public void setModel(String model) {
        this.model = model;
    }
    public BigDecimal getPrice() {
        return price;
    }
    public void setPrice(BigDecimal price) {
        this.price = price;
    }
    public OwnerDTO getOwner() {
        return owner;
    }
    public void setOwner(OwnerDTO owner) {
        this.owner = owner;
    }
    public List<AccessoryDTO> getAccessories() {
        return accessories;
    }
    public void setAccessories(List<AccessoryDTO> accessories) {
        this.accessories = accessories;
    }
}

Second Implementation: Immutable Through Getters

To improve the above code, we might refactor the DTO by switching the mutable implementation by the immutable implementation. This new implementation provides a safe way to exchange data where none of the sides can change it. Below is the refactored code:

public class AccessoryDTO {
    private final Integer quantity;
    private final String description;
    public AccessoryDTO(Integer quantity, String description) {
        this.quantity = quantity;
        this.description = description;
    }

    public Integer getQuantity() {
        return quantity;
    }
    public String getDescription() {
        return description;
    }
}

public class OwnerDTO {
    private final String firstName;
    private final String lastName;
    private final Integer age;
    public OwnerDTO(String firstName, String lastName, Integer age) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.age = age;
    }

    public String getFirstName() {
        return firstName;
    }
    public String getLastName() {
        return lastName;
    }
    public Integer getAge() {
        return age;
    }
}

public class CarDTO {
    private final Long id;
    private final String make;
    private final Integer year;
    private final String model;
    private final BigDecimal price;
    private final OwnerDTO owner;
    private final List<AccessoryDTO> accessories;
    public CarDTO(Long id, String make, Integer year, String model, 
            BigDecimal price, OwnerDTO owner, List<AccessoryDTO> accessories) {
        this.id = id;
        this.make = make;
        this.year = year;
        this.model = model;
        this.price = price;
        this.owner = owner;
        this.accessories = Collections.unmodifiableList(accessories);
    }

    public Long getId() {
        return id;
    }
    public String getMake() {
        return make;
    }
    public Integer getYear() {
        return year;
    }
    public String getModel() {
        return model;
    }
    public BigDecimal getPrice() {
        return price;
    }
    public OwnerDTO getOwner() {
        return owner;
    }
    public List<AccessoryDTO> getAccessories() {
        return accessories;
    }
}

Now what we need is to just use the refactored code like this method below:

public String displayFirstNameOwner(Integer carId) {
     CarService carService = new CarService();
     CarDTO carDTO = carService.findById(carId);
     String ownerFirstName = carDTO.getOwner().getFirstName();
     return ownerFirstName;
 }

Notice the return statement of the snippet above and the DTO classes. Why not create immutable classes through instance variables when they are only data structures? Why are we tied to the resources offered by IDEs and always generate getters and/or setters for convenience? Is there something we can do to improve this code?

Third Implementation: Immutable Through Instance Variables

So,         I think we can improve this code to declare public instance variables and remove getter methods to thereby transform these classes in cleaner data structures. I see your pertinent adoption when they are completely immutable and they will not become objects with behaviors. Below is the refactored code again:

public class AccessoryDTO {
    public final Integer quantity;
    public final String description;
    public AccessoryDTO(Integer quantity, String description) {
        this.quantity = quantity;
        this.description = description;
    }
}

public class OwnerDTO {
    public final String firstName;
    public final String lastName;
    public final Integer age;
    public OwnerDTO(String firstName, String lastName, Integer age) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.age = age;
    }
}

public class CarDTO {
    public final Long id;
    public final String make;
    public final Integer year;
    public final String model;
    public final BigDecimal price;
    public final OwnerDTO owner;
    public final List<AccessoryDTO> accessories;
    public CarDTO(Long id, String make, Integer year, String model, 
            BigDecimal price, OwnerDTO owner, List<AccessoryDTO> accessories) {
        this.id = id;
        this.make = make;
        this.year = year;
        this.model = model;
        this.price = price;
        this.owner = owner;
        this.accessories = Collections.unmodifiableList(accessories);
    }
}

And we can use DTOs like below:

public String displayFirstNameOwner(Integer carId) {
    CarService carService = new CarService();
    CarDTO carDTO = carService.findById(carId);
    String ownerFirstName = carDTO.owner.firstName;
    return ownerFirstName;
}
This third implementation has the following advantages in my opinion:
  • Cleaner code

  • Better readability

  • Promotes less code to be written and maintained

  • It's in compliance with the Law of Demeter

There are some disadvantages too:

  • It doesn't follow the Java Beans conventions.

  • It requires that all instance variables are immutable. It means that the children instance variable objects need to be immutable too.

  • It can have incompatibilities with third-party libraries responsible for tasks like translating ORM instances to DTO instances.

Thus, we can spend our time in lines of code like these below, where I tried to increase the readability and avoid the nullability:

public class OwnerDTO {
    public final String firstName;
    public final String lastName;
    public final Integer age;
    public OwnerDTO(String firstName, String lastName, Integer age) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.age = age;
    }
    private OwnerDTO() {
        this("", "", 0);
    }

    public static OwnerDTO nullObject() {
        return new OwnerDTO();
    }
    ...
}

...

public class CarDTO {
    public final Long id;
    public final String make;
    public final Integer year;
    public final String model;
    public final BigDecimal price;
    public final OwnerDTO owner;
    public final List<AccessoryDTO> accessories;
    private CarDTO(Long id, String make, Integer year, String model, 
            BigDecimal price, OwnerDTO owner, List<AccessoryDTO> accessories) {
        this.id = id;
        this.make = make;
        this.year = year;
        this.model = model;
        this.price = price;
        this.owner = owner;
        this.accessories = Collections.unmodifiableList(accessories);
    }
    private CarDTO() {
        this(0L, "", 0, "", BigDecimal.ZERO, 
            OwnerDTO.nullObject(), new ArrayList<AccessoryDTO>(0));
    }
    public CarDTO(Long id, String make, Integer year, String model, BigDecimal price) {
        this(id, make, year, model, price, 
            OwnerDTO.nullObject(), new ArrayList<AccessoryDTO>(0));
    }

    public static CarDTO nullObject() {
        return new CarDTO();
    }
    public CarDTO withOwner(OwnerDTO owner) {
        return new CarDTO(id, make, year, model, price, owner, accessories);
    }
    public CarDTO withAccessories(List<AccessoryDTO> accessories) {
        return new CarDTO(id, make, year, model, price, owner, accessories);
    }
    ...
}

Additionally, the snippet below shows how to create instances of the CarDTO class:

CarDTO car = new CarDTO(1L, "Foo", 2010, "A380", new BigDecimal(15000.0))
    .withOwner(new OwnerDTO("Gustavo", "Gomes", 30))
    .withAccessories(new ArrayList<AccessoryDTO>(0));
CarDTO anotherCar = CarDTO.nullObject();
if (anotherCar.equals(car))
    System.out.println("Equal");
else
    System.out.println("Not equal");

Conclusion

I know that the third implementation can be seen as an odd alternative considering we are accustomed to write getters and setters, but in some cases we can give up conventions in exchange for better readability and cleaner code.

To be clear, I'm not saying to ignore the Java conventions, the use of interfaces, or the use of getters.

CA App Experience Analytics, a whole new level of visibility. Learn more. Brought to you in partnership with CA Technologies.

Topics:
java ,data structure ,data transfer ,oop ,clean code

Opinions expressed by DZone contributors are their own.

THE DZONE NEWSLETTER

Dev Resources & Solutions Straight to Your Inbox

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.

X

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}