Comment créer un objet dont la classe dérivée est spécifiée implicitement par les propriétés de création?

StackOverflow https://stackoverflow.com/questions/1000681

Question

Je cherche un modèle pour ce qui suit. (Je travaille à Perl, mais je ne pense pas que la langue soit particulièrement importante).

Avec une classe de parents Foo et des enfants Bar, Baz, Bazza.

L’une des méthodes de construction d’un Foo consiste à analyser une chaîne. Une partie de cette chaîne spécifie implicitement la classe à créer. Ainsi, par exemple, si elle commence par "http:", il s’agit d’un Bar, mais si elle ne contient pas "[Date]", Baz l’aime, et ainsi de suite.

Maintenant, si Foo connaît tous ses enfants et quelle chaîne est un Bar, ce qu’est un Baz, etc., il peut appeler le constructeur approprié. Mais une classe de base ne devrait avoir aucune connaissance de ses enfants.

Ce que je veux, c’est que le constructeur de Foo puisse essayer ses enfants à tour de rôle, jusqu’à ce que l’un d’eux dise: "Oui, c’est à moi, je vais créer la chose".

Je me rends compte que dans le cas général, ce problème n’est pas bien défini car il peut y avoir plus d’un enfant qui acceptera la chaîne. L’ordre dans lequel ils sont appelés est donc important: ignorez-le et supposez que les caractéristiques de la chaîne sont tels que seulement une classe enfant va aimer la chaîne.

Le mieux que j'ai proposé est que les classes enfants "s'enregistrent" avec la classe de base lors de l'initialisation, afin qu'elle obtienne une liste de constructeurs, puis les parcourt. Mais y a-t-il une meilleure méthode qui me manque?

Exemple de code:

package Foo;

my @children;

sub _registerChild
{
  push @children, shift();
}

sub newFromString
{
  my $string = shift;
  foreach (@children) {
    my $object = 

Je cherche un modèle pour ce qui suit. (Je travaille à Perl, mais je ne pense pas que la langue soit particulièrement importante).

Avec une classe de parents Foo et des enfants Bar, Baz, Bazza.

L’une des méthodes de construction d’un Foo consiste à analyser une chaîne. Une partie de cette chaîne spécifie implicitement la classe à créer. Ainsi, par exemple, si elle commence par "http:", il s’agit d’un Bar, mais si elle ne contient pas "[Date]", Baz l’aime, et ainsi de suite.

Maintenant, si Foo connaît tous ses enfants et quelle chaîne est un Bar, ce qu’est un Baz, etc., il peut appeler le constructeur approprié. Mais une classe de base ne devrait avoir aucune connaissance de ses enfants.

Ce que je veux, c’est que le constructeur de Foo puisse essayer ses enfants à tour de rôle, jusqu’à ce que l’un d’eux dise: "Oui, c’est à moi, je vais créer la chose".

Je me rends compte que dans le cas général, ce problème n’est pas bien défini car il peut y avoir plus d’un enfant qui acceptera la chaîne. L’ordre dans lequel ils sont appelés est donc important: ignorez-le et supposez que les caractéristiques de la chaîne sont tels que seulement une classe enfant va aimer la chaîne.

Le mieux que j'ai proposé est que les classes enfants "s'enregistrent" avec la classe de base lors de l'initialisation, afin qu'elle obtienne une liste de constructeurs, puis les parcourt. Mais y a-t-il une meilleure méthode qui me manque?

Exemple de code:

<*>->newFromString(@_) and return $object; } return undef; } package Bar; our @ISA = ('Foo'); Foo::_registerChild(__PACKAGE__); sub newFromString { my $string = shift; if ($string =~ /^http:/i) { return bless(...); } return undef; }
Était-ce utile?

La solution

Peut-être pourriez-vous implémenter cela avec Module: : Enfichable ? Cela supprimerait la nécessité de s'inscrire.

L’approche que j’ai adoptée auparavant consistait à utiliser Module :: Pluggable pour charger mes modules enfants (cela m’a permis d’ajouter de nouveaux modules enfants simplement en les écrivant et en les installant). Chacune des classes enfants aurait un constructeur qui renverrait un objet béni ou undef. Vous passez en boucle sur vos plugins jusqu'à ce que vous obteniez un objet, puis vous le retournez.

Quelque chose comme:

package MyClass;
use Module::Pluggable;

sub new
{
    my ($class, @args) = @_;
    for my $plugin ($class->plugins)
    {
       my $object = $plugin->new(@args);
       return $object if $object;
    }
}

Il existe Classe: Factory aussi, mais cela peut être un peu excessif pour vos besoins.

Autres conseils

Il semble que vous essayez de faire en sorte qu'une seule classe soit à la fois une classe de base et une usine. Ne pas Utilisez 2 classes séparées. Quelque chose comme ça:

package Foo;

package Bar;
use base 'Foo';

package Baz;
use base 'Foo';

package Bazza;
use base 'Foo';

package Factory;
use Bar;
use Baz;
use Bazza;

sub get_foo {
    my ($class, $string) = @_;
    return Bar->try($string) || Baz->try($string) || Bazza->try($string);
}

Et ensuite utilisez-le comme:

my $foo = Factory->get_foo($string);

De cette manière, votre classe de base n'a pas besoin de connaître vos classes enfants, seule votre usine le sait. Et les classes enfants n’ont pas besoin non plus de se connaître, seule Factory a besoin de connaître les classes enfants à essayer et dans quel ordre.

Vous pouvez implémenter un algorithme de recherche arbitraire dans la classe Foo, qui recherche les classes enfants existantes. Peut-être basé sur des fichiers de configuration fournis avec des classes enfants ou avec tout autre mécanisme auquel vous pourriez penser.

La classe Foo détectera alors les classes clientes existantes au moment de l'exécution et les appellera à tour de rôle.

De plus, vous pouvez mettre en cache les résultats de la recherche et vous rapprocher de la solution de registre que vous avez déjà décrite vous-même.

Si vous prenez votre commentaire sur la classe parente ne contenant pas d’informations sur les enfants et par votre méthode de délégation de la tâche d’établissement de l’adéquation des classes enfants à la classe elle-même, il est probablement correct de factoriser la sélection de classe à partir de la classe parente et créez un singleton pour cette tâche.

du moins, ce serait ma préférence ... à partir de là, votre classe parente actuelle (qui a probablement des fonctionnalités communes dans toutes vos classes enfants) peut vraisemblablement devenir abstraite ou devenir une interface.

le singleton pourrait alors gérer la construction de toutes les classes enfants et leur distribution (les cloner si elles ne sont pas fonctionnelles?) ... De plus, les classes enfants peuvent être déplacées dans une dll séparée pour favoriser la séparation.

désolé, ce n’est pas une solution directe. Je l’ai fait par le passé en gérant une liste de classes dans le singleton, un peu comme vous. L'idée derrière le singleton est que si vous voulez utiliser une réflexion coûteuse, vous ne devez le faire qu'une seule fois.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top