Come devo aggiungere funzionalità a un oggetto già esistente?


25

Ho un'interfaccia che ha una certa quantità di funzionalità ben definite. Diciamo:

interface BakeryInterface {
  public function createCookies();
  public function createIceCream();
}

Questo funziona bene per la maggior parte delle implementazioni dell'interfaccia, ma in alcuni casi, ho bisogno di aggiungere alcune nuove funzionalità (come forse inserito in un nuovo metodo createBrownies()). L'approccio ovvio / ingenuo per fare questo sarebbe estendere l'interfaccia:

interface BrownieBakeryInterface extends BakeryInterface {
  public function createBrownies();
}

Ma ha un grosso svantaggio in quanto non posso aggiungere la nuova funzionalità senza modificare l'API esistente (come cambiare la classe per usare la nuova interfaccia).

Stavo pensando di utilizzare un adattatore per aggiungere la funzionalità dopo l'istanza:

class BrownieAdapter {
  private brownieBakery;

  public function construct(BakeryInterface bakery) {
    this->brownieBakery = bakery;
  }

  public function createBrownies() {
    /* ... */
  }
}

Il che mi farebbe guadagnare qualcosa del tipo:

bakery = new Bakery();
bakery = new BrownieBakery(bakery);
bakery->createBrownies();

Sembra una buona soluzione al problema, ma mi chiedo se sto risvegliando i vecchi dei facendolo. L'adattatore è la strada da percorrere? C'è un modello migliore da seguire? O dovrei davvero solo mordere il proiettile ed estendere l'interfaccia originale?


Delphi ha classi helper, è come aggiungere metodi alle classi esistenti senza modificarle davvero. Ad esempio Delphi ha una classe TBitmap definita nella sua unità grafica, è possibile creare una classe helper che aggiunge, diciamo, una funzione Flip a TBitmap. Finché la classe helper è nell'ambito, è possibile chiamare MyBitmap.Flip;
Bill

Risposte:


14

Qualsiasi dei corpo maniglia potrebbe adattarsi alla descrizione, a seconda delle esigenze, della lingua e del livello di astrazione necessari.

L'approccio purista sarebbe il modello Decorator , che fa esattamente quello che stai cercando, aggiungendo dinamicamente responsabilità agli oggetti. Se stai effettivamente costruendo panetterie, è decisamente eccessivo e dovresti semplicemente andare con Adapter.


Questo è esattamente ciò di cui avevo bisogno: mi sono reso conto che l'uso di un adattatore avrebbe rovinato l'iniezione di dipendenza, ma l'uso di un decoratore lo aggira.

5

Indaga sul concetto di riutilizzo orizzontale , dove puoi trovare cose come Traits , la programmazione orientata agli aspetti ancora sperimentale ma già a prova di produzione e i Mixin a volte odiati .

Un modo diretto per aggiungere metodi a una classe dipende anche dal linguaggio di programmazione. Ruby consente il patching delle scimmie mentre l' ereditarietà basata sul prototipo di Javascript , dove le classi non esistono davvero, crei un oggetto e lo copi e continui ad aggiungerlo, ad esempio:

var MyClass = {
    do : function(){...}
};

var MyNewClass = new MyClass;
MyClass.undo = function(){...};


var my_new_object = new MyNewClass;
my_new_object.do();
my_new_object.undo();

Infine, puoi anche emulare il riutilizzo orizzontale, la "modifica" del runtime e l '"aggiunta" del comportamento di classe / oggetto con la riflessione .


4

Se c'è un requisito che bakery istanza cambi il suo comportamento in modo dinamico (a seconda delle azioni dell'utente, ecc.), Dovresti scegliere il motivo Decoratore .

Se bakerynon cambia il suo comportamento in modo dinamico ma non è possibile modificarloBakery class API esterna, ecc., È necessario scegliere il modello Adapter .

Se bakerynon cambia il suo comportamento in modo dinamico e puoi modificarlo Bakery class, dovresti estendere l'interfaccia esistente (come inizialmente proposto) o introdurre una nuova interfaccia BrownieInterface e lasciare Bakeryimplementare due interfacce BakeryInterfacee BrownieInterface.
Altrimenti aggiungerai complessità non necessaria al tuo codice (usando il modello Decorator) senza una buona ragione!


Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.