Nome di questo modello per avvolgere le funzionalità

4

Ho un sistema che consente a determinate funzionalità di essere implementate in modi diversi, ma richiede che la funzionalità di ogni classe di implementazione sia racchiusa in un altro livello. Ad esempio, ciascun implementatore ha un metodo doFoo che interagisce con un'API. Le classi implementor possono essere caricate in fase di runtime (sono essenzialmente plug-in per l'hooking in API diverse) e quindi l'insieme di possibili implementor non è fisso e non può essere gestito semplicemente da una switch o da una struttura simile a enum. / p>

Ma ogni volta che una delle classi implementor esegue il suo metodo doFoo , devo anche assicurarmi che il codice aggiuntivo, specifico del metodo, avvenga prima e dopo (si pensi alla registrazione a livello di sistema, alla gestione degli errori e alla sanità di input e output -check che sono gli stessi per tutti gli implementatori).

Ho la seguente configurazione di base:

  • un'interfaccia, FooInterface
  • una classe astratta FooAbstract che implementa FooInterface e imposta alcune azioni predefinite.
  • una classe concreta Foo che estende FooAbstract . Foo serve come una sorta di classe factory / bridge che istanzia un implementatore e aggiunge il proprio codice quando chiama i metodi di un implementatore
  • classi concrete FooImplementorABC , FooImplementorXYZ , ecc., ognuna delle quali estende FooAbstract .

In questo modo, quando un utente desidera utilizzare l'implementazione ABC di FooInterface , l'utente chiama new Foo('ABC') . Il costruttore Foo crea l'istanza corretta e il codice implementatore come necessario.

Codice di esempio (solo codice di esempio, le classi reali sono più complesse e sono tutte suddivise in file e caricate automaticamente in modo appropriato):

<?php
interface FooInterface
{
    public function doFoo();
    public function doBar();
    public function doGenericThing();
}

abstract class FooAbstract implements FooInterface
{
    abstract public function doFoo();
    abstract public function doBar();

    public function doGenericThing()
    {
        echo "Doing something generic\n";
    }
}

class Foo extends FooAbstract
{
    /** @var FooAbstract */
    private $implementor;

    /**
     * The constructor for this class accepts an implementation name.
     * It then attempts to instantiate the appropriate implementor
     * class.
     *
     * @param $implementationName String
     */
    public function __construct($implementationName)
    {
        $implementorClassName = 'FooImplementor' . $implementationName;
        try {
            if (class_exists($implementorClassName)) {
                $this->implementor = new $implementorClassName(); // Yes, this works in PHP! One of its best or worst features, depending on your perspective.
            } else {
                throw new Exception("ERROR: No implementation for $implementationName found!");
            }
        } catch (Exception $e) {
            echo "\n\n{$e->getMessage()}\n\n";
        }
    }

    public function doFoo()
    {
        echo "Doing Foo\n";
        // Do some stuff
        $this->implementor->doFoo();
        // Do some stuff
        echo "Done with Foo\n\n";
    }

    public function doBar()
    {
        echo "Doing Bar\n";
        // Do some stuff
        $this->implementor->doBar();
        // Do some stuff
        echo "Done with Bar\n\n";
    }
}

class FooImplementorABC extends FooAbstract
{
    public function doFoo()
    {
        echo "Foo Implementor ABC just did Foo!\n";
    }

    public function doBar()
    {
        echo "Foo Implementor ABC just did Bar!\n";
    }
}

class FooImplementorXYZ extends FooAbstract
{
    public function doFoo()
    {
        echo "Foo Implementor XYZ just did Foo!\n";
    }

    public function doBar()
    {
        echo "Foo Implementor XYZ just did Bar!\n";
    }
}

$myFooABC = new Foo('ABC');
$myFooXYZ = new Foo('XYZ');

$myFooABC->doFoo();
$myFooXYZ->doFoo();
$myFooABC->doBar();
$myFooXYZ->doBar();

$myFooQRS = new Foo('QRS'); // displays an error message

Tutto questo funziona bene; i metodi vengono tutti correttamente incapsulati, i dettagli di implementazione non sono importanti per il consumatore, ecc.

Le mie domande sono:

  1. È un pattern o un anti-pattern?
  2. Se è un modello, c'è un nome per questo? Non riesco a capire cosa sarebbe. Mi sembra un ibrido tra fabbrica, ponte e motivi di decorazione.
  3. Se si tratta di un anti-pattern, perché? Come mi morderà per la strada?
posta Ed Cottrell 15.10.2015 - 20:57
fonte

3 risposte

3

Foo sembra sia un factory che un decoratore (*). Secondo il Principio di Responsabilità Unica che potrebbe essere un anti-modello e potresti voler dividerlo (ad esempio se un giorno vorresti un'implementazione di FooInterface ma non vuoi le aggiunte di Foo ?)

Inoltre non vedo a cosa serve la classe astratta. Forse in PHP non puoi implementare direttamente un'interfaccia?

(*) molti modelli sembrano simili e differiscono solo per intenzione. Non sono bravo a distinguerli, ma ho non vedo un ponte .

    
risposta data 15.10.2015 - 22:28
fonte
1

Nella programmazione orientata agli oggetti, questo probabilmente avrà la somiglianza più vicina al modello di strategia a causa della sottoclasse con lo stesso semantica ma con algoritmi potenzialmente diversi.

the strategy pattern (also known as the policy pattern) is a software design pattern that enables an algorithm's behavior to be selected at runtime.

Ci sono alcuni elementi del pattern Decorator in cui stai avvolgendo la chiamata dell'algoritmo della strategia scelta con un codice sul classe base.

the decorator pattern (also known as Wrapper, an alternative naming shared with the Adapter pattern) is a design pattern that allows behavior to be added to an individual object, either statically or dynamically, without affecting the behavior of other objects from the same class.

L'implementazione del pattern decorator non è l'ideale, dal momento che puoi solo collegare un decoratore ... quello che viene cablato nella classe base. Ma se è tutto ciò di cui hai bisogno, bene.

La combinazione di pattern potrebbe essere raggiunta in modo più efficace in un altro modo (anche se non sono sicuro di quali oo costruisca PHP sia disponibile). Ad esempio, un'interfaccia viene tipicamente utilizzata per il modello di strategia in modo che gli stessi metodi possano essere chiamati indipendentemente dall'oggetto reale sottostante. Il decoratore può essere implementato in vari modi, ma un modo è quello di avere una classe wrapper che tenga sia un'interfaccia per la strategia, sia i decoratori che avvolgono la chiamata all'interfaccia.

    
risposta data 15.10.2015 - 22:37
fonte
0
  1. un anti-pattern
  2. N / A
  3. perché ti affidi a una stringa per instanciare $implementor . Immagina di rifattorizzare i nomi della classe da FooImplementorABC e FooImplementorXYZ a MyClass e BarWXY . La tua "fabbrica" ( Foo ) non sarà in grado di instanciare alcun implementatore e non hai modo di capirlo se non immergendoti nel codice. Inoltre la classe Foo ha 2 responsabilità (che viola il SRP ): trovare la corretta implementazione di FooAbstract e fa eco ad alcune cose.

Il mio suggerimento: delegare la responsabilità per la creazione di FooImplementor al di fuori della classe.

class LoggingFoo extends FooAbstract
{
    private $origin;

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

    public function doFoo()
    {
        echo "Doing Foo\n";
        $this->origin->doFoo();
        echo "Done with Foo\n\n";
    }

    public function doBar()
    {
        echo "Doing Bar\n";
        $this->origin->doBar();
        echo "Done with Bar\n\n";
    }
}

Modifica dell'utilizzo in:

$myFooABC = new LoggingFoo(new FooImplementorABC());
$myFooXYZ = new LoggingFoo(new FooImplementorXYZ());
$myFooABC->doFoo();
$myFooXYZ->doFoo();
$myFooABC->doBar();
$myFooXYZ->doBar();

Se non vuoi che il tuo "codice cliente" sia responsabile della creazione di FooImplementor , delegalo a una fabbrica (il concetto di evento enum esiste in PHP?):

public class FooFactory
{
    public static FooAbstract create(FooType $type)
    {
        switch($type)
        {
            case FooType.ABC:
                return new FooImplementorABC();
            case FooType.XYZ:
                return new FooImplementorXYZ();
            default:
                throw new Exception('Unsupported type ' + $type);
        }
    }
    public static FooAbstract createWithLogging(FooType $type)
    {
        return new LoggingFoo(create($type));
    }
}

L'utilizzo diventa:

$myFooABC = FooFactory.createWithLogging(FooType.ABC);
$myFooXYZ = FooFactory.createWithLogging(FooType.XYZ);
    
risposta data 16.10.2015 - 08:14
fonte

Leggi altre domande sui tag