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.
-
Is your model anemic or rich? This is a problem often associated with anemic modelNachiappan Kumarappan– Nachiappan Kumarappan10/30/2019 02:10:27Commented Oct 30, 2019 at 2:10
2 Answers 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.
-
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. CheersJames– James10/29/2019 21:49:22Commented 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 typeImmutableUserDto
might actually point to an instance ofMutableUserDto
which is mutable. It would be better to have aUserDto
class and aUserView
interface that only provides read access (but whose contract specifies that the underlying data might actually change).casablanca– casablanca10/30/2019 02:58:46Commented Oct 30, 2019 at 2:58
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).
-
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. ThanksJames– James10/30/2019 20:36:45Commented Oct 30, 2019 at 20:36