Primitive Obsession
Join the DZone community and get the full member experience.
Join For FreeThe Primitive Obsession is an anti-pattern well diffused in many different programming languages, especially in the ones supporting object-oriented programming, but not adhering to the everything is an object principle.
The anti-pattern describes an "obsession" with primitive values, being them scalar types or even objects provided by the platform. An example can be the management of a positive integer in languages without the unsigned keyword: they are often represented as primitive integers and passed around as variables of type int or objects of type Integer, despite the fact that half of the value supported by the type are not valid or meaningful.
In general, this meaning of the term primitive indicates the provenience of the type as the platform, instead of the developer himself. The natural consequence is that a primitive type is oblivious to the application domain (in fact it is supported by a platform like the Java or PHP language, used in many different domains). The lack of domain logic does not add any behavior to the object, making it anemic.
A first example in PHP
A PHP example can be the following. Suppose we have a method with a signature like method(My_Class $firstParameter, array $secondParameter). The parameter names in this example are completely hypothetical and lack meaning.
The second parameter of a method is affected by Primitive Obsession. Once I read this signature, what I should use as the second parameter? An array of strings, or of numbers, or a multidimensional one? It's really easy to create an invalid value for $secondParameter, while it will be difficult for $firstParameter is My_Class enforces a valid creation method (like requiring mandatory parameters in the constructor and insert automatically the default ones.)
Usually, this kind of parameters are documented via docblocks containing <code> and their tests. However, they are a step beyond simply reading the method signature, and when they are not present (legacy code) we are in trouble.
Of course not all the variables are worthy a replacement with a domain-related data structure. A rule of thumb to discover a Primitive Obsession is: are you passing a scalar value, or an array with the same structure, to more than one method?
If the answer is yes, you probably have a missing concept in that parameter, which can be modelled with a class of its own (Parameter Object). This class/concept can be small at will, comprehending one primitive variable or many of them. It can for example perform can do validation of parameters, so that you will never break a call again.
Here are some special categories of Primitive Obsession in PHP applications and how to deal with them.
Strings
A native string is one of the most abused types in the PHP language, containing values that range from class names to file contents. A small Value Object that wraps the string itself is usually enough to start benefitting from type hinting.
The class itself may add a bit of behavior, like immutability or conversions to different formats. My typical implementation, which leverages support from the language, is a combination of a private field, plus a getter or a __toString() method.
Associative arrays
Associative arrays can be thought of as small Value Objects when the number of keys is fixed. In this case, we'll have some getters, one for each key of the array.
Other useful additions can be an equals() method, and method to generate other Value Objects derived from the current one, especially when the object is immutable.
If the Value Object is not implemented as immutable, it won't be a Value Object from a technical point of view but it will still be an object, far better than a plain array.
When the keys of the array are not predefined, the class can extend ArrayObject for a simple implementation of values management, along with the advantage of type hinting.
Numeric arrays
These arrays can be modelled as instances of a class that extends ArrayObject. In some cases, depending on the behavior to provide, the class may extend one of the Iterators, or implement IteratorAggregate. Even Countable is sometimes a solution.
In any case, the result will be a stronger type hint where the object is pass around, that defines the object as instance of a specific class instead of a generic array.
In the example, I extract Value Objects on a Service Layer, but this could be applied to the Domain Model with great results. The only catch is the Object-Relational Mapping system (or Object-Somethingelse mapping system) must be able to translate your elaborate object graph into the storage. Doctrine 2 is not able yet of mapping Value Objects on an Entity into a single column out of the box. Thus, we have much more freedom in modelling other layers than the Domain Model, at least in PHP.
<?php /** * Here we reason on functions, but this pattern will probably be applied * to object methods. * This is the worst possible function: it accepts everything * and if you pass it the wrong $options, an internal error will be raise */ function searchArticles($options) { /* ... */ } /** * This function, which I encountered in a real project, is slightly * better than the previous one, but still I don't know what array I should pass * to it. Strings? Fields? * I can document it here with a sample call, but if $options is used throughout * many similar methods I would duplicate all the information... * <code> * $result = search(array( * 'field' => 'value' * )); * </code> */ function searchArticles(array $options) {} /** * Here we define a Parameter Object to avoid using the primitive type. * We don't need any behavior like immutability or population, so we stay * very simple. * The sample value is centralized here: * <code> * new SearchOptions(array( * 'field' => 'value' * )); * </code> */ class SearchOptions extends ArrayObject {} /** * Now we have a clear signature in every search() method scattered throughout * the codebase. */ function searchArticles(SearchOptions $options) {} function searchComments(SearchOptions $options) {} /** * Here we have a second argument that we can clearly distinguish from the previous one. */ function searchAuthors(SearchOptions $options, OrderingOptions $ordering) {} /** * Here a totally different $options that we could have confused. Parameter names * are never enough to characterize signatures: they do not enforce anything, can * easily get out of date and confuse who reads the code. */ function getAllTags(OrderingOptions $options) {}
Opinions expressed by DZone contributors are their own.
Comments