Stimmt es, dass das Überschreiben konkreter Methoden ein Codegeruch ist? Weil ich denke, wenn Sie konkrete Methoden überschreiben müssen:
public class A{
public void a(){
}
}
public class B extends A{
@Override
public void a(){
}
}
es kann umgeschrieben werden als
public interface A{
public void a();
}
public class ConcreteA implements A{
public void a();
}
public class B implements A{
public void a(){
}
}
und wenn B ein () in A wiederverwenden möchte, kann es wie folgt umgeschrieben werden:
public class B implements A{
public ConcreteA concreteA;
public void a(){
concreteA.a();
}
}
Wofür ist keine Vererbung erforderlich, um die Methode zu überschreiben?
virtual
Schlüsselwort verwenden, um Überschreibungen zu unterstützen. Das Problem wird also durch die Einzelheiten der Sprache mehr (oder weniger) mehrdeutig.final
Modifikator genau für solche Situationen existiert. Wenn das Verhalten der Methode so ist, dass sie nicht überschrieben werden soll, markieren Sie sie alsfinal
. Erwarten Sie andernfalls, dass Entwickler es möglicherweise außer Kraft setzen.Antworten:
Nein, es ist kein Codegeruch.
Es liegt in der Verantwortung jeder Klasse, sorgfältig zu prüfen, ob eine Unterklasse angemessen ist und welche Methoden möglicherweise überschrieben werden .
Die Klasse kann sich selbst oder eine Methode als endgültig definieren oder Einschränkungen (Sichtbarkeitsmodifikatoren, verfügbare Konstruktoren) dahingehend festlegen, wie und wo sie Unterklassen aufweist.
Der übliche Fall für das Überschreiben von Methoden ist eine Standardimplementierung in der Basisklasse, die in der Unterklasse angepasst oder optimiert werden kann (insbesondere in Java 8 mit dem Aufkommen von Standardmethoden in Schnittstellen).
Eine Alternative zum Überschreiben von Verhalten ist das Strategy Pattern , bei dem das Verhalten als Schnittstelle abstrahiert wird und die Implementierung in der Klasse festgelegt werden kann.
quelle
A
implementiert werdenDescriptionProvider
?A
könnte implementierenDescriptionProvider
und somit der Standardbeschreibungsanbieter sein. Die Idee hier ist jedoch die Trennung von Bedenken:A
Benötigt die Beschreibung (einige andere Klassen könnten dies auch tun), während andere Implementierungen sie bereitstellen.Ja, im Allgemeinen ist das Überschreiben konkreter Methoden ein Codegeruch.
Da mit der Basismethode ein Verhalten verbunden ist, das Entwickler normalerweise respektieren, führt eine Änderung zu Fehlern, wenn Ihre Implementierung etwas anderes ausführt. Schlimmer noch, wenn sie das Verhalten ändern, kann Ihre zuvor korrekte Implementierung das Problem verschlimmern. Und im Allgemeinen ist es ziemlich schwierig, das Liskov-Substitutionsprinzip für nicht-triviale konkrete Methoden zu respektieren .
Jetzt gibt es Fälle, in denen dies möglich und sinnvoll ist. Semi-triviale Methoden können einigermaßen sicher übersteuert werden. Basismethoden , die nur als „gesund default“ Art von Verhalten gibt , die ist gemeint sein übergangen haben einige gute Anwendungen.
Daher scheint mir das ein Geruch zu sein - manchmal gut, normalerweise schlecht, schauen Sie, um sicherzugehen.
quelle
Number
Klasse mit einerincrement()
Methode, die den Wert auf den nächsten gültigen Wert setzt. Wenn Sie es in Unterklasse ,EvenNumber
woincrement()
zwei fügt anstelle eines (und die Setter erzwingt Gleichmäßigkeit), der erste Vorschlag der OP erfordert doppelte Implementierungen von jeder Methode in der Klasse und seine zweite erfordert das Schreiben Wrapper - Methoden , die Methoden in dem Element zu nennen. Ein Teil des Vererbungszwecks besteht darin, dass untergeordnete Klassen deutlich machen, wie sie sich von den Eltern unterscheiden, indem sie nur die Methoden bereitstellen, die unterschiedlich sein müssen.Wenn es auf diese Weise umgeschrieben werden kann, haben Sie die Kontrolle über beide Klassen. Aber dann wissen Sie, ob Sie Klasse A so entworfen haben und wenn ja, ist es kein Codegeruch.
Wenn Sie andererseits keine Kontrolle über die Basisklasse haben, können Sie nicht auf diese Weise neu schreiben. Entweder wurde die Klasse so entworfen, dass sie auf diese Weise abgeleitet wird. In diesem Fall handelt es sich nicht um einen Codegeruch. In diesem Fall haben Sie zwei Möglichkeiten: Suchen Sie nach einer anderen Lösung oder gehen Sie trotzdem vor , weil das Arbeiten die Reinheit des Designs übertrumpft.
quelle
abstract
; aber es gibt viele Fälle, in denen es nicht sein muss. 2) Wenn es nicht überschrieben werden darf, sollte esfinal
(nicht virtuell) sein. Aber es gibt immer noch viele Fälle , in denen Methoden können außer Kraft gesetzt werden. 3) Wenn der nachgeschaltete Entwickler die Dokumente nicht liest, schießt er sich in den Fuß, tut dies jedoch unabhängig von den von Ihnen ergriffenen Maßnahmen.Lassen Sie mich zunächst darauf hinweisen, dass diese Frage nur in bestimmten Schreibsystemen anwendbar ist. Zum Beispiel in Structural Typing- und Duck-Typing- Systemen - eine Klasse mit einer Methode mit demselben Namen und derselben Signatur würde bedeuten, dass die Klasse typkompatibel ist (zumindest für diese bestimmte Methode im Fall von Duck-Typing).
Dies unterscheidet sich (offensichtlich) stark vom Bereich statisch typisierter Sprachen wie Java, C ++ usw. sowie von der Art der starken Typisierung , wie sie sowohl in Java und C ++ als auch in C # und anderen verwendet wird.
Ich vermute, Sie kommen aus einem Java-Hintergrund, in dem Methoden explizit als endgültig markiert werden müssen, wenn der Vertragsgestalter beabsichtigt, dass sie nicht überschrieben werden. In Sprachen wie C # ist jedoch das Gegenteil der Standard. Methoden werden (ohne Dekoration, spezielle Schlüsselwörter) " versiegelt " (C # -Version von
final
) und müssen explizit deklariert werden, alsvirtual
ob sie überschrieben werden dürfen .Wenn eine API oder Bibliothek in diesen Sprachen mit einer konkreten Methode entworfen wurde, die überschrieben werden kann, muss dies (zumindest in einigen Fällen) sinnvoll sein, damit sie überschrieben wird. Daher ist es kein Codegeruch, eine nicht versiegelte / virtuelle / nicht
final
konkrete Methode zu überschreiben . (Dies setzt natürlich voraus, dass die Designer der API Konventionen befolgen und entweder alsvirtual
(C #) oder alsfinal
(Java) die geeigneten Methoden für das, was sie entwerfen , markieren .)Siehe auch
quelle
Ja, das liegt daran, dass es zu einem Super- Anti-Pattern kommen kann. Es kann auch den ursprünglichen Zweck der Methode denaturieren, Nebenwirkungen hervorrufen (=> Bugs) und Tests zum Scheitern bringen. Würden Sie eine Klasse verwenden, deren Unit-Tests rot (failling) sind? Werde ich nicht.
Ich gehe noch weiter und sage, der anfängliche Codegeruch ist, wenn eine Methode weder
abstract
noch istfinal
. Weil es erlaubt, das Verhalten einer konstruierten Entität (durch Überschreiben) zu ändern (dieses Verhalten kann verloren gehen oder geändert werden). Mitabstract
undfinal
die Absicht ist eindeutig:Der sicherste Weg, einer vorhandenen Klasse Verhalten hinzuzufügen, ist das, was Ihr letztes Beispiel zeigt: das Dekorationsmuster verwenden.
quelle
Object.toString()
an Java ... Wenn dies derabstract
Fall wäre, würden viele Standardfunktionen nicht funktionieren und der Code würde nicht einmal kompiliert, wenn jemand dietoString()
Methode nicht überschrieben hätte ! Wennfinal
dies der Fall wäre, wären die Dinge noch schlimmer - es gibt keine Möglichkeit, Objekte in Zeichenfolgen umzuwandeln, mit Ausnahme der Java-Methode. Als @ Peter Walser ‚s Antwort spricht, Standardimplementierungen (wieObject.toString()
) sind wichtig. Besonders in Java 8 Standardmethoden.toString()
entwederabstract
oderfinal
es wäre ein Schmerz. Meine persönliche Meinung ist, dass diese Methode kaputt ist und es besser gewesen wäre, sie überhaupt nicht zu haben (wie Equals und HashCode ). Der ursprüngliche Zweck von Java-Standardeinstellungen besteht darin, API-Evolution mit Retrokompatibilität zu ermöglichen.