Programmazione orientata all'aspetto e implicità

7

Supponiamo che abbia una classe che rappresenta un'immagine e ha un numero di metodi.

class Image
{
    circle(x,y,radius,color);
    square(x,y,w,h,color);
    floodfill(x,y,color)
    clear();
}

Inoltre, voglio avere la funzionalità di annullamento. Un modo semplice per implementare questo è mantenere un elenco di tutte le azioni che sono state eseguite. Quando annullo, eseguo nuovamente tutte le azioni. Una soluzione sarebbe quella di implementare un aspetto, qualcosa del genere:

aspect ImageUndo
{
    on Image::circle, Image::square, Image::floodfill
    precall(object, args)
    {
         object.actions_list.add(args)
    }
}

In sostanza, questo aspetto ha ora modificato il comportamento di Image. Questo mi preoccupa. In particolare, un altro programmatore che non ha familiarità con l'esistenza dell'aspetto ImageUndo può incontrare i seguenti problemi:

  1. Aggiunge un metodo e non funziona con la funzionalità di annullamento.
  2. Tentativo di eseguire il debug del meccanismo di annullamento, non è chiaro dove viene aggiunto l'elenco_selezioni.

D'altra parte potremmo avere

class Image
{
    @undoable_action
    circle(x,y,radius,color);

    @undoable_action
    square(x,y,w,h,color);

    @undoable_action
    floodfill(x,y,color)

    @undoable_action
    clear();
}

Il che non mi infastidisce più di tanto perché dà un'idea di dove cercare il codice di annullamento e fa sì che il nuovo programmatore noterà probabilmente che lo aggiungerà automaticamente a un nuovo metodo.

Per riassumere: aspetti (almeno quelli come quello che ho mostrato) sembrano portare "magia implicita" in ciò che fa il codice. Mi sembra che l'implicità sia pericolosa e dovremmo renderla esplicita.

Ci sono buone ragioni per l'implicito? Le persone che effettivamente usano il codice di scrittura AOP che esegue questo tipo di modifica?

Nota: questa è una rielaborazione di Sono sicuro problemi risolti in modo più elegante con AOP? che è stato chiuso perché la mia versione precedente si era imbattuta, involontariamente, come un ranting.

    
posta Winston Ewert 16.11.2010 - 15:43
fonte

2 risposte

3

Le classi che implementano preoccupazioni trasversali hanno nulla da dire sulle funzionalità di base delle classi che sono AOP'd. Questa è l'idea principale qui.

Il tuo esempio è uno di un'azione di annullamento. Facciamo un ulteriore passo avanti: faremo una copia profonda della tua classe e la conserveremo da qualche parte. Se vogliamo eseguire un annullamento, tutto ciò che dobbiamo fare è invertire la copia profonda. Ciò "ripristinerà" la classe al suo stato originale. Non è nemmeno necessario annotare i membri della classe per farlo funzionare (sebbene si possa annotare la classe stessa se si desidera che la copia approfondita funzioni automaticamente).

Ecco la mia domanda: questo processo di copia profonda ha qualsiasi cosa da fare con la funzionalità primaria della classe che viene copiata in profondità? La copia profonda interessa anche ciò che fa la classe?

Gli oggetti di serializzazione e deserializzazione sono un altro esempio di questo. Il processo di serializzazione non ha conoscenza del comportamento della classe e gli oggetti che vengono serializzati non hanno alcuna conoscenza del processo di serializzazione. È un potente strumento di disaccoppiamento.

    
risposta data 16.11.2010 - 16:55
fonte
1

Una cosa da sottolineare è che il codice non viene modificato attraverso gli aspetti, ma viene esteso. Può essere semantico, ma la distinzione è importante, specialmente quando si considera il principio aperto / chiuso .

Ora, da quello che posso dire (sembra forse l'aspettoj?), il tuo primo esempio applica l'aspetto ai metodi nella dichiarazione dell'aspetto, mentre il tuo secondo esempio applica l'aspetto tramite attributi sui metodi. Il problema che sembri avere è quello in cui qualcuno che estende il tuo codice potrebbe non essere in grado di capire come includere l'annullamento nelle sue aggiunte utilizzando il primo esempio.

  1. La documentazione esiste per un motivo. Quindi scrivi alcuni per i tuoi consumatori.
  2. Utilizza un design diverso per implementare la funzionalità per loro.

Credo che funzioni come la seguente:

public abstract class ImageOperation
{
    @undoable_action
    public abstract void Action(Image img);
}

public class Flood_Fill : ImageOperation
{
    public void Action(Image img)
    {
        // flood-fill
    }
}

Ora, dopo aver rimosso le operazioni sull'immagine, puoi fornire ImageOperation in una .dll diversa dal tuo programma, e puoi usarlo come base per i plugin ImageOperation, in modo che gli altri non debbano vedere il tuo codice a tutti per produrre un'operazione, e ancora implicitamente fornisci un annullamento per loro.

    
risposta data 16.11.2010 - 17:58
fonte

Leggi altre domande sui tag