Skip to content

How to implement the Equatable interface / Equals or SameValueAs method in value objects #61

@webdevilopers

Description

@webdevilopers

Here are some examples from the PHP world inside for instance from the @dddinphp book under "Value Equality":

Or by @spriebsch:

{
  public function equals(Money $money)
  {
    if ($this->currency != $money->getCurrency()) {
      return false;
    }

    return $this->amount = $money->getAmount();
  }
}

Or by @lorenzomar :

public function sameValueAs(ValueObjectInterface $valueObject)
{
     return $this->iso31661Alpha2Code->sameValueAs($valueObject->iso31661Alpha2Code()) && 
        $this->iso31661Alpha3Code->sameValueAs($valueObject->iso31661Alpha3Code()) && 
        $this->englishName->sameValueAs($valueObject->englishName()) && 
        $this->phoneNumberPrefix->sameValueAs($valueObject->phoneNumberPrefix);
 }

Unfortunately implementing the interface in PHP can get tricky because of type-hinting.

Here is a SWIFT example by @twostraws:

struct User: Equatable {
    let value: String

    init?(string: String) {
        guard string.trimmingCharacters(in: .whitespacesAndNewlines).count >= 3 else {
            return nil
        }

        let illegalCharacters = ["@", "-", "&", "."]
        guard illegalCharacters.contains(where: string.contains) == false else {
            return nil
        }

        self.value = string
    }
}

Some thoughts from the .NET world by @jbogard:

Override the Equals method, to implement equality instead of identity, which is the default Additionally, Framework Design Guidelines has some additional requirements I must meet:
Provide a reflexive, transitive, and symmetric implementation of Equals
...
Implement IEquatable
Override the equality operators
Generic Implementation

What I wanted was a base class that would give me all of the Framework Design Guidelines requirements as well as the Domain Driven Design requirements, without any additional logic from concrete types. Here’s what I ended up with:

public abstract class ValueObject<T> : IEquatable<T>
    where T : ValueObject<T>
{
    public override bool Equals(object obj)
    {
        if (obj == null)
            return false;

        T other = obj as T;

        return Equals(other);
    }

IEquatable of T is designed to avoid boxing/unboxing of .NET value types during equality comparison. Its main purpose is to be used on structs.
In classes, there's no benefit in implementing Equals(MyValueObject obj) over the standard Equals(object obj), hence I don't use this interface due to YAGNI.

  • Unknown source

@EresDev states:

With improvement in my ideas, experience and knowledge, I no longer agree with having an interface for a Value Object. I don’t write interface for Value Objects anymore. The most solid reason for this is given by Kent Beck.

Some final thoughts by @vkhorikov:

There's no need in implementing IEquatable, this interface was introduced specifically to avoid boxing/unboxing of .NET value types (structs) when dealing with comparison.
As long as you implement ValueObject as class, you can safely omit implementing IEquatable.

Related discussion on Twitter:

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions