Come ridurre il codice "mappatura"?

2

Ho due componenti, entrambi sono una coppia parser / builder. Quindi componente A può analizzare A e creare A , componente B può analizzare B e creare B .

A e B contengono entrambe entità diverse, che vengono estratte dai parser.

Esempio, BParser funziona allo stesso modo:

$AParserResult = AParser::parse(A);
dump(
    $AParserResult->getEntityZ(),
    $AParserResult->getEntityX(),
    $AParserResult->getEntityY(), 
    ...
);

$aBuilderSpecification = ABuilderSpecification::new()
    ->addEntityZ(Z)
    ->addEntityY(Y)  
    ->addEntityX(X);

A = ABuilder::build($aBuilderSpecification);
// now I have a "fully functionally" A again

Il mio compito è scrivere componente C che utilizza AParser per analizzare un A , quindi creare un BBuilderSpecification da AParserResult e poi passarlo a BBuilder e ottenere un B da esso - e viceversa, creando un B da un A .

I parser / builder sono relativamente semplici da usare, tuttavia, il mio codice nel componente C si presenta principalmente come segue:

// in method 'transformAToB()'

$AParserResult = AParser::parse(A);
$BBuilderSpecification = BBuilderSpecification::new();

if ($AParserResult->hasEntityZ()) {
    $BBuilderSpecification->setEntityZ($AParserResult->getEntityZ());
}

if ($AParserResult->hasEntityX()) {
    $BBuilderSpecification->setEntityX($AParserResult->getEntityX());
}

// sometimes some simple conditions maybe, and simple transformations
if ($AParserResult->hasEntityY() && someCondition()) {
    $BBuilderSpecification->setEntityY(to_lower_case($AParserResult->getEntityY()));
}

...

// Goes on for about 10 more entities
// and very similar code in the method 'transformBToA'

Questo è un codice molto ripetitivo - mi chiedo se ci sia un modo per ripulirlo / refactarlo - senza introdurre ulteriore complessità aggiungendo una configurazione XML o così ... alla fine è (quasi) solo codice boilerplate.

La lingua è PHP, se questo è importante.

    
posta Max 17.11.2016 - 05:16
fonte

1 risposta

1

Primo passo: introdurre la mappa di trasformazione

Il

dump(
    $AParserResult->getEntityZ(),
    $AParserResult->getEntityX(),
    $AParserResult->getEntityY(), 
    ...
);
L'esempio

purtroppo non è molto specifico, ma potrebbe essere sensato creare un ulteriore getter getEntities che restituisca un dizionario di tutte le entità, in questo caso Z, X e Y. Analogamente, un nuovo metodo builder addEntities($entities) accetterà un dizionario contenente più entità.

Poi c'è una fase di trasformazione. Dato il tuo esempio:

if ($AParserResult->hasEntityZ()) {
    $BBuilderSpecification->setEntityZ($AParserResult->getEntityZ());
}

if ($AParserResult->hasEntityX()) {
    $BBuilderSpecification->setEntityX($AParserResult->getEntityX());
}

// sometimes some simple conditions maybe, and simple transformations
if ($AParserResult->hasEntityY() && someCondition()) {
    $BBuilderSpecification->setEntityY(to_lower_case($AParserResult->getEntityY()));
}

le entità Z e X sono mappate come sono, mentre Y è più difficile:

  • Ha applicato la funzione to_lower_case ,

  • Viene mappato solo quando viene soddisfatta una condizione.

Un altro modo per vederlo è che Z e X sono solo casi specifici in cui:

  • La funzione è l'identità,

  • La condizione è semplicemente if (true); .

Il codice risultante assomiglia a questo:

if ($AParserResult->hasEntityZ() && conditionZ()) {
    $BBuilderSpecification->setEntityZ(transformZ($AParserResult->getEntityZ()));
}

if ($AParserResult->hasEntityX() && conditionX()) {
    $BBuilderSpecification->setEntityX(transformX($AParserResult->getEntityX()));
}

if ($AParserResult->hasEntityY() && conditionY()) {
    $BBuilderSpecification->setEntityY(transformY($AParserResult->getEntityY()));
}

È quindi possibile spostare le condizioni e le funzioni di trasformazione su una mappa. Combinato con getEntities e addEntities precedentemente creati, si finisce con il seguente codice:

$AParserResult = AParser::parse(A);
$BBuilderSpecification = BBuilderSpecification::new();

$transformRules = ... // Load the map here.

$allEntities = $AParserResult->getEntities();
$filtered = array_filter($allEntities, $transformRules->canUse);
$transformed = array_map($transformRules->transform, $filtered);
$BBuilderSpecification->setEntities($transformed);

Secondo passaggio: sposta la logica dalla mappa alle entità stesse

Il codice sopra riportato rimane invariato quando aggiungi nuovi tipi di entità, il che è positivo. Tuttavia, la mappa deve essere cambiata, e non va bene affatto. Il problema è che quando aggiungi un nuovo tipo di entità, non avrai assolutamente la certezza che da qualche parte, in futuro, questo cambiamento porterà a un bug; anche tracciare il bug al cambiamento non sarà facile.

C'è un'alternativa migliore.

Puoi creare classi per ogni tipo di entità. Ad esempio, entità Z e X saranno istanze di classe α e l'entità Y sarà un'istanza di classe β. Sia α che β implementeranno un'interfaccia comune che verrà quindi utilizzata dal metodo transformAToB .

interface IEntity
{
    public function canUse();
    public function transform();
}

class α implements IEntity
{
    ...
    public function canUse() {
        return true;
    }

    public function transform() {
        return $this->value;
    }
}

class β implements IEntity
{
    ...
    public function canUse() {
        ... // Implement whatever logic was in 'someCondition()'.
    }

    public function transform() {
        return to_lower_case($this->value);
    }
}

Ora il codice componente C ha il seguente aspetto:

$AParserResult = AParser::parse(A);
$BBuilderSpecification = BBuilderSpecification::new();

$allEntities = $AParserResult->getEntities();

$filtered = array_filter($allEntities, function ($entity) {
    return $entity->canUse();
});

$transformed = array_map(function () {
    return $entity->transform();
}, $filtered);

$BBuilderSpecification->setEntities($transformed);

Ora quando aggiungi un'entità W che ha una logica diversa da Z , X e Y , non c'è nessuna mappa da modificare: tutto ciò che devi fare è includere la nuova logica all'interno del nuovo classe.

    
risposta data 24.12.2016 - 19:27
fonte

Leggi altre domande sui tag