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
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

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
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

The software you build is only as secure as the code that powers it. Learn how malicious code creeps into your software supply chain.

Apache Cassandra combines the benefits of major NoSQL databases to support data management needs not covered by traditional RDBMS vendors.

Generative AI has transformed nearly every industry. How can you leverage GenAI to improve your productivity and efficiency?

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workloads.

Related

  • Cutting-Edge Object Detection for Autonomous Vehicles: Advanced Transformers and Multi-Sensor Fusion
  • Writing DTOs With Java8, Lombok, and Java14+
  • Graph API for Entra ID (Azure AD) Object Management
  • A Comprehensive Guide to IAM in Object Storage

Trending

  • Building Resilient Identity Systems: Lessons from Securing Billions of Authentication Requests
  • Unlocking Data with Language: Real-World Applications of Text-to-SQL Interfaces
  • Mastering Fluent Bit: Installing and Configuring Fluent Bit on Kubernetes (Part 3)
  • Can You Run a MariaDB Cluster on a $150 Kubernetes Lab? I Gave It a Shot
  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.

By 
Grzegorz Ziemoński user avatar
Grzegorz Ziemoński
·
Updated Apr. 12, 17 · Tutorial
Likes (29)
Comment
Save
Tweet
Share
88.4K 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.

Related

  • Cutting-Edge Object Detection for Autonomous Vehicles: Advanced Transformers and Multi-Sensor Fusion
  • Writing DTOs With Java8, Lombok, and Java14+
  • Graph API for Entra ID (Azure AD) Object Management
  • A Comprehensive Guide to IAM in Object Storage

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

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

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!