0

I have 2 "user" classes which are essentially the same except one is immutable without setters and one is mutable with setters. I have a separate factory for creating each object.

  • ImmutableUserDto - ImmutableUserDtoFactory
  • MutableUserDto - MutableUserDtoFactory

Both factories take in an array of data and pass it to the class when instantiating it. Then each object class' constructor sets the object from the array passed in, eg:
(simplified code for example)

class MutableUserDtoFactory
{
    public function create(array $userData): MutableUserDto
    {
        return new MutableUserDto($userData);
    }
}

class MutableUserDto
{
    private $name;

    public function __construct($userData)
    {
        $this->name = $userData['name'];
    }

    public function getName()
    {
        return $this->name;
    }

    public function setName($name)
    {
        $this->name = $name;
    }
}

The immutable version is the same except the names start Immutable and it has no setter.

  • Immutable = Data from an API that represents fixed data that should not change, eg display in view, other places, but remain the same for the cycle.
  • Mutable = Mostly for forms, add new user or edit user's data.

I dislike that I’m repeating/duplicating the same properties and getters (name, dob, email etc), but moreso am concerned I'm defining the User model in two places - the Immutable and Mutable object constructors.

If anything changes in the User model, it's defined in (at least) 2 classes that need changing.

Is there a way to somehow abstract some of this into another place? I can't think/find a way, as in the end the two separate classes need to set their own properties which will be the same in both (name, dob, etc).

But am open to ideas, or confirmation that this is just the way it is.

1
  • Is your model anemic or rich? This is a problem often associated with anemic model Oct 30, 2019 at 2:10

2 Answers 2

2

You can accomplish reducing the duplication in a couple ways. One of those is to extend the immutable class to make it mutable. To do that, your backing variables have to be protected. For example:

public class ImmutableUserDto
{
    protected $name; // <- Important!

    public function __construct($userData)
    {
        $this->name = $userData['name'];
    }

    public function getName()
    {
        return $this->name;
    }
}

public class MutableUserDto : extends ImmutableUserDto
{
    public function __construct($userData)
    {
        $base->__construct($userData);
    }

    public function setName($name)
    {
        $this->name = $name;
    }
}

Ideally, you would call the parent constructor from the child constructor, so if I got the syntax wrong for the language you've chosen that's what I intended to fake out.

This allows you to simply add the mutators in the subclass. The parent class is still technically immutable. It might be worthwhile to create a new constructor for the MutableUserDto to accept an ImmutableUserDto so you can copy the contents and edit it.

The advantage of this approach is that the immutable version of the class is truly immutable.


An alternative is to provide an interface for the immutable accessors, and implement the interface with the mutable object. The immutable interface forces the DTO to behave as if it were immutable as long as no-one casts the class to the mutable version.

I would only use this approach if you didn't need true immutability, but simply needed objects to behave that way. It does not have the type safety as the above answer.

2
  • Interesting ideas, thanks! Yeah I don't need immutability for anything hardcore, just good practise as it's DB/API data that should be relatively safe. Devs can do what they want anyway so any of this only works if people use it properly. Cheers
    – James
    Oct 29, 2019 at 21:49
  • 1
    the immutable version of the class is truly immutable - this isn't totally accurate and it actually breaks LSP, because a reference of type ImmutableUserDto might actually point to an instance of MutableUserDto which is mutable. It would be better to have a UserDto class and a UserView interface that only provides read access (but whose contract specifies that the underlying data might actually change).
    – casablanca
    Oct 30, 2019 at 2:58
2

From what you describe, your DTOs look like "view models" or "presentation models", i.e. objects that provide a UI view of the underlying model. It seems you are also missing a class for the actual User model (it seems to be hidden within array $userData).

I would suggest creating a User class to represent the core model, which is inherently mutable because you allow user data to be edited. Then instead of creating Immutable and Mutable versions of this model, you create view-model classes for each use case, for example, DisplayUserViewModel and EditUserViewModel, which wrap the User object and expose methods that are applicable to that view. Now this may still look like you are duplicating some methods in the view-model, but you are simply defining what the view should look like and not redefining the underlying model (you might even have other computed data in the view that's not stored in the model).

1
  • Interesting, this sounds closer to defining once and reusing. I think some duplication is unavoidable at times, if they're different objects then they're different, even if they follow an almost identical model. Thanks
    – James
    Oct 30, 2019 at 20:36

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.