2010-06-28 20 views
12

Dopo aver letto il libro più eccellente "Head First Design Patterns", ho iniziato a fare proselitismo ai miei colleghi i benefici dei modelli e dei principi di progettazione. Mentre esaltavo le virtù del mio schema preferito - Strategia Pattern - mi è stata fatta una domanda che mi ha fatto riflettere. La strategia, ovviamente, usa l'ereditarietà e la composizione e io ero in una delle mie invettive su "programma su un'interfaccia (o supertipo) non su un'implementazione", quando un collega chiese "perché usare una classe base astratta invece di una classe concreta?" .
Potrei solo inventare "bene costringi le tue sottoclassi a implementare metodi astratti e impedire loro di creare un'istanza dell'ABC". Ma ad essere onesti, la domanda mi ha sorpreso. Sono questi i soli benefici dell'uso di una classe base astratta su una classe concreta nella parte superiore della mia gerarchia?Classe Base astratta rispetto alla classe Concrete come SuperType

+0

A mio parere SI. Ma penso che sia una "caratteristica" molto importante di un linguaggio per forzare qualcuno che eredita da questa classe per implementare un metodo. A volte è necessario creare una classe generale ma non è possibile implementare tutte le funzionalità perché sarebbe troppo specifica. – KroaX

risposta

22

Se sono necessari metodi specifici da implementare, utilizzare un'interfaccia. Se c'è una logica condivisa che può essere estratta, usa una classe base astratta. Se il set base di funzionalità è completo, è possibile utilizzare una classe concreate come base. Una classe base astratta e un'interfaccia non possono essere istanziate direttamente, e questo è uno dei vantaggi. Se è possibile utilizzare un tipo concreto, è necessario eseguire i metodi di override e questo ha un "odore di codice".

+0

+1. Ottimo scarico di cosa usare e quando. – NotMe

+1

Eh? Perché il metodo ignorerebbe l'odore? – ladenedge

+5

Il codice con metodi virtuali è molto più difficile da modificare in futuro senza interrompere un contratto comportamentale implicito. Quindi in .NET hanno deciso che rendere un metodo superabile deve essere una decisione consapevole e deve essere attentamente considerato. Quindi qualsiasi metodo superabile senza istruzioni per l'implementatore è un "odore di codice". –

1

Sì, anche se è possibile utilizzare un'interfaccia per imporre a una classe l'implementazione di metodi specifici.

Un'altra ragione per utilizzare una classe astratta rispetto a una classe concreta è che una classe astratta non può essere istanziata. A volte anche tu non vorresti che succedesse, quindi una classe astratta è la strada da percorrere.

5

Programma di interfaccia, non di implementazione ha poco a che fare con classi astratte e concrete. Ricorda lo template method pattern? Le lezioni, astratte o concrete, sono i dettagli di implementazione.

E la ragione per utilizzare classi astratte anziché classi concrete è che è possibile invocare metodi senza implementarli, ma lasciandoli invece implementati in sottoclassi.

programmazione a un'interfaccia è una cosa diversa - si sta definendo cosa vostra API fa, non come lo fa. E questo è denotato dalle interfacce.

Nota una differenza chiave: è possibile avere i metodi protected abstract, il che significa che si tratta di dettagli di implementazione. Ma tutti i metodi di interfaccia sono pubblici, parte dell'API.

+0

Per aggiungere a questo, "Programma su un'implementazione" farebbe cose come usare la refelection per accedere ai campi privati ​​di una classe. –

1

Prima di tutto, il modello di strategia non dovrebbe quasi mai essere usato nel moderno C#. È principalmente per le lingue come Java che non supportano i puntatori di funzione, i delegati o le funzioni di prima classe. Lo vedrai nelle vecchie versioni di C# in interfacce come IComparer.

Come per Abstract Base Class rispetto a Concrete Class, la risposta in Java è sempre "Cosa funziona meglio in questa situazione?" Se le tue strategie possono condividere il codice, allora lascia che lo facciano.

Gli schemi di progettazione non sono istruzioni su come fare qualcosa. Sono modi per classificare le cose che abbiamo già fatto.

0

Se il cliente fa affidamento su "contratto di comportamento implicito [s]", è programmato contro un'implementazione e contro un comportamento non garantito. L'override del metodo mentre segue il contratto espone solo bug nel client, non li causa.

OTOH, l'errore di assumere contratti che non ci sono è meno probabile che causi problemi se il metodo in questione non è virtuale, vale a dire, ignorarlo non può causare problemi perché non può essere sovrascritto. Solo se l'implementazione del metodo originale viene modificata (pur continuando a rispettare il contratto), può interrompere il client.

0

La questione se una classe base debba essere astratta o concreta dipende IMHO in gran parte dall'utilità di un oggetto di classe base che implementasse solo comportamenti comuni a tutti gli oggetti della classe. Considera un WaitHandle. Chiamare "wait" su di esso causerà il blocco del codice fino a quando alcune condizioni non saranno soddisfatte, ma non esiste un modo comune per dire a un oggetto WaitHandle che la sua condizione è soddisfatta. Se si potesse istanziare un "WaitHandle", invece di essere in grado di istanziare istanze di tipi derivati, un tale oggetto non dovrebbe mai aspettare, o attendere sempre per sempre. Quest'ultimo comportamento sarebbe abbastanza inutile; il primo potrebbe essere stato utile, ma potrebbe essere raggiunto quasi altrettanto bene con un ManualResetEvent assegnato staticamente (penso che quest'ultimo sprechi alcune risorse, ma se è assegnato staticamente la perdita totale di risorse dovrebbe essere banale).

In molti casi, penso che la mia preferenza sarebbe utilizzare i riferimenti a un'interfaccia anziché a una classe base astratta, ma fornire all'interfaccia una classe base che fornisce una "implementazione del modello". Quindi in qualsiasi posto si userebbe un riferimento a un MyThing, si fornirebbe un riferimento a "iMyThing". Può darsi che il 99% (o anche il 100%) degli oggetti iMyThing siano in realtà un MyThing, ma se qualcuno ha mai bisogno di avere un oggetto iMyThing che erediti da qualcos'altro, uno potrebbe farlo.

1

Le classi base astratte vengono solitamente utilizzate in scenari in cui il progettista desidera forzare un modello architettonico in cui determinati compiti devono essere eseguiti nello stesso modo da tutte le classi mentre altri comportamenti dipendono dalle sottoclassi. esempio:

public abstract class Animal{ 

public void digest(){ 

} 

public abstract void sound(){ 

} 
} 

public class Dog extends Animal{ 
public void sound(){ 
    System.out.println("bark"); 
} 
} 

stratergy modello chiede ai progettisti di utilizzare il comportamento composizionale per i casi in cui ci sono famiglie di alogirthms per un comportamento.

0

Preferisco classi base astratte in scenari di seguito:

  1. Una classe di base non può esistere senza una sub class => la classe base è semplicemente astratta e può' t essere un'istanza.
  2. Una classe base non può avere un'implementazione completa o concreta di un metodo => L'implementazione di un metodo è una classe base incompleta e solo le sotto classi possono fornire un'implementazione completa.
  3. classe base fornisce un modello per l'attuazione metodo ma dipende ancora sulla classe concreta per completare l'implementazione del metodo - Template_method_pattern

Un semplice esempio per illustrare i punti

Shape è astratta di cui sopra e non può esiste senza forma concreta come Rectangle. Disegnare un Shape non può essere implementato alla classe Shape poiché forme diverse hanno formule diverse.L'opzione migliore per gestire scenario: lasciare draw() attuazione sottoclassi

abstract class Shape{ 
    int x; 
    int y; 
    public Shape(int x,int y){ 
     this.x = x; 
     this.y = y; 
    } 
    public abstract void draw(); 
} 
class Rectangle extends Shape{ 
    public Rectangle(int x,int y){ 
     super(x,y); 
    } 
    public void draw(){ 
     //Draw Rectangle using x and y : length * width 
     System.out.println("draw Rectangle with area:"+ (x * y)); 
    } 
} 
class Triangle extends Shape{ 
    public Triangle(int x,int y){ 
     super(x,y); 
    } 
    public void draw(){ 
     //Draw Triangle using x and y : base * height /2 
     System.out.println("draw Triangle with area:"+ (x * y)/2); 
    } 
} 
class Circle extends Shape{ 
    public Circle(int x,int y){ 
     super(x,y); 
    } 
    public void draw(){ 
     //Draw Circle using x as radius (PI * radius * radius 
     System.out.println("draw Circle with area:"+ (3.14 * x * x)); 
    } 
} 

public class AbstractBaseClass{ 
    public static void main(String args[]){ 
     Shape s = new Rectangle(5,10); 
     s.draw(); 
     s = new Circle(5,10); 
     s.draw(); 
     s = new Triangle(5,10); 
     s.draw(); 
    } 
} 

uscita:

draw Rectangle with area:50 
draw Circle with area:78.5 
draw Triangle with area:25 

Sopra codice copre punto 1 e il punto 2. È possibile modificare draw() metodo come metodo di modello se classe base ha qualche implementazione e chiama il metodo sub-class per completare la funzione draw().

Ora stesso esempio con il metodo del modello modello:

abstract class Shape{ 
    int x; 
    int y; 
    public Shape(int x,int y){ 
     this.x = x; 
     this.y = y; 
    } 
    public abstract void draw(); 

    // drawShape is template method 
    public void drawShape(){ 
     System.out.println("Drawing shape from Base class begins"); 
     draw(); 
     System.out.println("Drawing shape from Base class ends");  
    } 
} 
class Rectangle extends Shape{ 
    public Rectangle(int x,int y){ 
     super(x,y); 
    } 
    public void draw(){ 
     //Draw Rectangle using x and y : length * width 
     System.out.println("draw Rectangle with area:"+ (x * y)); 
    } 
} 
class Triangle extends Shape{ 
    public Triangle(int x,int y){ 
     super(x,y); 
    } 
    public void draw(){ 
     //Draw Triangle using x and y : base * height /2 
     System.out.println("draw Triangle with area:"+ (x * y)/2); 
    } 
} 
class Circle extends Shape{ 
    public Circle(int x,int y){ 
     super(x,y); 
    } 
    public void draw(){ 
     //Draw Circle using x as radius (PI * radius * radius 
     System.out.println("draw Circle with area:"+ (3.14 * x * x)); 
    } 
} 

public class AbstractBaseClass{ 
    public static void main(String args[]){ 
     Shape s = new Rectangle(5,10); 
     s.drawShape(); 
     s = new Circle(5,10); 
     s.drawShape(); 
     s = new Triangle(5,10); 
     s.drawShape(); 
    } 
} 

uscita:

Drawing shape from Base class begins 
draw Rectangle with area:50 
Drawing shape from Base class ends 
Drawing shape from Base class begins 
draw Circle with area:78.5 
Drawing shape from Base class ends 
Drawing shape from Base class begins 
draw Triangle with area:25 
Drawing shape from Base class ends 

volta che avete deciso che si deve fare come metodo di abstract, avete due opzioni: o utente interface o abstract classe. È possibile dichiarare i metodi in interface e definire la classe abstract come classe che implementa lo interface.

Problemi correlati