Interface Insanity

8

Ich trinke die coolade und liebe es - Schnittstellen, IoC, DI, TDD, etc. etc. Sehr gut ausarbeiten. Aber ich stelle fest, dass ich gegen eine Tendenz ankämpfen muss, alles zu einer Schnittstelle zu machen! Ich habe eine Fabrik, die eine Schnittstelle ist. Ihre Methoden geben Objekte zurück, bei denen es sich um Schnittstellen handeln könnte (was das Testen erleichtern könnte). Diese Objekte sind DI-Schnittstellen zu den Diensten, die sie benötigen. Was ich finde, ist, dass die Interfaces mit den Implementierungen synchron zu der Arbeit sind - das Hinzufügen einer Methode zu einer Klasse bedeutet das Hinzufügen zu einer Klasse + die Schnittstelle, Mocks, etc.

Rechne ich die Schnittstellen zu früh aus? Gibt es Best Practices, um zu wissen, wann etwas eine Schnittstelle oder ein Objekt zurückgeben soll?

    
n8wrl 31.03.2009, 12:50
quelle

8 Antworten

7

Interfaces sind nützlich, wenn Sie eine Interaktion zwischen einem Objekt und einem seiner Mitbearbeiter vortäuschen möchten. Allerdings gibt es in einer Schnittstelle weniger Wert für ein Objekt mit internem Status.

Nehmen wir zum Beispiel an, ich hätte einen Dienst, der mit einem Repository redet, um ein Domain-Objekt zu extrahieren, um es irgendwie zu manipulieren.

Beim Entpacken einer Schnittstelle aus dem Repository gibt es einen eindeutigen Design-Wert. Meine konkrete Implementierung des Repositorys ist möglicherweise stark mit NHibernate oder ActiveRecord verknüpft. Durch die Verknüpfung meines Dienstes mit der Schnittstelle erhalte ich eine saubere Trennung von diesem Implementierungsdetail. Es ist einfach so, dass ich jetzt auch superschnelle Standalone-Unit-Tests für meinen Service schreiben kann, die ich dann einem Mock-IRepository übergeben kann.

In Anbetracht des Domänenobjekts, das aus dem Repository zurückkam und auf das mein Service reagiert, ist der Wert geringer. Wenn ich einen Test für meinen Dienst schreibe, möchte ich ein echtes Domänenobjekt verwenden und dessen Status überprüfen. Z.B. Nach dem Aufruf von service.AddSomething () möchte ich überprüfen, ob etwas zum Domänenobjekt hinzugefügt wurde. Ich kann dies durch einfache Überprüfung des Zustands des Domänenobjekts testen. Wenn ich mein Domänenobjekt isoliert teste, brauche ich keine Schnittstellen, da ich nur Operationen für das Objekt ausführen und es auf seinen internen Zustand überprüfen werde. z.B. Ist es für meine Schafe gültig, Gras zu essen, wenn es schläft?

Im ersten Fall sind wir an Interaktion -basierten Tests interessiert. Schnittstellen helfen, weil wir die Aufrufe zwischen dem zu testenden Objekt und seinen Mitbearbeitern mit Mocks abfangen wollen. Im zweiten Fall interessieren wir uns für state Tests. Schnittstellen helfen hier nicht. Versuchen Sie, sich bewusst zu sein, ob Sie den Zustand oder die Interaktionen testen, und lassen Sie dies Ihre Entscheidung für die Schnittstelle oder keine Schnittstelle beeinflussen.

Denken Sie daran, dass es (wenn Sie eine Kopie von Resharper installiert haben) extrem billig ist, eine Schnittstelle später zu extrahieren. Es ist auch billig, die Schnittstelle zu löschen und zu einer einfacheren Klassenhierarchie zurückzukehren, wenn Sie sich entschließen, diese Schnittstelle nicht zu benötigen. Mein Rat wäre, ohne Schnittstellen zu beginnen und sie auf Nachfrage zu extrahieren, wenn Sie finden, dass Sie die Interaktion verspotten möchten.

Wenn du IoC ins Bild bringst, würde ich dazu neigen, mehr Interfaces zu extrahieren - aber versuche, die Anzahl der Klassen, die du in deinen IoC-Container schubst, zu verbergen. In der Regel möchten Sie diese auf weitgehend zustandslose Serviceobjekte beschränken.

    
Stuart Caborn 09.04.2009, 20:03
quelle
6

Klingt so, als ob Sie ein bisschen von BDUF leiden.

Nimm es einfach mit der Coolade und lass es natürlich fließen.

    
Ed Guiness 31.03.2009 12:52
quelle
6

Denken Sie daran, dass, während Flexibilität ein erstrebenswertes Ziel ist, die zusätzliche Flexibilität mit IoC und DI (die zu einem gewissen Grad Anforderungen für TDD sind) auch die Komplexität erhöht. Der einzige Punkt der Flexibilität besteht darin, Änderungen schneller, billiger oder besser nachgeschalten zu machen. Jeder IoC / DI-Punkt erhöht die Komplexität und trägt somit dazu bei, Änderungen anderswo schwieriger zu machen.

Hier benötigen Sie eigentlich einen Big Design Up Front bis zu etwas : Identifizieren Sie, welche Bereiche am ehesten geändert werden (und / oder umfangreiche Komponententests benötigen), und planen Sie dort Flexibilität. Refactor um Flexibilität zu vermeiden, wo Änderungen unwahrscheinlich sind.

Nun, ich sage nicht, dass Sie erraten können, wo Flexibilität mit jeder Art von Genauigkeit benötigt wird. Du liegst falsch. Aber es ist wahrscheinlich, dass du etwas richtig findest. Wenn Sie später feststellen, dass Sie keine Flexibilität benötigen, kann dies in der Wartung berücksichtigt werden. Wo Sie es brauchen, kann es beim Hinzufügen von Features berücksichtigt werden.

Nun hängen Bereiche, die sich ändern können oder nicht, von Ihrem Geschäftsproblem und Ihrer IT-Umgebung ab. Hier sind einige wiederkehrende Bereiche.

  1. Ich würde immer extern denken Schnittstellen, in die Sie integrieren andere Systeme stark veränderbar sein.
  2. Welcher Code auch immer ein Back-End bietet Die Benutzeroberfläche muss Änderungen in der Benutzeroberfläche unterstützen. Planen Sie jedoch in erster Linie Änderungen der Funktionalität: Gehen Sie nicht über Bord und planen Sie keine anderen UI-Technologien (z. B. die Unterstützung eines Smartclients und einer Webanwendung - die Nutzungsmuster unterscheiden sich zu sehr).
  3. Auf der anderen Seite, Codierung für Portabilität zu verschiedenen Datenbanken und Plattformen ist normalerweise eine Verschwendung zumindest in Unternehmen Umgebungen. Fragen Sie nach und überprüfen Sie Welche Pläne existieren, um zu ersetzen oder aktualisieren Sie Technologien innerhalb der voraussichtliche Lebensdauer Ihrer Software.
  4. Änderungen an Dateninhalt und -formaten sind schwierig Geschäft: während Daten werden gelegentlich ändern sich die meisten Designs Ich habe gesehen, wie solche Änderungen gehandhabt werden schlecht, und so wirst du konkret Entitätsklassen direkt verwendet.

Aber nur Sie können beurteilen, was sich ändern könnte oder nicht.

    
Pontus Gagge 31.03.2009 13:11
quelle
5

Ich finde normalerweise, dass ich Schnittstellen für "Dienste" möchte - während Typen, die hauptsächlich "Daten" betreffen, konkrete Klassen sein können. Zum Beispiel hätte ich eine Authenticator -Schnittstelle, aber eine Contact -Klasse. Natürlich ist das nicht immer klar, aber es ist eine erste Faustregel.

Ich fühle jedoch deinen Schmerz - es ist ein bisschen wie in die dunklen Zeiten von .h und .c Dateien zurück zu gehen ...

    
Jon Skeet 31.03.2009 13:03
quelle
2

Ich denke, das wichtigste "agile" Prinzip ist YAGNI ("Du wirst es nicht brauchen"). Mit anderen Worten, schreibe keinen zusätzlichen Code, bis er tatsächlich benötigt wird, denn wenn du es im Voraus schreibst, könnten sich die Anforderungen und Einschränkungen sehr wohl geändert haben, wenn (wenn!) Du es endlich brauchst.

Schnittstellen, Dependency-Injections usw. - all das macht Ihren Code komplexer und erschwert das Verständnis und die Änderung. Meine Faustregel ist, die Dinge so einfach wie möglich zu halten (aber nicht einfacher) und keine Komplexität hinzuzufügen, es sei denn, sie bringt mir mehr als genug, um die Belastung, die sie verursacht, auszugleichen.

Wenn Sie also tatsächlich ein Mock-Objekt testen und es sehr nützlich wäre, dann definieren Sie auf jeden Fall eine Schnittstelle, die sowohl Ihre Mock- als auch Ihre Real-Klasse implementieren. Aber erstellen Sie nicht eine Reihe von Schnittstellen auf rein hypothetischen Gründen, dass es an einem bestimmten Punkt nützlich sein könnte, oder dass es "korrekte" OO-Design ist.

    
Jesse Smith 31.03.2009 13:11
quelle
0

Schnittstellen haben den Zweck, einen Vertrag zu erstellen und sind besonders nützlich, wenn Sie die Klasse ändern möchten, die eine Aufgabe im laufenden Betrieb ausführt. Wenn es nicht notwendig ist, die Klassen zu ändern, könnte eine Schnittstelle nur in die Quere kommen.

Es gibt automatische Werkzeuge, um die Schnittstelle zu extrahieren, also sollten Sie die Schnittstellenextraktion vielleicht später im Prozess verzögern.

    
Manrico Corazzi 31.03.2009 13:03
quelle
0

Es kommt sehr darauf an, was Sie anbieten ... wenn Sie an internen Dingen arbeiten, dann ist der Ratschlag "mach sie nicht, bis sie gebraucht werden" vernünftig. Wenn Sie jedoch eine API erstellen, die von anderen Entwicklern verwendet werden soll, kann das lästig sein, Dinge zu einem späteren Zeitpunkt auf Interfaces umzustellen.

Eine gute Faustregel besteht darin, aus allem, was Subklassen sein müssen, Schnittstellen zu machen. Dies ist kein "immer eine Schnittstelle in diesem Fall" Art der Sache, Sie müssen immer noch darüber nachdenken.

Also ist die kurze Antwort (und das funktioniert sowohl mit internen Dingen als auch mit einer API), dass, wenn Sie erwarten, dass mehr als eine Implementierung benötigt wird, dann machen Sie es zu einer Schnittstelle.

Etwas, das normalerweise keine Interfaces wäre, wären Klassen, die nur Daten enthalten, wie eine Location-Klasse, die sich mit x und y beschäftigt. Die Wahrscheinlichkeit, dass es eine andere Implementierung davon gibt, ist gering.

    
TofuBeer 31.03.2009 14:37
quelle
-1

Erstellen Sie nicht zuerst die Schnittstellen - Sie werden sie nicht brauchen. Sie können nicht erraten, für welche Klassen Sie eine Schnittstelle für welche Klassen nicht benötigen. Deshalb verbringe jetzt keine Zeit, um den Code mit nutzloser Schnittstelle zu belasten.

Aber extrahieren Sie sie, wenn Sie das Bedürfnis verspüren - wenn Sie die Notwendigkeit einer Schnittstelle sehen - beim Refactoring-Schritt.

Diese Antworten können ebenfalls helfen.

    
philant 31.03.2009 13:05
quelle