OK .. nach all den Diskussionen ändere ich meine Frage ein wenig, um ein konkretes Beispiel besser wiederzugeben, mit dem ich mich befasse.
Ich habe zwei Klassen ModelOne
und ModelTwo
, Diese Klassen führen ähnliche Arten von Funktionen aus, sind jedoch nicht miteinander verbunden. Ich habe jedoch eine dritte Klasse CommonFunc
, die einige öffentliche Funktionen enthält, die sowohl in ModelOne
als auch implementiert sind und ModelTwo
gemäß den Vorgaben ausgeklammert wurden DRY
. Die beiden Modelle werden innerhalb der ModelMain
Klasse instanziiert (die selbst auf einer höheren Ebene instanziiert wird usw. - aber ich höre auf dieser Ebene auf).
Der von mir verwendete IoC-Container ist Microsoft Unity . Ich gebe nicht vor, ein Experte zu sein, aber ich verstehe es so, dass Sie ein Tupel von Interface und Klasse für den Container registrieren. Wenn Sie eine konkrete Klasse wünschen, fragen Sie den IoC-Container nach dem Objekt, das mit einem bestimmten Interface übereinstimmt. Dies bedeutet, dass für jedes Objekt, das ich aus Unity instanziieren möchte, eine passende Schnittstelle vorhanden sein muss. Da jede meiner Klassen unterschiedliche (und nicht überlappende) Funktionen ausführt, gibt es ein 1: 1-Verhältnis zwischen Schnittstelle und Klasse 1 . Es bedeutet jedoch nicht, dass ich eine Schnittstelle für jede Klasse, die ich schreibe, sklavisch schreibe.
Also, was den Code betrifft, erhalte ich 2 :
public interface ICommonFunc
{
}
public interface IModelOne
{
ICommonFunc Common { get; }
..
}
public interface IModelTwo
{
ICommonFunc Common { get; }
..
}
public interface IModelMain
{
IModelOne One { get; }
IModelTwo Two { get; }
..
}
public class CommonFunc : ICommonFunc { .. }
public class ModelOne : IModelOne { .. }
public class ModelTwo : IModelTwo { .. }
public class ModelMain : IModelMain { .. }
Die Frage ist, wie meine Lösung zu organisieren ist. Soll ich die Klasse und die Schnittstelle zusammenhalten? Oder sollte ich Klassen und Interfaces zusammenhalten? Z.B:
Option 1 - Nach Klassennamen organisiert
MySolution
|
|-MyProject
| |
|-Models
| |
|-Common
| |
| |-CommonFunc.cs
| |-ICommonFunc.cs
|
|-Main
| |
| |-IModelMain.cs
| |-ModelMain.cs
|
|-One
| |
| |-IModelOne.cs
| |-ModelOne.cs
|
|-Two
|
|-IModelTwo.cs
|-ModelTwo.cs
|
Option 2 - Organisiert nach Funktionen (meistens)
MySolution
|
|-MyProject
| |
|-Models
| |
|-Common
| |
| |-CommonFunc.cs
| |-ICommonFunc.cs
|
|-IModelMain.cs
|-IModelOne.cs
|-IModelTwo.cs
|-ModelMain.cs
|-ModelOne.cs
|-ModelTwo.cs
|
Option 3 - Schnittstelle und Implementierung trennen
MySolution
|
|-MyProject
|
|-Interfaces
| |
| |-Models
| | |
| |-Common
| | |-ICommonFunc.cs
| |
| |-IModelMain.cs
| |-IModelOne.cs
| |-IModelTwo.cs
|
|-Classes
|
|-Models
| |
|-Common
| |-CommonFunc.cs
|
|-ModelMain.cs
|-ModelOne.cs
|-ModelTwo.cs
|
Option 4 - Weiteres Funktionsbeispiel
MySolution
|
|-MyProject
| |
|-Models
| |
|-Components
| |
| |-Common
| | |
| | |-CommonFunc.cs
| | |-ICommonFunc.cs
| |
| |-IModelOne.cs
| |-IModelTwo.cs
| |-ModelOne.cs
| |-ModelTwo.cs
|
|-IModelMain.cs
|-ModelMain.cs
|
Ich mag Option 1 wegen des Klassennamens im Pfad nicht. Da ich jedoch aufgrund meiner IoC-Auswahl / -Verwendung (und das ist möglicherweise umstritten) zum Verhältnis 1: 1 tendiere, hat dies Vorteile darin, die Beziehung zwischen den Dateien zu sehen.
Option 2 gefällt mir, aber jetzt habe ich das Wasser zwischen dem ModelMain
und den Untermodellen getrübt .
Option 3 trennt die Schnittstellendefinition von der Implementierung, aber jetzt habe ich diese künstlichen Unterbrechungen in den Pfadnamen.
Option 4. Ich habe Option 2 gewählt und optimiert, um die Komponenten vom übergeordneten Modell zu trennen.
Gibt es einen guten Grund, eins dem anderen vorzuziehen? Oder andere mögliche Layouts, die ich verpasst habe?
1. Frank machte einen Kommentar, dass das Verhältnis von 1: 1 auf C ++ - Tage mit .h- und .cpp-Dateien zurückgeht. Ich weiß, woher er kommt. Mein Verständnis von Einigkeit scheint mich in diese Ecke zu führen, aber ich bin mir auch nicht sicher, wie ich aus ihr herauskommen soll, wenn Sie auch dem Sprichwort von Program to an interface
folgen. Aber das ist eine Diskussion für einen weiteren Tag.
2. Ich habe die Details jedes Objektkonstruktors ausgelassen. Hier injiziert der IoC-Container Objekte nach Bedarf.
quelle
Client1
BedarfIBase
eineDerived1
. BeiClient2
BedarfIBase
bietet die IoC eineDerived2
.interface
. Eineinterface
ist wirklich nur eine abstrakte Klasse mit allen virtuellen Mitgliedern.Antworten:
Da eine Schnittstelle einer Basisklasse abstrakt ähnlich ist, verwenden Sie dieselbe Logik, die Sie für eine Basisklasse verwenden würden. Klassen, die eine Schnittstelle implementieren, sind eng mit der Schnittstelle verbunden.
Ich bezweifle, dass Sie ein Verzeichnis mit dem Namen "Base Classes" bevorzugen würden. die meisten Entwickler würden das nicht wollen, noch ein Verzeichnis namens "Interfaces". In c # sind Verzeichnisse standardmäßig auch Namespaces, was die Sache doppelt verwirrt.
Am besten überlegen Sie sich, wie Sie die Klassen / Interfaces aufteilen würden, wenn Sie einige in eine separate Bibliothek stellen müssten, und organisieren Sie die Namespaces / Verzeichnisse auf ähnliche Weise. Die Entwurfsrichtlinien für das .net-Framework enthalten Vorschläge für Namespaces , die hilfreich sein können.
quelle
Ich gehe den zweiten Weg, natürlich mit einigen weiteren Ordnern / Namespaces in komplexen Projekten. Das bedeutet, dass ich die Schnittstellen von den konkreten Implementierungen entkopple.
In einem anderen Projekt müssen Sie möglicherweise die Schnittstellendefinition kennen, aber es ist überhaupt nicht erforderlich, eine konkrete Klasse zu kennen, die sie implementiert - insbesondere, wenn Sie einen IoC-Container verwenden. Diese anderen Projekte müssen also nur auf die Schnittstellenprojekte verweisen, nicht auf die Implementierungsprojekte. Dies kann die Anzahl der Verweise niedrig halten und Probleme mit Zirkelverweisen vermeiden.
quelle
Wie immer bei jeder Designfrage müssen Sie als Erstes feststellen, wer Ihre Benutzer sind. Wie werden sie Ihre Organisation nutzen?
Sind es Programmierer, die Ihre Klassen verwenden? Dann ist es am besten, die Schnittstellen vom Code zu trennen.
Sind sie Betreuer Ihres Codes? Dann halten Sie die Schnittstellen und Klassen am besten zusammen.
Setzen Sie sich und erstellen Sie einige Use-Cases. Dann erscheint vielleicht die beste Organisation vor Ihnen.
quelle
Ich denke, es ist besser, Schnittstellen in der separaten Bibliothek zu speichern. Erstens erhöht diese Art der Gestaltung die Abhängigkeit. Deshalb kann die Implementierung dieser Schnittstellen durch eine andere Programmiersprache erfolgen.
quelle