Come utilizzare OO Design per rifattorizzare una libreria con funzioni specifiche per i prodotti

5

Ho una classe che funge da libreria di funzioni per vari prodotti. Per calcolare i suoi dati, la funzione deve attualmente essere a conoscenza di tutti i nomi dei prodotti. A seconda del prodotto che chiama la funzione, restituisce le risposte specifiche del prodotto.

Il codebase è abbastanza contorto da dove le classi sono legate l'una all'altra in modi rotondi e sono disordinate e non hanno una struttura chiara immediatamente riconoscibile. Quindi sto rifattorizzando spesso senza uno scopo specifico in mente, ma in questo caso voglio refactoring per

  • ridurre la necessità della biblioteca di fare affidamento sulla conoscenza del prodotto
  • forse posso pulire il codice in modo sufficiente a dove posso usare il polimorfismo - creare un'istanza di un prodotto specifico e poi avere un codice di blocco dall'aspetto generico chiamare le funzioni specifiche del prodotto

Se io ho bisogno di per fare questo tipo di refactoring, non lo so, poiché dopotutto "Le cose funzionano bene ora", tranne che sono difficili da seguire e gli errori sono difficili da rintracciare. Tuttavia, il mio obiettivo e la mia domanda è di "rimuovere la dipendenza da conoscenze specifiche del prodotto dalla funzione calcVar ".

Domanda : come rimuovo la consapevolezza del prodotto dalla funzione calcVar() di seguito.

Esempio di lavoro di seguito :

class Product{}

class Graph{}

class ProductA extends Product
{
    public $calc;

    public function __construct() {
        $this->calc = new CalcLibrary();
        $this->calc->spec->product = "A";
    }
}

class GraphB extends Graph
{
    public $calc;

    public function __construct()    {
        $this->calc = new CalcLibrary();
        $this->calc->spec->product = "B";
    }
}

class Specs
{
    public $var;
    public $product;

    //loads different specs based on product name
    function __construct() {
        if ($this->product == "A") $this->var = 3;
        if ($this->product == "B") $this->var = 4;
        $this->var = 5;
    }
}

class CalcLibrary
{
    public $spec;

    public function __construct() {
        $this->spec = new Specs();
    }

    //problem - library holding formulas for "Var" is product-aware
    //loads different formulas based on product name
    public function calcVar() {
        if ($this->spec->product == "A")
        {
            //somewhat different formulas for different products
            if ($this->spec->var <= 5) return 3 * $this->spec->var;
            else return 0;
        }        
        if ($this->spec->product == "B")
        {
            if ($this->spec->var == 0) return 16;
            else return 2 * $this->spec->var;
        }
        return $this->spec->var;
    }

    public function output() {
        echo $this->calcVar() . "\n";
    }
}

// tests should output 15, 10, and 5 
(new ProductA())->calc->output();
(new GraphB())->calc->output();
(new CalcLibrary())->output();

I miei pensieri sulla soluzione

Tentativo di soluzione 1 All'inizio la soluzione era facile, pensavo - quando non ero a conoscenza della famiglia di Graph classi

  • sposta calcVar () da CalcLibary in Product class
  • elimina le istruzioni if / then / else e lascia la versione generica di calcVar () all'interno della classe Product, ma sposta quelle specifiche in ProductA/B/C (classi che estendono il prodotto)

e tutto andava bene fino a quando ho scoperto che esiste un gruppo di classi chiamato GraphA / B / C, che usa anche CalcLibrary.

Tentativo di soluzione 1 di recupero

La soluzione naturale era copiare CalcVar () in Graph e specializzare fondamentalmente utilizzare lo stesso approccio con Product . Ma, il problema è che avrò più% funzioni% di%, quelle generiche in Prodotto e in Grafico, e quelle specializzate in ciascuna delle loro sottoclassi. Come in un problema di duplicazione, quando in questo momento non ho la duplicazione di CalcVar() . Non volevo scambiare un problema con un altro.

Idea della soluzione 2

Un altro modo era di rendere calcVar() la top class, e avere classi Graph e Product estendere CalcLibrary, avere CalcLibrary contenere calcVar, come faceva prima, ma ora CalcLibrary avrà solo la versione generica di CalcLibrary , e quindi le singole classi ProductA / B / C e GraphA / B / C hanno versioni calcVar () specifiche del prodotto. Questa sembra una buona soluzione in quanto rimuove la dipendenza dalla conoscenza del prodotto e si occupa della duplicazione. Ma non sono sicuro al 100% che estendere questa libreria sia una buona idea. Non è davvero una biblioteca, se non voglio che sia. È una classe personalizzata, quindi posso renderlo quello che mi serve.

Soluzione 3 Schizzo

Forse tutto ciò di cui ho bisogno è di trovare un nome migliore, quindi invece calcVar usa CalcLibrary , e quindi usa la soluzione # 2. Forse più tardi attraverso il refactoring vedrò abbastanza chiaramente nella mia base di codice per vedere un miglior adattamento delle classi (trovare un design migliore), ma quali sono i tuoi pensieri adesso, sul refactoring di questo particolare esempio con le mie esigenze in mente? La "Soluzione 2" è ciò che dovrei fare? Poiché non è così semplice spostare CalcLibrary in alto, con tutte le altre funzioni e vari altri effetti collaterali, forse posso creare una nuova classe con la funzione ClassThatContainsVariousFunctionsUsefulToGraphAndProductClasses lì, e poi avere Product e Graph estendere quella nuova classe , lascia CalcLibrary da solo e rimuovi le parti inutilizzate di esso mentre muovo le cose in quella nuova classe che ho creato ...

Considerazioni finali

Perché altrimenti, non riesco a vedere un modo per le classi calcVar e Plot per usare le funzioni all'interno di Graph , senza spostare le funzioni all'interno delle rispettive classi specifiche del prodotto in qualche modo. (Voglio dire a parte che CalcLibrary viene utilizzato ora, essendo in una classe separata, ma con il prezzo di essere consapevole del prodotto)

ANCHE, ... il mio terzo schizzo di soluzione con la creazione di CalcLibrary sembra (forse giusto) ma un po 'esoterico .... come in Non vedo un significato se non quello di far funzionare la situazione esistente con un progetto OO. Voglio dire, un'altra idea è smantellare completamente la classe e fare in modo che ogni classe esegua i propri calc-calc calcoli direttamente all'interno delle classi. Ci saranno molte duplicazioni, ma si sentiranno più "naturali" .....

    
posta Dennis 22.01.2015 - 17:59
fonte

1 risposta

1

Da qualche parte nel tuo codice hai per memorizzare le specifiche di ciascun prodotto. Nel tuo esempio, il posto naturale per questa sembra essere la classe Specs , dal momento che immagino sia qualcosa come la "configurazione" della tua libreria. Estenderei quella classe, ad esempio, con un attributo "calcFactor", quindi la tua funzione calcVar sarà simile a questa:

public function calcVar()    {
    return $this->spec->calcFactor * $this->spec->var;        
}

Ciò di cui hai bisogno è una fabbrica per la creazione di oggetti Spec per ogni prodotto, ad esempio

class SpecFactory
{
    public function CreateSpec(productName) {
        spec = new Specs();
        spec->product=productName;
        if (productName == "A") 
        {
            spec->calcFactor= 3;        
            // ... more parameters for product A here
        }
        else (productName == "B")
        {
            spec->calcFactor= 2;
            // ... more parameters for product B here
        }
        else
        {
           spec->calcFactor= 1; // default
        }
        return spec; 
    }

allora il tuo codice finale sarà simile a questo:

class ProductA extends Product
{
    public $calc;

    public function __construct() {
        spec = SpecFactory->CreateSpec("A");  // sets spec->calcFactor to 3 
        $this->calc = new CalcLibrary(spec); 
    }
}

o

class GraphB extends Graph
{
    public $calc;

    public function __construct()    {
        spec = SpecFactory->CreateSpec("A");  // sets spec->calcFactor to 2
        $this->calc = new CalcLibrary(spec); 
    }
}

Il codice della tua fabbrica sarà abbastanza semplice, conterrà il unico e solo "interruttore" tra diversi prodotti nell'intero programma. La libreria ottiene quindi la specifica "iniettata" (semplice iniezione di dipendenza) e ogni volta che c'è qualcosa di specifico del prodotto, consulta le specifiche per fornire i parametri. I parametri delle specifiche possono essere più complicati, forse un oggetto polimorfico con una derivazione per ciascun prodotto, per supportare scenari più complessi. Probabilmente sai già come si chiama questo: è il ben noto modello di strategia . Come forma "leggera" del modello di strategia, puoi anche provare se semplici "oggetti funzione" si adattano alle tue esigenze (AFAIK PHP supporta questo tipo di cose funzionali).

In effetti, come deve apparire la tua classe spec potrebbe essere diversa nella realtà. Non so come sia oggi, forse contiene un sacco di cose indipendenti dal prodotto, se questo è il caso puoi separare la configurazione specifica del prodotto e la configurazione indipendente dal prodotto in classi separate "specs", ma spero che tu possa ottenere idea generale: separa tutte le cose specifiche del prodotto in un unico pezzo di configurazione che è semplice e facile da gestire, e lascia che la libreria usi quella configurazione.

    
risposta data 22.01.2015 - 19:01
fonte

Leggi altre domande sui tag