Object-oriented – does this model-pattern have a name

design-patternsobject-oriented

A few years back, I wrote this answer to a question from which the following idea emerged.

I call these "Type Descriptors", as I have never been able to find this pattern documented or formally described anywhere – not to be confused with type descriptors in C#, but it's the most accurate name I've been able to come up with, so bear with me.

Here's my first attempt at a "formal" description:

A Type Descriptor is a type that provides meta-information about
another type.

This pattern can be seen as an alternative to run-time reflection and
annotations, and as such is equally applicable to languages that do or
do not support reflection or annotations.

Type Descriptors are particularly useful as a means of describing
aspects of model types found in for example domain and view-models, as
well as in specialized types of models, such as form-models in a
business- or web-application. Often such applications have many common
aspects, such as the need to label inputs and perform common
validations for e.g. required input, data types and formats, etc. –
the information required by helper objects (or services) to handle
these aspects fits comfortably in descriptors.

A descriptor mirrors the shape of the type it describes – for example,
if a model type User has properties like id, name and email, it's type
descriptor must have properties id, name and email, but where the
properties on the model type have various value types (in this case
for example integer and string) the properties of the descriptor are
objects that provide information about those properties.

Maintaining identical model type and descriptor shapes may seem like
duplication, in terms of property-names being identical in the model
type and type descriptor – however, this does not violate the DRY
principle as such, since the model type and descriptor, strictly
speaking, are two different types that only happen to have the same
shape, but do not have any overlapping concerns or duplication of any
code; only the property names are duplicates.

I program mostly in PHP on a daily basis, and have used this pattern successfully to build applications. The following example is in PHP, though this could be implemented in any language capable of resolving properties by name at run-time.

A tiny bit of framework is required to implement this, so here goes…

A model-type implementing the following interface can provide a "type descriptor" – a type that describes the model-type itself:

interface TypeInfo
{
    function getType();
}

Individual model-properties are described by "property descriptors" in the "type descriptor", for which I use a base class along the lines of this:

abstract class PropertyInfo
{
    /** @var string */
    public $name;

    /** @var string */
    public $label;

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

From this class, I derive "property descriptors" for specific types of properties – for example, this type describes a string property:

class StringInfo extends PropertyInfo
{
    /**
     * @var int|null
     */
    public $max_length;
}

Now here's a sample User model, and it's "type descriptor" UserType:

class User implements TypeInfo
{
    /** @var string */
    public $name;

    /** @var string */
    public $email;

    /** @return UserType */
    public function getType()
    {
        return UserType::instance();
    }
}

class UserType
{
    /** @var StringInfo */
    public $name;

    /** @var StringInfo */
    public $email;

    public function __construct()
    {
        $this->name = new StringInfo('name');
        $this->name->label = 'Full Name';
        $this->name->max_length = 50;

        $this->email = new StringInfo('email');
        $this->email->label = 'E-mail Address';
        $this->email->max_length = 128;
    }

    /** @return self */
    public static function instance()
    {
        static $instance;

        return $instance ?: $instance = new static();
    }
}

As you can see, it's now possible to get the type descriptor either from a User instance by e.g. $user->getType() – notice that the property-names of the "property descriptors" in the "type descriptor" match the property-names in the actual model; using this pattern, it's my own responsibility to make sure the model and it's descriptor have the same shape.

Now I can write services and helpers etc. which may consume information from a "type descriptor" while working with model objects – for example, here's a very simple form helper:

class FormHelper
{
    /** @var TypeInfo */
    public $model;

    /** @var string */
    public $prefix;

    /**
     * @param TypeInfo $model
     * @param string     $prefix
     */
    public function __construct(TypeInfo $model, $prefix)
    {
        $this->model = $model;
        $this->prefix = $prefix;
    }

    /**
     * @param StringInfo $prop
     *
     * @return string
     */
    public function text(StringInfo $prop)
    {
        $name = $prop->name;

        return '<label for="' . $name . '">' . htmlspecialchars($prop->label) . '</label>'
            . '<input type="text" name="' . $this->prefix . '[' . $name . ']"'
            . ($prop->max_length ? ' maxlength="' . $prop->max_length . '"' : '')
            . ' value="' . $this->model->$name . '" id="' . $name . '" />';
    }
}

You would use the form helper like this:

$user = new User;
$user->name = 'Rasmus';
$user->email = 'rschultz@gorges.us';

$form = new FormHelper($user, 'user');

$t = $user->getType();

echo $form->text($t->name) . "\n";
echo $form->text($t->email) . "\n";

Which would output something like this:

<label for="name">Full Name</label><input type="text" name="user[name]" maxlength="50" value="Rasmus" id="name" />
<label for="email">E-mail Address</label><input type="text" name="user[email]" maxlength="128" value="rschultz@gorges.us" id="email" />

The two key reasons I enjoy working like this are (1) patterns like rendering forms, mapping post-data to model objects, mapping model-objects to database records, performing input validation and so forth can all be performed by formal helpers and service objects, all of which consume the same single source of information, and (2) everything is "statically typed", meaning only a single unchecked property-reference (by name, as a string) is made and encapsulated inside the "type descriptor", and type-hints and inference in a modern IDE (PhpStorm) can provide automated code inspections at development-time, I can safely use automated refactorings, and so forth.

Okay, a rather lengthy discourse, for what boils down to one question: does this pattern have a name? 🙂

Side question: does this constitute (reflective) meta-programming? (The use of this pattern is very similar to e.g. using reflection to obtain annotations from a model, though I find this to be much safer, less complicated, more flexible and transparent.)

Best Answer

That is an OOP Data Descriptor. Although you are describing additional information about a class rather than information about bits in memory, the fundamental parity still applies; you're describing in one place useful aspects about data in another place.

I'd advise you to adopt that name, write up a clearer definition of the pattern, and use the name in code. ("Type" already means something in PHP, so you really shouldn't use it.) So, your user class could be changed as follows:

class User {
   ...
   public function getDescriptor() {
   }
}

class UserDescriptor {
   ...
}
Related Topic