Alcuni anni fa, ho scritto questa risposta a una domanda da cui è emersa la seguente idea.
Io chiamo questi "Descrittori di tipi", in quanto non sono mai stato in grado di trovare questo modello documentato o descritto formalmente da nessuna parte - da non confondere con i descrittori di tipi in C #, ma è il nome più accurato che ho potuto venire con, quindi sopportare me.
Ecco il mio primo tentativo di descrizione "formale":
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.
Io programma principalmente su PHP ogni giorno e ho usato questo modello con successo per creare applicazioni. Il seguente esempio è in PHP, anche se questo potrebbe essere implementato in qualsiasi linguaggio in grado di risolvere le proprietà per nome in fase di esecuzione.
Per implementare questo è necessario un minimo di framework, quindi ecco qui ...
Un modello di tipo che implementa la seguente interfaccia può fornire un "descrittore di tipi" - un tipo che descrive il tipo di modello stesso:
interface TypeInfo
{
function getType();
}
Le proprietà dei singoli modelli sono descritte da "descrittori di proprietà" nel "descrittore di tipi", per il quale uso una classe base lungo le linee di questo:
abstract class PropertyInfo
{
/** @var string */
public $name;
/** @var string */
public $label;
public function __construct($name)
{
$this->name = $name;
}
}
Da questa classe, deriva "descrittori di proprietà" per specifici tipi di proprietà - ad esempio, questo tipo descrive una proprietà stringa:
class StringInfo extends PropertyInfo
{
/**
* @var int|null
*/
public $max_length;
}
Ora ecco un esempio di modello User
, ed è "tipo descrittore" 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();
}
}
Come puoi vedere, è ora possibile ottenere il descrittore del tipo da un'istanza User
di ad es. $user->getType()
- si noti che i nomi di proprietà dei "descrittori di proprietà" nel "descrittore di tipi" corrispondono ai nomi di proprietà nel modello attuale; usando questo modello, è mia responsabilità accertarmi che il modello e il suo descrittore abbiano la stessa forma.
Ora posso scrivere servizi e helper ecc. che possono consumare informazioni da un "descrittore di tipi" mentre si lavora con gli oggetti del modello - ad esempio, ecco un helper per la forma molto semplice:
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 . '" />';
}
}
Dovresti usare l'helper del modulo in questo modo:
$user = new User;
$user->name = 'Rasmus';
$user->email = '[email protected]';
$form = new FormHelper($user, 'user');
$t = $user->getType();
echo $form->text($t->name) . "\n";
echo $form->text($t->email) . "\n";
Quale prodotto di questo tipo:
<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="[email protected]" id="email" />
I due motivi principali per cui mi diverto a lavorare in questo modo sono (1) modelli come il rendering di moduli, la mappatura di post-dati per modellare oggetti, mappare gli oggetti modello ai record del database, eseguire la convalida dell'input e così via possono essere tutti eseguiti da aiutanti formali e oggetti di servizio, ognuno dei quali consuma la stessa singola fonte di informazioni, e (2) tutto è "tipizzato staticamente", cioè solo un singolo riferimento di proprietà non controllato (per nome, come stringa) viene creato e incapsulato all'interno del "tipo" descrittore ", e i tipi di suggerimenti e inferenze in un moderno IDE (PhpStorm) possono fornire ispezioni di codice automatizzate in fase di sviluppo, posso tranquillamente utilizzare i refactoring automatizzati e così via.
Va bene, un discorso piuttosto lungo, perché ciò che si riduce a una domanda: questo schema ha un nome? : -)
Domanda secondaria: questa è una meta-programmazione (riflessiva)? (L'uso di questo modello è molto simile ad esempio usando la riflessione per ottenere annotazioni da un modello, anche se trovo che questo sia molto più sicuro, meno complicato, più flessibile e trasparente.)