Come evitare la duplicazione del codice durante l'estensione di due classi umodificabili

3

Ho già questa struttura della classe principale che non può essere modificata :

class A {
    //some basic fields and methods
}
class B {
    //some another basic fields and methods
}

Sono le classi di base e aggiungo alcune funzionalità in aggiunta al sistema esistente. Devo aggiungere alcune funzionalità comuni a queste classi. La sua implementazione è quasi identica per A e B . La mia soluzione attuale è costruire qualcosa del genere:

class A {
    //some basic fields and methods
}
class B {
    //some another basic fields and methods
}

class Foo extends A {
    //my new features
}
class Bar extends B {
    //my new features (identical with Foo)
}

Il problema è che non posso cambiare la classe A o B e non solo non posso cambiarli, ho bisogno di entrambe le classi A e B e le mie classi Foo e Bar esistenti. Quindi usare la riflessione per cambiare le classi A e B non è una soluzione.

Ho anche provato ad aggiungere questa funzionalità come api-object:

class FeatureApi {
    my new features
}
class Foo extends A {
    FeatureApi api;
}
class Bar extends B {
    FeatureApi api;
}

Ma ottieni un altro problema: i nuovi metodi devono accedere ai miei campi / metodi delle classi. Ma non li ho in api-object.

In breve:

  • Non riesco a modificare le classi esistenti
  • Voglio creare alcune classi personalizzate, che estendono A e B
  • Non ho bisogno di accedere a nessun campo privato.
  • Oltre a modifiche specifiche, le mie classi avranno molte funzionalità identiche per entrambi.

UPD 2 Lo scopo principale è quello di implementare esattamente lo stesso comportamento in due classi personalizzate, ereditate da due classi di base intercambiabili. Come vedo ora, l'ereditarietà delle classi base non ha alcun senso, quindi l'ho rimosso e ho chiarito la mia domanda.

    
posta TEXHIK 05.03.2018 - 15:43
fonte

3 risposte

0

puoi farlo con il tuo esempio FeatureApi. hai bisogno di un paio di modifiche per esporre i campi protetti in A o B al FeatureApi avvolto

(sto per andare con c #)

class FeatureApi 
{
    FeatureApi(IExposeStuff parent) {..}

    public string myNewFeature()
    {
       var info = parent.GetProtectedInfo();
       //do stuff
    }
}

interface IExposeStuff
{
    string GetProtectedInfo();
}

class NewA : A, IExposeStuff
 {
    FeatureApi api;
    public NewA()
    {
        this.api = new FeatureApi(this)
    }

    public string GetProtectedInfo()
    {
        return this.protectedInfo;
    }
}
    
risposta data 05.03.2018 - 16:56
fonte
1

Sfortunatamente sei un po 'indeciso. Qualcun altro ti ha bloccato in una gerarchia ereditaria che non puoi cambiare. A seconda di come funzionano A e B , potresti essere costretto ad estendere ogni classe separatamente per implementare il tuo comportamento. Ecco alcuni scenari in base ai vincoli che affronti.

Devi eseguire l'override dei metodi da A e B .

In questo scenario A e B chiamano i propri metodi e la funzionalità che stai implementando richiede l'intercettazione di queste chiamate. Ad esempio:

class A {
    private void fizz() {
        buzz();
    }
    void buzz(); // <- you must override this to implement your functionality
}

In questo scenario il modo solo per ottenere il tuo comportamento su un'istanza raw di A è di estendere A e sovrascrivere buzz . Allo stesso modo, l'unico modo per ottenere il tuo comportamento su un'istanza di B è estendere B e sovrascrivere buzz . Devi estenderli separatamente ma potresti essere in grado di riutilizzare il codice facendo in modo che i due override di buzz chiamino un metodo di utilità statica o un metodo di istanza separato in A .

Questa situazione è scomoda perché sei bloccato a duplicare del codice e se dovessi implementare più funzionalità allo stesso modo, ora hai 4 classi da estendere. Se devi farlo ancora una volta hai 8 classi da estendere. Come puoi vedere, questo approccio non si adatta affatto bene.

Non è necessario eseguire l'override dei metodi da A e B .

In questo scenario, i metodi da A e B non richiedono "richiama" la nuova funzionalità. Questo scenario potrebbe consentire di utilizzare la delega tramite il pattern decoratore, a seconda di quanto sono tolleranti A e B . Questo approccio comporta l'estensione di A con una sottoclasse che accetta l'istanza "reale" di A e delega tutte le chiamate di metodo ad esso, implementando la tua funzionalità in aggiunta alle chiamate di metodo a A . Allo stesso modo estendere B per decorare B .

Il vantaggio piccolo che questo approccio ha rispetto al primo approccio è che dovresti essere in grado di implementare qualsiasi funzionalità aggiunta in termini di metodi di utilità che prendono l'istanza "reale" di A . Poiché B è un A , puoi chiamare questo metodo di utilità sia dal A decorator che dal B decorator.

Questo approccio potrebbe non essere fattibile se A ha solo costruttori che prendono dati specifici, poiché ogni sottoclasse dovrebbe chiamare i costruttori della superclasse. Potresti fondere i dati passati alla superclasse, ma farlo è hacky e fragile (cosa succede se A aggiunge una convalida che i dati danneggiati falliscono?).

Puoi introdurre le interfacce.

Se sei in grado di introdurre interfacce per A e B allora le tue opzioni si aprono notevolmente. In questo caso è possibile implementare il modello decoratore in modo pulito, senza preoccuparsi di chiamare un costruttore di una superclasse di cemento. Hai ancora bisogno di implementare due decoratori ma, poiché tutte le funzionalità che aggiungi possono essere implementate con metodi di utilità statici che accettano gli oggetti decorati, è necessario solo un duplicato standard.

Si noti che con questo approccio, non è necessario esporre le interfacce del decoratore. Il codice dipenderà solo da A e B . Pertanto, se devi aggiungere più funzionalità sia a A che a B , devi solo implementarlo due volte. Se è necessario aggiungere funzionalità una terza volta, è comunque necessario implementarlo due volte. Pertanto questo approccio si adatta molto meglio del primo.

    
risposta data 05.03.2018 - 17:37
fonte
0

Non utilizzare l'ereditarietà o le interfacce, sono entrambi inappropriati per la tua situazione. Dovresti invece usare il contenimento.

Crea un'istanza privata di classe A in una nuova classe MyA. Implementa le tue nuove cose. Ogni volta che hai bisogno di alcune funzionalità chiuse di A, chiama A. Puoi creare i tuoi nuovi metodi che hanno lo stesso nome dei metodi chiusi in A e solo delegati. Fai lo stesso con B.

    
risposta data 05.03.2018 - 19:07
fonte

Leggi altre domande sui tag