2010-06-28 13 views
12

Nachdem ich das beste Buch "Head First Design Patterns" gelesen hatte, begann ich meinen Kollegen die Vorteile von Mustern und Designprinzipien zu vermitteln. Während ich die Vorzüge meines Lieblingsmusters - Strategy Pattern - pries, wurde mir eine Frage gestellt, die mich innehalten ließ. Strategie verwendet natürlich Vererbung und Komposition und ich war in einer meiner Tiraden über "Programm zu einer Schnittstelle (oder Supertyp) keine Implementierung", als ein Kollege fragte, "warum eine abstrakte Basisklasse anstelle einer konkreten Klasse verwenden?" .
Ich konnte nur "gut erzwingen Sie zwingen Ihre Unterklassen abstrakte Methoden zu implementieren und verhindern, dass sie die ABC instanziieren". Aber um ehrlich zu sein, die Frage hat mich aufgehalten. Sind dies die einzigen Vorteile einer abstrakten Basisklasse gegenüber einer konkreten Klasse an der Spitze meiner Hierarchie?Zusammenfassung Basisklasse vs. Betonklasse als SuperType

+0

Meiner Meinung nach JA. Aber ich denke, es ist ein sehr wichtiges "Merkmal" einer Sprache, jemanden, der von dieser Klasse erbt, zu zwingen, eine Methode zu implementieren. Manchmal müssen Sie eine allgemeine Klasse erstellen, aber nicht alle Funktionen implementieren, da dies zu spezifisch wäre. – KroaX

Antwort

22

Wenn Sie bestimmte Methoden implementieren möchten, verwenden Sie eine Schnittstelle. Wenn es eine gemeinsam genutzte Logik gibt, die herausgezogen werden kann, verwenden Sie eine abstrakte Basisklasse. Wenn der Basissatz von Funktionen eigenständig abgeschlossen ist, können Sie eine Concreate-Klasse als Basis verwenden. Eine abstrakte Basisklasse und eine Schnittstelle können nicht direkt instanziiert werden, und das ist einer der Vorteile. Wenn Sie einen konkreten Typ verwenden können, müssen Sie Methoden überschreiben, die einen "Code-Geruch" haben.

+0

+1. Großer Rundown von was zu verwenden und wann. – NotMe

+1

Huh? Warum würde die Methode den Geruch stören? – ladenedge

+5

Code mit virtuellen Methoden ist viel schwieriger in der Zukunft zu ändern, ohne einen impliziten Verhaltenskontrakt zu brechen. In .NET entschieden sie sich daher dafür, dass eine Methode, die überschrieben werden muss, eine bewusste Entscheidung sein muss und sorgfältig abgewogen werden sollte. Jede überschreibbare Methode ohne Anweisungen für den Implementierer ist also ein "Code-Geruch". –

1

Ja, obwohl Sie auch eine Schnittstelle verwenden könnten, um eine Klasse zu zwingen, bestimmte Methoden zu implementieren.

Ein weiterer Grund für die Verwendung einer abstrakten Klasse im Gegensatz zu einer konkreten Klasse ist, dass eine abstrakte Klasse offensichtlich nicht instanziiert werden kann. Manchmal möchten Sie auch nicht, dass dies geschieht, also ist eine abstrakte Klasse der richtige Weg.

5

Programm zur Schnittstelle, nicht zur Implementierung hat wenig mit abstrakten und konkreten Klassen zu tun. Erinnern Sie sich an die template method pattern? Klassen, abstrakt oder konkret, sind die Implementierungsdetails.

Und der Grund für die Verwendung von abstrakten Klassen anstelle von konkreten Klassen ist, dass Sie Methoden aufrufen können, ohne sie zu implementieren, sondern indem Sie sie stattdessen für Unterklassen implementieren lassen.

Programmierung einer Schnittstelle ist eine andere Sache - es ist zu definieren, was Ihre API der Fall ist, nicht wie sie es tut. Und das wird durch Schnittstellen bezeichnet.

Beachten Sie einen Schlüsselunterschied - Sie können protected abstract Methoden haben, was bedeutet, dass dies Implementierungsdetails ist. Alle Schnittstellenmethoden sind jedoch öffentlich - Teil der API.

+0

Um das hinzuzufügen, würde "Programm zu einer Implementierung" Dinge wie die Verwendung von refelection tun, um auf die privaten Felder einer Klasse zuzugreifen. –

1

Zunächst sollte das Strategy Pattern fast nie in modernen C# verwendet werden. Es ist hauptsächlich für Sprachen wie Java, die Funktionszeiger, Delegaten oder erstklassige Funktionen nicht unterstützen. Sie werden es in älteren Versionen von C# in Schnittstellen wie IComparer sehen.

Wie für abstrakte Basisklasse vs. konkrete Klasse lautet die Antwort in Java immer "Was funktioniert in dieser Situation besser?" Wenn Ihre Strategien Code teilen können, dann lassen Sie sie das auf jeden Fall tun.

Entwurfsmuster sind keine Anweisungen, wie etwas zu tun ist. Sie sind Möglichkeiten, Dinge zu kategorisieren, die wir bereits getan haben.

0

Wenn der Client auf "implicated behavior contract [s]" angewiesen ist, wird er gegen eine Implementierung und gegen ungarantiertes Verhalten programmiert. Durch das Überschreiben der Methode während der Ausführung des Vertrags werden nur Fehler im Client angezeigt und nicht verursacht.

OTOH, der Fehler der Annahme von Verträgen, die nicht dort sind, ist weniger wahrscheinlich, um Probleme zu verursachen, wenn die betreffende Methode nicht virtuell ist - d., Overriding kann keine Probleme verursachen, weil es nicht überschrieben werden kann. Nur wenn die Implementierung der ursprünglichen Methode geändert wird (während der Vertrag noch eingehalten wird), kann es den Client beschädigen.

0

Die Frage, ob eine Basisklasse abstrakt oder konkret sein soll, hängt IMHO weitgehend davon ab, ob ein Basisklassenobjekt, das nur Verhalten implementiert, die allen Objekten in der Klasse gemeinsam sind, nützlich wäre. Betrachten Sie ein WaitHandle. Wenn Sie auf "warten" klicken, wird der Code blockiert, bis eine Bedingung erfüllt ist. Es gibt jedoch keine übliche Methode, einem WaitHandle-Objekt mitzuteilen, dass seine Bedingung erfüllt ist. Wenn man ein "WaitHandle" instanziieren könnte, anstatt nur Instanzen von abgeleiteten Typen instanziieren zu können, müsste ein solches Objekt entweder niemals warten oder immer auf ewig warten. Das letztere Verhalten wäre ziemlich nutzlos; Ersteres könnte nützlich gewesen sein, könnte aber fast genauso gut mit einem statisch zugewiesenen ManualResetEvent erreicht werden (ich denke, Letzteres verschwendet ein paar Ressourcen, aber wenn es statisch zugewiesen wird, sollte der gesamte Ressourcenverlust trivial sein).

In vielen Fällen denke ich, dass ich lieber Referenzen zu einer Schnittstelle als zu einer abstrakten Basisklasse verwenden würde, aber mit der Schnittstelle eine Basisklasse bereitstellen würde, die eine "Modellimplementierung" bietet. Also würde jeder Ort, an dem man einen Verweis auf ein MyThing verwenden würde, einen Verweis auf "iMyThing" liefern. Es kann gut sein, dass 99% (oder sogar 100%) von iMyThing-Objekten tatsächlich ein MyThing sind, aber wenn jemand jemals ein iMyThing-Objekt haben muss, das von etwas anderem erbt, könnte man dies tun.

1

Abstrakte Basisklassen werden normalerweise in Szenarien verwendet, in denen der Designer ein Architekturmuster erzwingen möchte, bei dem bestimmte Aufgaben von allen Klassen auf die gleiche Weise ausgeführt werden, während andere Verhaltensweisen von den Unterklassen abhängig sind. Beispiel:

public abstract class Animal{ 

public void digest(){ 

} 

public abstract void sound(){ 

} 
} 

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

stratergy Muster fragt Designer für Fälle Kompositorische Verhalten zu verwenden, an denen Familien von alogirthms für ein Verhalten sind.

0

Bevorzugen abstrakte Basisklassen in folgenden Szenarien:

  1. Eine Basisklasse nicht aus einer Unterklasse => die Basisklasse ist einfach abstrakt existieren kann, und es kann‘t instanziert werden.
  2. Eine Basisklasse kann keine vollständige oder konkrete Implementierung einer Methode haben => Implementierung einer Methode ist Basisklasse ist unvollständig und nur Unterklassen können vollständige Implementierung bieten.
  3. Basisklasse bietet eine Vorlage für die Methode der Umsetzung, aber es hängt immer noch von Beton-Klasse die Methode zur vollständigen Umsetzung - Template_method_pattern

Ein einfaches Beispiel über Punkte

Shape ist abstrakt und es kann nicht illustrieren existieren ohne Betonform wie Rectangle. Das Zeichnen einer Shape kann nicht unter Shape Klasse implementiert werden, da verschiedene Formen unterschiedliche Formeln haben.Die beste Option Szenario zu behandeln: verlassen draw() Umsetzung Subklassen

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(); 
    } 
} 

Ausgang:

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

Above Code umfasst Punkt 1 und Punkt 2. Sie können draw() Methode als Vorlage Methode, wenn Basisklasse ändern hat einige Implementierung und ruft Unterklasse-Methode, um draw() Funktion abzuschließen.

Jetzt gleiche Beispiel mit Template-Methode Muster:

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(); 
    } 
} 

Ausgang:

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 

Sobald Sie sich entschieden haben, dass Sie als Methode zu machen haben abstract, haben Sie zwei Möglichkeiten: Entweder Benutzer interface oder abstract Klasse. Sie können Ihre Methoden in interface deklarieren und die Klasse abstract als Klasse definieren, die interface implementiert.

Verwandte Themen