Wie lassen sich Klassen- und Schnittstellendateien am besten organisieren?

17

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 ModelOneund 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 ModelOneals auch implementiert sind und ModelTwogemäß den Vorgaben ausgeklammert wurden DRY. Die beiden Modelle werden innerhalb der ModelMainKlasse 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 ModelMainund 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.

Peter M
quelle
1
Pfui. Es riecht nach einem 1: 1-Verhältnis von Schnittstelle zu Klasse. Welchen IoC-Container verwenden Sie? Ninject bietet Mechanismen, die kein 1: 1-Verhältnis erfordern.
RubberDuck
1
@RubberDuck FWIW Ich benutze Unity. Ich behaupte nicht, ein Experte darin zu sein, aber wenn meine Klassen gut mit einzelnen Verantwortlichkeiten entworfen sind, wie komme ich dann nicht zu einem Verhältnis von fast 1: 1?
Peter M
Benötigen Sie IBase oder könnte base abstrakt sein? Warum IDerivedOne, wenn es bereits IBase implementiert? Sie sollten abhängig von IBase nicht abgeleitet sein. Ich kenne Unity nicht, aber andere IoC-Container ermöglichen Ihnen die "kontextsensitive" Injektion. Grundsätzlich bietet es bei Client1Bedarf IBaseeine Derived1. Bei Client2Bedarf IBasebietet die IoC eine Derived2.
RubberDuck
1
Nun, wenn die Basis abstrakt ist, gibt es keinen Grund, einen Grund dafür zu haben interface. Eine interfaceist wirklich nur eine abstrakte Klasse mit allen virtuellen Mitgliedern.
RubberDuck
1
RubberDuck ist richtig. Überflüssige Schnittstellen nerven einfach.
Frank Hileman

Antworten:

4

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.

Frank Hileman
quelle
Ich kenne den von Ihnen angegebenen Link irgendwie, bin mir aber nicht sicher, wie er sich auf die Dateiorganisation bezieht. Während VS standardmäßig Pfadnamen für den anfänglichen Namespace verwendet, weiß ich, dass dies eine willkürliche Wahl ist. Außerdem habe ich mein Code-Beispiel und meine Layoutmöglichkeiten aktualisiert.
Peter M
@PeterM Ich bin nicht sicher, welche IDE Sie verwenden, aber Visual Studio verwendet die Verzeichnisnamen, um die Namespace-Deklarationen beispielsweise beim Hinzufügen einer Klasse automatisch zu generieren. Dies bedeutet, dass Sie zwar Unterschiede zwischen Verzeichnisnamen und Namespace-Namen haben können, es jedoch schmerzhafter ist, auf diese Weise zu arbeiten. Die meisten Leute machen das also nicht.
Frank Hileman
Ich benutze VS. Und ja, ich kenne Schmerzen. Es weckt Erinnerungen an das Visual Source Safe-Dateilayout.
Peter M
1
@PeterM In Bezug auf das Verhältnis von Schnittstelle zu Klasse 1: 1. Einige Tools erfordern dies. Ich betrachte dies als einen Defekt des Tools - .net verfügt über genügend Reflektionsfähigkeiten, um solche Einschränkungen in keinem solchen Tool zu erfordern.
Frank Hileman
1

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.

Bernhard Hiller
quelle
Ich habe mein Codebeispiel überarbeitet und einige weitere Optionen hinzugefügt
Peter M
1

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.

shawnhcorey
quelle
-2

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.

Larissa Savchekoo
quelle