DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports Events Over 2 million developers have joined DZone. Join Today! Thanks for visiting DZone today,
Edit Profile Manage Email Subscriptions Moderation Admin Console How to Post to DZone Article Submission Guidelines
View Profile
Sign Out
Refcards
Trend Reports
Events
Zones
Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones AWS Cloud
by AWS Developer Relations
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones
AWS Cloud
by AWS Developer Relations
  1. DZone
  2. Coding
  3. Languages
  4. Value Objects

Value Objects

Value objects let you transform values into objects themselves. That provides a number of benefits, but with the potential risk of bloating your project.

Grzegorz Ziemoński user avatar by
Grzegorz Ziemoński
·
Apr. 12, 17 · Tutorial
Like (25)
Save
Tweet
Share
80.57K Views

Join the DZone community and get the full member experience.

Join For Free

Since I’ve already set you up in a DDD mood with the previous post, let’s capitalize on that further. In this post, I’ll try to provide an easy explanation of Value Objects and their benefits.

Motivation

Most of the concepts we’re modeling in our software have no global identity. If you wonder what I meant with the previous sentence, look inside your entity classes. Each of them contains a bunch of fields, usually represented by standard types such as String or BigDecimal, or by simple data structures. These, without the context of the enclosing entity, cannot be distinguished from other Strings, BigDecimals, or structures. We don’t identify them by ID of any kind; we identify them only by their values.

For example, if we were talking about a Person class, we would probably see fields like firstName, lastName, or address. A person has a clear, global identity. If there was an object representing me, the object would refer to a real guy sitting at work, finishing a blog post and waiting to go home – I’m an entity. On the other hand, if we were talking only about a String like “Grzegorz”, we don’t have this global scope – it’s just a value.

Values like the ones mentioned above can have certain logic associated with them e.g. validation, transformations or calculus. As we’re using an OO language, it makes all the sense in the world to use its powers and combine the value and the logic together in an object.

Value Objects

The Value Objects pattern does just that – it transforms our values into objects. Everything boils down to wrapping the standard types inside our own, named by the concept their representing:

// entity:

class Person {
    PersonId id; // global identity
    FirstName firstName;
    LastName lastName;
    Address address;
}

// value objects:

class PersonId {
    Long value; 
}

class FirstName {
    String value;
}

class LastName {
    String value;
}

class Address {
    String street;
    String streetNo;
    String city;
    String postalCode;
}


As the value objects have no identity, we compare them together by simply comparing all the values they contain:

// Address

@Override
public boolean equals(Object o) {
    // basic checks and casting cut out for brevity
    return Objects.equals(street, address.street) &&
            Objects.equals(streetNo, address.streetNo) &&
            Objects.equals(city, address.city) &&
            Objects.equals(postalCode, address.postalCode);
}

@Override
public int hashCode() {
    return Objects.hash(street, streetNo, city, postalCode);
}


Usually, we also make/treat the value objects as immutable, i.e. instead of changing the value objects, we create new instances that wrap the new values:

// wrong:
this.address.setStreet(event.street);

// good:
this.address = new Address(event.street, ...);


There are practical and conceptual reasons behind this. From the practical perspective, immutable types are handy, as they can be easily shared between different objects and returned by the entities without the risk of compromising consistency. From the conceptual perspective, it makes sense to create a new instance of a value object when the value changes, as we’re literally assigning a new value.

Benefits

For a person unfamiliar with the concept, this might look like heavy overengineering. In reality, value objects have some nice advantages:

  • The code gets more expressive:
    // without:
    Map<Long, String>
    
    // with:
    Map<PersonId, PhoneNumber>
  • They make our code safer, as the type system prevents us from doing stupid mistakes:
    Person(String firstName, String lastName, String email) { ... }
    
    new Person("john@doe.com", "John", "Doe"); // compiles
    
    Person(FirstName firstName, LastName lastName, Email email) { ... }
    
    new Person(
        new Email("john@doe.com"),
        new FirstName("John"),
        new LastName("Doe")); // doesn't compile!
  • They give us the flexibility in terms of internal representation. For example, I could easily change...:
    class PersonId {
        Long id;
    
        // c-tor, getter
    }

    ...to...:
    class PersonId {
        String value;
    
        public PersonId(Long value) {
            this.value = value.toString();
        }
    
        // rest
    }

    ...without changing most of the PersonId  clients.
  • As mentioned, they also encapsulate related logic e.g. validation:
    class Email {
        String value;
    
        Email(String value) {
            if (!value.contains("@")) { // don't use this code in a real product
                throw new InvalidEmailException(value);
            }
            this.value = value;
        }
    }

    Or calculus:
    class Money {
        BigDecimal amount;
    
        Money(BigDecimal amount) {
            this.amount = amount;
        }
    
        Money add(Money other) {
            // this is cheesy, but in reality, it could also handle currencies and stuff
            return new Money(amount + other.amount);
        }
    
        // etc.
    }

Drawbacks

The obvious drawbacks of value objects are that the numbers of classes in the project might grow significantly and, as they’re usually immutable, the number of created objects, too. While I wouldn’t initially care about the performance, as in most cases it would not be a bottleneck, I’d be extremely careful not to overdo the concept and bloat the project with a ton of String wrappers that do nothing but wrapping. In my projects, I create value objects only when I see that there is a piece of logic to encapsulate.

Implementing Value Objects

All the examples I’ve shown by now were in pseudo-Java, as their purpose was to convey the idea rather than show all the guts. Now we’ll look at three real implementation examples for value objects – using plain Java, Project Lombok, and JPA.

Plain Java

If we’re not using libraries like Lombok or Immutables, and we don’t use JPA for persistence, we can implement value objects in plain Java. Basically, this boils down to implementing an immutable class by ourselves and providing an equals method that compares fields by values. As a reminder, to create such an immutable class, we have to:

  • Make it final 
  • Make all the fields final (this also implies no field mutation in methods and creating new objects when someone wants a value object with different data)
  • Copy all mutable state during construction and retrieval
public final class DateRange {
    private final Date fromInclusive;
    private final Date toExclusive;

    public DateRange(Date fromInclusive, Date toExclusive) {
        this.fromInclusive = (Date) fromInclusive.clone();
        this.toExclusive = (Date) toExclusive.clone();
    }

    public DateRange extend(long millis) {
        Date extendedTo = new Date(toExclusive.getTime() + millis);
        return new DateRange(fromInclusive, extendedTo);
    }

    public Date getFromInclusive() {
        return (Date) fromInclusive.clone();
    }

    public Date getToExclusive() {
        return (Date) toExclusive.clone();
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        DateRange dateRange = (DateRange) o;

        return fromInclusive.equals(dateRange.fromInclusive)
                && toExclusive.equals(dateRange.toExclusive);
    }

    @Override
    public int hashCode() {
        int result = fromInclusive.hashCode();
        result = 31 * result + toExclusive.hashCode();
        return result;
    }
}


Project Lombok

Many of us use plugins like Project Lombok or Immutables to limit the amount of boilerplate code we need to write. The former has a @Value annotation designed specifically to support easy creation of value objects — it auto-generates private and final keywords, a constructor, getters, and equals/hashcode methods. It’s important to know that it does not support defensive-copying, which may force us to use different types than we’d normally do. In our example, we have to use Instant instead of Date.

@Value
public class DateRange {
    Instant fromInclusive;
    Instant toExclusive;

    public DateRange extend(long millis) {
        return new DateRange(fromInclusive, toExclusive.plusMillis(millis));
    }
}


JPA

It’s common for developers store their value objects in a relational database using JPA. In such a case, we need to relax the immutability constraints and add some annotations. Since the implementation can’t make the fields final, we have to be more careful about people not adding setter methods to the value objects.

@Embeddable
public final class DateRange {
    @Temporal(TemporalType.TIMESTAMP)
    private Date fromInclusive;
    @Temporal(TemporalType.TIMESTAMP)
    private Date toExclusive;

    public DateRange() {
        // only for JPA
    }

    public DateRange(Date fromInclusive, Date toExclusive) {
        this.fromInclusive = (Date) fromInclusive.clone();
        this.toExclusive = (Date) toExclusive.clone();
    }

    // rest as in Plain Java example
}

Summary

The Value Objects pattern transforms values in our projects into real objects, giving us more type safety, hiding implementation, and giving a home to all related logic. That being said, we should always evaluate if the mentioned benefits outweigh the drawbacks of creating extra classes, which, in Java, implies extra source files and a rapidly growing size of the project. To implement a value object, we simply wrap a value into an immutable class with an equals/hashcode pair that compares the objects by values.

Object (computer science)

Published at DZone with permission of Grzegorz Ziemoński, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

Popular on DZone

  • Top 10 Best Practices for Web Application Testing
  • Old School or Still Cool? Top Reasons To Choose ETL Over ELT
  • Seamless Integration of Azure Functions With SQL Server: A Developer's Perspective
  • A Beginner's Guide to Infrastructure as Code

Comments

Partner Resources

X

ABOUT US

  • About DZone
  • Send feedback
  • Careers
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 600 Park Offices Drive
  • Suite 300
  • Durham, NC 27709
  • support@dzone.com
  • +1 (919) 678-0300

Let's be friends: