Le classi / metodi astratti sono obsoleti?

35

Ero solito creare molte classi / metodi astratti. Quindi ho iniziato a utilizzare le interfacce.

Ora non sono sicuro che le interfacce non stiano rendendo obsolete le classi astratte.

Hai bisogno di una lezione completamente astratta? Crea invece un'interfaccia. Hai bisogno di una classe astratta con qualche implementazione in essa? Crea un'interfaccia, crea una classe. Eredita la classe, implementa l'interfaccia. Un ulteriore vantaggio è che alcune classi potrebbero non aver bisogno della classe genitore, ma implementeranno semplicemente l'interfaccia.

Quindi, le classi / metodi astratti sono obsoleti?

    
posta Boris Yankov 21.07.2011 - 17:39
fonte

11 risposte

111

No.

Le interfacce non possono fornire un'implementazione predefinita, possono essere classi e metodi astratti. Ciò è particolarmente utile per evitare la duplicazione del codice in molti casi.

Questo è anche un ottimo modo per ridurre l'accoppiamento sequenziale. Senza metodo / classi astratti, non è possibile implementare un modello di metodo del modello. Ti suggerisco di guardare questo articolo su Wikipedia: link

    
risposta data 21.07.2011 - 17:46
fonte
15

Esiste un metodo astratto in modo che tu possa chiamarlo dalla tua classe base ma implementarlo in una classe derivata. Quindi la tua classe base sa:

public void DoTask()
{
    doSetup();
    DoWork();
    doCleanup();
}

protected abstract void DoWork();

Questo è un modo abbastanza bello per implementare un buco nel modello centrale senza che la classe derivata sia a conoscenza delle attività di installazione e pulizia. Senza metodi astratti, dovresti fare affidamento sulla classe derivata che implementa DoTask e ricorda di chiamare base.DoSetup() e base.DoCleanup() per tutto il tempo.

Modifica

Inoltre, grazie a deadalnix per aver pubblicato un link al modello di metodo dei modelli , che è quello che ho descritto sopra senza conoscere davvero il nome. :)

    
risposta data 21.07.2011 - 17:48
fonte
10

No, non sono obsoleti.

In realtà, c'è una differenza oscura ma fondamentale tra classi astratte / metodi e interfacce.

se l'insieme di classi in cui uno di questi deve essere usato ha un comportamento comune che condividono (classi correlate, intendo), quindi vai su Abstract classes / methods.

Esempio: impiegato, ufficiale, direttore - tutte queste classi hanno in comune il CalculateSalary (), usano le classi base astratte. CalculateSalary () può essere implementato in modo diverso ma ci sono alcune altre cose come GetAttendance () ad esempio che ha una definizione comune in classe base.

Se le tue classi non hanno nulla di comune (classi non correlate, nel contesto scelto) tra di esse ma hanno un'azione molto diversa nell'implementazione, allora vai a Interfaccia.

Esempio: classi relative a mucca, panchina, auto, telesope ma Isortable può essere lì per ordinarle in un array.

Questa differenza viene solitamente ignorata quando ci si avvicina da una prospettiva polimorfica. Ma personalmente ritengo che ci siano situazioni in cui uno è un apt rispetto all'altro per il motivo spiegato sopra.

    
risposta data 21.07.2011 - 18:08
fonte
6

Oltre alle altre buone risposte, c'è una differenza fondamentale tra le interfacce e le classi astratte che nessuno ha menzionato specificamente, vale a dire che le interfacce sono molto meno affidabili e quindi impongono molto maggiore carico di test rispetto alle classi astratte. Ad esempio, considera questo codice C #:

public abstract class Frobber
{
    private Frobber() {}
    public abstract void Frob(Frotz frotz);
    private class GreenFrobber : Frobber
    { ... }
    private class RedFrobber : Frobber
    { ... }
    public static Frobber GetFrobber(bool b) { ... } // return a green or red frobber
}

public sealed class Frotz
{
    public void Frobbit(Frobber frobber)
    {
         ...
         frobber.Frob(this);
         ...
    }
}

Sono sicuro che ci sono solo due percorsi di codice che devo testare. L'autore di Frobbit può fare affidamento sul fatto che lo stroboscopio sia rosso o verde.

Se invece diciamo:

public interface IFrobber
{
    void Frob(Frotz frotz);
}
public class GreenFrobber : IFrobber
{ ... }
public class RedFrobber : Frobber
{ ... }

public sealed class Frotz
{
    public void Frobbit(IFrobber frobber)
    {
         ...
         frobber.Frob(this);
         ...
    }
}

Ora so assolutamente niente sugli effetti di quella chiamata a Frob lì. Devo essere sicuro che tutto il codice in Frobbit sia solido contro qualsiasi implementazione possibile di IFrobber , anche implementazioni da parte di persone incompetenti (cattive) o attivamente ostili a me o ai miei utenti (molto peggio).

Le classi astratte ti consentono di evitare tutti questi problemi; usali!

    
risposta data 21.07.2011 - 23:44
fonte
3

Lo dici tu stesso:

You need an abstract class with some implementation in it? Create an interface, create a class. Inherit the class, implement the interface

sembra molto lavoro rispetto a "ereditare la classe astratta". Puoi lavorare da solo apprendendo il codice da una vista 'purista', ma trovo di averne abbastanza da fare già senza tentare di aggiungere al mio carico di lavoro senza alcun vantaggio pratico.

    
risposta data 21.07.2011 - 17:49
fonte
3

Come ho commentato su @deadnix post: le implementazioni parziali sono un anti-pattern, nonostante il modello di template le formalizzi.

Una soluzione pulita per questo esempio di wikipedia del modello di modello :

interface Game {
    void initialize(int playersCount);
    void makePlay(int player);
    boolean done();
    void finished();
    void printWinner();
}
class GameRunner {
    public void playOneGame(int playersCount, Game game) {
        game.initialize(playersCount);
        int j = 0;
        for (int i = 0; !game.finished(); i++)
             game.makePlay(i % playersCount);
        game.printWinner();
    }
} 
class Monopoly implements Game {
     //... implementation
}

Questa soluzione è migliore, perché usa composizione invece dell'ereditarietà . Il modello di modello introduce una dipendenza tra l'implementazione delle regole del monopolio e l'implementazione di come i giochi devono essere eseguiti. Tuttavia si tratta di due responsabilità completamente diverse e non vi è alcuna buona ragione per accoppiarle.

    
risposta data 21.07.2011 - 23:47
fonte
2

Le classi astratte non sono interfacce. Sono classi che non possono essere istanziate.

You need a fully abstract class? Create an interface instead. You need an abstract class with some implementation in it? Create an interface, create a class. Inherit the class, implement the interface. An additional benefit is that some classes may not need the parent class, but will just implement the interface.

Ma allora avresti una classe inutile non astratta. Sono necessari metodi astratti per riempire il buco della funzionalità nella classe base.

Ad esempio, data questa classe

public abstract class Frobber {
    public abstract void Frob();

    public abstract boolean IsFrobbingNeeded { get; }

    public void FrobUntilFinished() {
        while (IsFrobbingNeeded) {
            Frob();
        }
    }
}

Come implementeresti questa funzionalità di base in una classe che non ha né Frob()IsFrobbingNeeded ?

    
risposta data 21.07.2011 - 17:47
fonte
2

No. Anche la tua alternativa proposta include l'uso di classi astratte. Inoltre, dal momento che non hai specificato la lingua, ho intenzione di andare avanti e dire che il codice generico è l'opzione migliore rispetto all'ereditarietà fragile comunque. Le classi astratte presentano vantaggi significativi rispetto alle interfacce.

    
risposta data 21.07.2011 - 17:47
fonte
1

Sono un creatore della struttura servlet in cui le classi astratte svolgono un ruolo essenziale. Direi di più, ho bisogno di metodi semi astratti, quando un metodo deve essere sovrascritto nel 50% dei casi e mi piacerebbe vedere l'avviso dal compilatore su quel metodo non è stato sovrascritto. Risolve il problema aggiungendo annotazioni. Tornando alla tua domanda, ci sono due diversi casi d'uso di classi astratte e interfacce, e nessuno è obsoleto finora.

    
risposta data 22.07.2011 - 02:36
fonte
0

Non penso che siano obsoleti dalle interfacce, ma potrebbero essere resi obsoleti dal modello di strategia.

L'uso principale di una classe astratta è di posticipare una parte dell'implementazione; per dire "questa parte dell'implementazione della classe può essere resa diversa".

Sfortunatamente, una classe astratta costringe il cliente a farlo tramite ereditarietà . Mentre il modello strategico ti consentirà di ottenere lo stesso risultato senza alcuna eredità. Il client può creare istanze della tua classe anziché definirne sempre la propria, e la "implementazione" della classe (comportamento) può variare in modo dinamico. Il modello strategico ha il vantaggio in più che il comportamento può essere modificato in fase di esecuzione, non solo in fase di progettazione, e ha anche un accoppiamento significativamente più debole tra i tipi coinvolti.

    
risposta data 06.04.2017 - 23:04
fonte
0

In genere ho affrontato problemi di manutenzione di gran lunga superiori a quelli delle interfacce pure rispetto agli ABC, persino agli ABC utilizzati con ereditarietà multipla. YMMV: non so, forse il nostro team li ha usati in modo inadeguato.

Detto questo, se usiamo un'analogia del mondo reale, quanta utilità c'è per le pure interfacce completamente prive di funzionalità e stato? Se uso l'USB come esempio, si tratta di un'interfaccia ragionevolmente stabile (penso che ora siamo a USB 3.2, ma ha anche mantenuto la compatibilità con le versioni precedenti).

Tuttavia non è un'interfaccia senza stato. Non è privo di funzionalità. È più simile a una classe base astratta che a un'interfaccia pura. In realtà è più vicino a una classe concreta con requisiti funzionali e di stato molto specifici, con l'unica astrazione che è ciò che si connette alla porta essendo l'unica parte sostituibile.

Altrimenti sarebbe solo un "buco" nel tuo computer con un fattore di forma standardizzato e requisiti funzionali molto più sciolti che non farebbero nulla da solo fino a quando ogni produttore non ha creato il proprio hardware per fare quel buco fare qualcosa, a quel punto diventa uno standard molto più debole e nient'altro che un "buco" e una specifica di cosa dovrebbe fare, ma nessuna disposizione centrale su come farlo. Nel frattempo potremmo finire con 200 modi diversi di farlo dopo che tutti i produttori di hardware cercheranno di inventare i propri modi per collegare funzionalità e stato a quel "buco".

E a quel punto potremmo avere determinati produttori che introducono diversi problemi rispetto ad altri. Se abbiamo bisogno di aggiornare le specifiche potremmo avere 200 diverse implementazioni concrete di porte USB con modi completamente diversi di affrontare le specifiche che devono essere aggiornate e testate. Alcuni produttori potrebbero sviluppare di fatto implementazioni standard che condividono tra loro (la classe di base analogica che implementa tale interfaccia), ma non tutti. Alcune versioni potrebbero essere più lente di altre. Alcuni potrebbero avere un rendimento migliore ma una latenza peggiore o viceversa. Alcuni potrebbero usare più energia della batteria di altri. Alcuni potrebbero sfaldarsi e non funzionare con tutto l'hardware che dovrebbe funzionare con le porte USB. Alcuni potrebbero richiedere l'installazione di un reattore nucleare per funzionare che ha la tendenza a dare ai suoi utenti un avvelenamento da radiazioni.

E questo è quello che ho trovato, personalmente, con interfacce pure. Ci possono essere alcuni casi in cui hanno senso, come solo per modellare il fattore di forma di una scheda madre rispetto a un caso di CPU. Le analogie dei fattori di forma sono, infatti, praticamente prive di status e prive di funzionalità, come nel caso del "buco" analogico. Ma considero spesso un grosso errore per i team considerarlo in qualche modo superiore in tutti i casi, nemmeno vicino.

Al contrario, penso che molti casi sarebbero meglio risolti dagli ABC rispetto alle interfacce se queste sono le due scelte a meno che la tua squadra non sia così gigantesca che è effettivamente desiderabile avere l'equivalente analogico sopra di 200 implementazioni USB concorrenti piuttosto che una standard centrale da mantenere. In una ex squadra in cui mi trovavo, ho dovuto lottare duramente solo per sciogliere lo standard di codifica per consentire l'ABC e l'ereditarietà multipla, e principalmente in risposta a questi problemi di manutenzione sopra descritti.

    
risposta data 19.12.2017 - 05:31
fonte

Leggi altre domande sui tag