Quello che vedi qui, Tom, è il modello di progettazione Strategia , che può o meno essere una variante di Facciata o Decoratore e utilizza il Adattatore modello di progettazione.
Il metodo di strategia stesso è un metodo di fabbrica . Un metodo factory semplice produce un oggetto con i parametri forniti, ma hai fornito un ritorno condizionale a uno specifico sottotipo (o il supertipo stesso), hai fornito una strategia.
Quando si utilizza il modello di strategia di solito si fornisce un'interfaccia comune (adattatore) per tutte le implementazioni sottostanti e si usa quella. In questo modo stai astrarre le parti interiori di cui non ti interessa.
Considera il tuo esempio in una lingua compilata C++
:
#include <exception>
class Number
{
protected:
int _number;
public:
explicit Number(const int number)
: _number(number)
{
}
int getNumber() const
{
return _number;
}
};
class EvenNumber : public Number
{
public:
explicit EvenNumber(const int number)
: Number(number)
{
}
bool isNumberReallyEven() const
{
return _number % 2 == 0;
}
};
namespace NumberStrategy
{
Number createNumber(const int number)
{
switch (number % 2)
{
case 0:
return EvenNumber(number);
case 1:
return Number(number);
}
throw std::exception("Should never happen.");
}
}
int main(int agrc, char* argv[])
{
auto number = NumberStrategy::createNumber(2);
auto isItEven = number.isNumberReallyEven();
return EXIT_SUCCESS;
}
Compilando questo breve snippert, si otterrà un errore di compilazione sulla riga 52:
'isNumberReallyEven': is not a member of 'Number'
Ma perché, potresti chiedere? Passando il numero 2 alla funzione NumberStrategy::createNumber
, dovrei avere l'istanza EvenNumber
che ha il metodo isNumberReallyEven
.
In realtà hai l'istanza, ma il compilatore non lo sa, perché il tipo di ritorno della funzione NumberStrategy::createNumber
è Number
. Se vuoi accedere ai metodi sottostanti, devi dire al compilatore quale tipo hai veramente colando.
C ++ 11 ha un concetto molto carino per questo chiamato reinterpret_cast
. È completamente pericoloso, quindi quando lo usi devi essere assolutamente sicuro di sapere cosa stai facendo. Ovviamente, in questo esempio sai che passare un numero pari alla funzione NumberStrategy::createNumber
restituirà il tipo EvenNumber
.
Cambia il programma in questo modo:
int main(int agrc, char* argv[])
{
auto number = NumberStrategy::createNumber(2);
// I know I recieved a number but I am 100% sure it has to be even,
// thus I am casting it manually without any worry.
auto evenNumber = reinterpret_cast<EvenNumber&>(number);
auto isItEven = evenNumber.isNumberReallyEven();
return EXIT_SUCCESS;
}
E si compila.
Potresti chiederti perché ho deciso di mostrarti un esempio in C ++ invece di uno in Python? Non sono un programmatore Python ma lavoro con PHP (anche un linguaggio dinamico). Nei linguaggi dinamici il programma probabilmente funzionerebbe anche senza il cast, a causa della natura del linguaggio - controllerà semplicemente il tipo durante il runtime e vedrà se il metodo è disponibile o meno e nel caso di passaggio di un 2 al metodo sarebbe.
Usando per esempio C ++, il compilatore non ti permetterà di superare certe restrizioni che non dovresti affrontare in un linguaggio interpretato e che è buono. Quando un altro programmatore legge il codice, saprà immediatamente che lo stai facendo apposta.
Lo stesso vale per Python. Se chiami un metodo che sai essere disponibile in un sottotipo, ma il contratto del metodo afferma che viene restituito un supertipo, probabilmente dovresti commentare che sei esattamente sicuro che il metodo sarà disponibile quando lo chiami.
Poi c'è l'altra cosa: astrazione errata . Se incontri una situazione in cui sei costretto a digitare una variabile per accedere ai suoi metodi, dovrebbe esistere anche la relazione supertype<->subtype
?
Nello snippet C ++ questo problema viene facilmente risolto inserendo il metodo isNumberReallyEven
nella classe Number
. Se non puoi fare qualcosa del genere, il tuo design potrebbe essere sbagliato.