Frage

Ist es besser, eine Standardimplementierung eines Verfahrens in einem übergeordneten Klasse setzen, und sie überschreibt, wenn Subklassen von diesem abweichen wollen, oder sollten Sie nur die Oberklasse-Methode abstrakt verlassen, und haben die normale Implementierung in vielen Subklassen wiederholt

Zum Beispiel ist ein Projekt, das ich involviert bin in hat eine Klasse, die verwendet wird, um die Bedingungen zu spezifizieren, in der sie stoppen sollte. Die abstrakte Klasse ist wie folgt:

public abstract class HaltingCondition{
    public abstract boolean isFinished(State s);
}

Eine triviale Implementierung könnte sein:

public class AlwaysHaltingCondition extends HaltingCondition{
    public boolean isFinished(State s){
        return true;
    }
}

Der Grund, warum wir diese mit Objekten zu tun ist, dass wir dann willkürlich diese Objekte zusammen komponieren. Zum Beispiel:

public class ConjunctionHaltingCondition extends HaltingCondition{
    private Set<HaltingCondition> conditions;

    public void isFinished(State s){
        boolean finished = true;
        Iterator<HaltingCondition> it = conditions.iterator();
        while(it.hasNext()){
            finished = finished && it.next().isFinished(s);
        }
        return finished;
    }
}

Wir haben jedoch einige Haltebedingungen, die benachrichtigt werden müssen, dass die Ereignisse aufgetreten sind. Zum Beispiel:

public class HaltAfterAnyEventHaltingCondition extends HaltingCondition{
    private boolean eventHasOccurred = false;

    public void eventHasOccurred(Event e){
        eventHasOccurred = true;
    }

    public boolean isFinished(State s){
        return eventHasOccurred;
    }
}

Wie sollten wir am besten eventHasOccurred(Event e) in der abstrakten Oberklasse vertreten? Die meisten Unterklassen können eine haben no-op-Implementierung dieser Methode (zB AlwaysHaltingCondition), während einige eine bedeutende Implementierung erfordern korrekt zu arbeiten (zB HaltAfterAnyEventHaltingCondition) und andere nicht, etwas zu tun mit der Nachricht benötigen, aber es an ihre Untergebenen passieren muss so dass sie korrekt funktionieren (zB ConjunctionHaltingCondition).

Wir könnten eine Default-Implementierung haben, die Code-Duplizierung reduzieren würde, würde aber einige Unterklassen führen noch nicht kompilieren korrekt arbeiten, wenn sie nicht außer Kraft gesetzt wurde, oder wir könnten haben erklärt die Methode als abstrakt, die der Autor erfordern würde jede Unterklasse zu denken, über die Umsetzung sie vorsähen, obwohl neun von zehn wäre es eine no-op Implementierung sein. Was sind die anderen Vor-und Nachteile dieser Strategien? Ist man viel besser als die andere?

War es hilfreich?

Lösung

Eine Option ist eine andere abstrakte Unterklasse haben, als die übergeordnete Klasse für alle Implementierungen verwendet werden, die tun wollen die Standardimplementierung verwenden.

Ich persönlich in der Regel verlassen nicht-final-Methoden abstrakt in einer abstrakten Klasse (oder auch nur den Einsatz Schnittstellen statt), aber es hängt definitiv von der Situation. Wenn Sie eine Schnittstelle mit vielen Methoden, und Sie wollen in der Lage sein, nur Opt-in zu einig von ihnen, zum Beispiel dann eine abstrakte Klasse, die implementiert der Schnittstelle in einer No-Op Art und Weise für jeden Methode ist in Ordnung.

Sie müssen jeden Fall gesondert geprüft zu bewerten, im Grunde.

Andere Tipps

Wenn Sie eine Implementierung in der abstrakten Basisklasse setzen möchten, sollten sie den Code für die Unterklassen sein, die die no-op-Implementierung verwenden, da dies eine Implementierung ist, die auch Sinn für die Basisklasse macht. Wenn es keine vernünftige Implementierung für die Basisklasse war (zum Beispiel, wenn es kein vernünftiges no-op für das Verfahren ist Sie diskutiert hier), dann würde ich vorschlagen, es abstrakt zu verlassen.

Im Hinblick auf duplizierten Code, wenn es „Familien“ von Klassen sind, dass alle nutzen die gleiche Implementierung des Verfahrens und Sie wollen nicht den Code in allen Klassen in der Familie duplizieren, könnten Sie einfach Hilfsklassen verwenden pro Familie, die die Implementierungen liefern. In Ihrem Beispiel ein Helfer für Klassen, die nach unten die Ereignisse passieren, ein Helfer für die Klassen übernehmen und das Ereignis aufzuzeichnen, etc.

Es klingt wie Sie besorgt sind über die boolesche Variable eingestellt wird, wenn das Ereignis eintritt. Wenn der Benutzer überschreibt eventHasOccurred(), dann ist die Boolesche Variable wird nicht gesetzt und isFinished() nicht den richtigen Wert zurück. Um dies zu tun, können Sie eine abstrakte Methode haben, die der Benutzer überschreibt das Ereignis und eine andere Methode zu behandeln, das die abstrakte Methode aufruft und setzt den Booleschen Wert (siehe Codebeispiel unten).

Auch anstelle die eventHasOccurred() Methode in der HaltingCondition Klasse setzen, können Sie nur die Klassen haben, dass Bedarf an Griff Ereignisse eine Klasse erweitern, die dieses Verfahren (wie die Klasse weiter unten) definiert. Jede Klasse, die muss nicht Handle Ereignisse können nur HaltingCondition erweitern:

public abstract class EventHaltingCondition extends HaltingCondition{
  private boolean eventHasOccurred = false;

  //child class implements this
  //notice how it has protected access to ensure that the public eventHasOccurred() method is called
  protected abstract void handleEvent(Event e);

  //program calls this when the event happens
  public final void eventHasOccurred(Event e){
    eventHasOccurred = true; //boolean is set so that isFinished() returns the proper value
    handleEvent(e); //child class' custom code is executed
  }

  @Override
  public boolean isFinished(){
    return eventHasOcccurred;
  }
}

EDIT (siehe Kommentare):

final EventHaltingCondition condition = new EventHaltingCondition(){
  @Override
  protected void handleEvent(Event e){
    //...
  }
};
JButton button = new JButton("click me");
button.addActionListener(new ActionListener(){
  public void actionPerformed(ActionEvent actionEvent){
    //runs when the button is clicked

    Event event = //...
    condition.eventHasOccurred(event);
  }
});

stieß ich auf ein ähnliches Szenario, wenn ich die Grundzüge (Klassenhierarchie) erstellt eine Anwendung, die ich zusammen bei der Arbeit mit anderen entwickle. Meine Wahl für die Platzierung eines Verfahrens abstrakt (und damit ihre Umsetzung zu zwingen) war für Kommunikation Zwecke.

Im Grunde der anderen Teamkollegen hatten irgendwie explizit die Methode zu implementieren und daher in erster Linie Mitteilung seiner Präsenz und zweiten zustimmen auf, was sie dorthin zurückkehren, auch wenn es nur die Standardimplementierung ist.

Standardimplementierungen in Basisklassen oft übersehen werden.

Wenn Sie die Super-Klasse-Methode verlassen abstrakt Sie eine Schnittstelle suchen mögen in Verwendung (nicht mit einer Schnittstelle zu verwechseln). Da die Schnittstelle ist eine, die keine konkrete Implementierung bietet.

Scott

zu erweitern, wenn wir als Programmierer Code an eine Schnittstelle angewiesen werden oft neue und manchmal erlebt, Entwickler falsch davon ausgehen, dass es in Bezug auf das Schlüsselwort Interface ist bei der keine Implementierungsdetails gefunden werden kann. Allerdings ist die freiere Art zu sagen, dass jedes Top-Level-Objekt kann als Schnittstelle behandelt werden, das mit seinem interagierten kann. Zum Beispiel eine abstrakte Klasse namens Tier wäre eine Schnittstelle eine Klasse namens würde Katze, die von Animal erben würde.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top