Jak najlepiej uporządkować pliki klas i interfejsów?

17

OK .. po całej dyskusji zmieniam nieco moje pytanie, aby lepiej odzwierciedlić konkretny przykład, z którym mam do czynienia.


Mam dwie klasy ModelOnei klasy ModelTwote wykonują podobny typ funkcjonalności, ale nie są ze sobą powiązane. Mam jednak trzecią klasę, CommonFuncktóra zawiera pewną funkcjonalność publiczną, która jest zaimplementowana zarówno ModelOnei ModelTwozostała uwzględniona zgodnie z DRY. Dwa modele są tworzone w obrębie ModelMainklasy (która sama tworzy się na wyższym poziomie itp. - ale zatrzymuję się na tym poziomie).

Kontener IoC, którego używam, to Microsoft Unity . Nie udaję, że jestem w tym ekspertem, ale rozumiem, że rejestrujesz krotkę interfejsu i klasy w kontenerze, a kiedy chcesz konkretnej klasy, pytasz kontener IoC o dowolny obiekt pasujący do określonego interfejsu. Oznacza to, że dla każdego obiektu, który chcę utworzyć z Unity, musi istnieć pasujący interfejs. Ponieważ każda z moich klas wykonuje inną (i nie nakładającą się) funkcjonalność, oznacza to, że istnieje stosunek 1: 1 między interfejsem a klasą 1 . Nie oznacza to jednak, że niewolniczo piszę interfejs dla każdej klasy, którą piszę.

Zatem pod względem kodu kończę na 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 { .. }

Pytanie dotyczy tego, jak zorganizować moje rozwiązanie. Czy powinienem trzymać klasę i interfejs razem? Czy powinienem trzymać klasy i interfejsy razem? NA PRZYKŁAD:

Opcja 1 - zorganizowane według nazwy klasy

MySolution
  |
  |-MyProject
  |   |
      |-Models
      |   |
          |-Common
          |   |
          |   |-CommonFunc.cs
          |   |-ICommonFunc.cs
          |
          |-Main
          |   |
          |   |-IModelMain.cs
          |   |-ModelMain.cs
          |
          |-One
          |   |
          |   |-IModelOne.cs
          |   |-ModelOne.cs
          |
          |-Two
              |
              |-IModelTwo.cs
              |-ModelTwo.cs
              |

Opcja 2 - uporządkowane według funkcjonalności (głównie)

MySolution
  |
  |-MyProject
  |   |
      |-Models
      |   |
          |-Common
          |   |
          |   |-CommonFunc.cs
          |   |-ICommonFunc.cs
          |
          |-IModelMain.cs
          |-IModelOne.cs
          |-IModelTwo.cs
          |-ModelMain.cs
          |-ModelOne.cs
          |-ModelTwo.cs
          |

Opcja 3 - Interfejs oddzielający i implementacja

MySolution
  |
  |-MyProject
      |
      |-Interfaces
      |   |
      |   |-Models
      |   |   |
      |       |-Common
      |       |   |-ICommonFunc.cs
      |       |
      |       |-IModelMain.cs
      |       |-IModelOne.cs
      |       |-IModelTwo.cs
      |
      |-Classes
          | 
          |-Models
          |   |
              |-Common
              |   |-CommonFunc.cs
              |
              |-ModelMain.cs
              |-ModelOne.cs
              |-ModelTwo.cs
              |

Opcja 4 - Dalszy przykład funkcjonalności

MySolution
  |
  |-MyProject
  |   |
      |-Models
      |   |
          |-Components
          |   |
          |   |-Common
          |   |   |
          |   |   |-CommonFunc.cs
          |   |   |-ICommonFunc.cs
          |   |   
          |   |-IModelOne.cs
          |   |-IModelTwo.cs
          |   |-ModelOne.cs
          |   |-ModelTwo.cs
          |
          |-IModelMain.cs
          |-ModelMain.cs
          |

Nie podoba mi się opcja 1 z powodu nazwy klasy na ścieżce. Ale ponieważ mam tendencję do stosunku 1: 1 ze względu na mój wybór / użycie IoC (i to może być dyskusyjne), ma to zaletę w widzeniu związku między plikami.

Opcja 2 jest dla mnie atrakcyjna, ale teraz zamazałem wody między podmodelami ModelMaina nimi.

Opcja 3 działa w celu oddzielenia definicji interfejsu od implementacji, ale teraz mam te sztuczne przerwy w nazwach ścieżek.

Opcja 4. Wziąłem opcję 2 i poprawiłem ją, aby oddzielić komponenty od modelu nadrzędnego.

Czy istnieje dobry powód, aby preferować jeden od drugiego? Lub jakieś inne potencjalne układy, które przegapiłem?


1. Frank skomentował, że stosunek 1: 1 nawiązuje do dni C ++ plików .h i .cpp. Wiem skąd on pochodzi. Moje rozumienie Jedności wydaje się wprowadzać mnie w ten kąt, ale nie jestem nawet pewien, jak się z tego wydostać, jeśli również postępujesz zgodnie z przysłowiem Program to an interface Ale to dyskusja na inny dzień.

2. Pominąłem szczegóły każdego konstruktora obiektów. Tutaj kontener IoC wstrzykuje obiekty zgodnie z wymaganiami.

Peter M.
źródło
1
Ugh. Pachnie, że masz stosunek 1: 1 interfejs / klasa. Którego kontenera IoC używasz? Ninject zapewnia mechanizmy, które nie wymagają stosunku 1: 1.
RubberDuck
1
@RubberDuck FWIW Korzystam z Unity. Nie twierdzę być ekspertem w tym, ale jeśli moje zajęcia są dobrze zaprojektowane z pojedynczych zadań, jak mogę nie skończyć z prawie 1: 1 stosunek?
Peter M
Potrzebujesz IBase, czy może baza może być abstrakcyjna? Dlaczego IDerivedOne, kiedy już implementuje IBase? Powinieneś być zależny od IBase, a nie pochodzić. Nie wiem o Unity, ale inne kontenery IoC pozwalają na wstrzykiwanie „kontekstowe”. Zasadniczo, gdy Client1trzeba IBase, zapewnia Derived1. W razie Client2potrzeby IBaseIoC zapewnia Derived2.
RubberDuck
1
Cóż, jeśli baza jest abstrakcyjna, to nie ma powodu, aby ją mieć interface. interfaceJest naprawdę klasą abstrakcyjną ze wszystkimi członkami wirtualnych.
RubberDuck
1
RubberDuck ma rację. Zbędne interfejsy są po prostu denerwujące.
Frank Hileman

Odpowiedzi:

4

Ponieważ interfejs jest abstrakcyjnie podobny do klasy podstawowej, użyj tej samej logiki, której użyłbyś dla klasy podstawowej. Klasy implementujące interfejs są ściśle powiązane z interfejsem.

Wątpię, czy wolisz katalog o nazwie „Klasy podstawowe”; większość programistów nie chce tego, ani katalogu o nazwie „Interfejsy”. W języku c # katalogi są również domyślnie przestrzeniami nazw, co powoduje podwójne zamieszanie.

Najlepiej jest pomyśleć o tym, jak rozbiłbyś klasy / interfejsy, gdybyś musiał umieścić je w osobnej bibliotece i zorganizować przestrzenie nazw / katalogi w podobny sposób. Wytyczne dotyczące projektowania .NET Framework zawierają sugestie dotyczące przestrzeni nazw, które mogą być pomocne.

Frank Hileman
źródło
Znam trochę podany przez Ciebie link, ale nie jestem pewien, w jaki sposób odnosi się on do organizacji plików. Podczas gdy VS domyślnie przyjmuje nazwy ścieżek dla początkowej przestrzeni nazw, wiem, że jest to dowolny wybór. Mam również aktualizację mojego „próbki” i możliwości układu.
Peter M
@PeterM Nie jestem pewien, którego IDE używasz, ale Visual Studio używa nazw katalogów do automatycznego generowania deklaracji przestrzeni nazw na przykład podczas dodawania klasy. Oznacza to, że chociaż możesz mieć różnice między nazwami katalogów i nazwami przestrzeni nazw, praca w ten sposób jest bardziej bolesna. Więc większość ludzi tego nie robi.
Frank Hileman
Używam VS. I tak, znam ból. Przywraca wspomnienia dotyczące układu plików Visual Source Safe.
Peter M
1
@PeterM W odniesieniu do stosunku interfejsu 1: 1 do klasy. Niektóre narzędzia tego wymagają. Uważam to za wadę narzędzia - .net ma wystarczającą zdolność odbijania, aby nie wymagać takich ograniczeń w żadnym takim narzędziu.
Frank Hileman
1

Przyjmuję drugie podejście, oczywiście z kilkoma folderami / przestrzeniami nazw w złożonych projektach. Oznacza to, że odłączam interfejsy od konkretnych implementacji.

W innym projekcie być może trzeba znać definicję interfejsu, ale nie jest wcale konieczna znajomość konkretnej klasy implementującej ją - szczególnie gdy używasz kontenera IoC. Tak więc te inne projekty muszą tylko odwoływać się do projektów interfejsu, a nie projektów wdrożeniowych. Może to utrzymywać niskie wartości referencyjne i zapobiegać problemom z referencjami cyklicznymi.

Bernhard Hiller
źródło
Poprawiłem swój przykład kodu i dodałem kilka dodatkowych opcji
Peter M
1

Jak zawsze w przypadku każdego pytania projektowego, pierwszą rzeczą do zrobienia jest ustalenie, kto jest Twoim użytkownikiem. Jak zamierzają korzystać z twojej organizacji?

Czy są programistami, którzy będą korzystać z twoich zajęć? Najlepszym rozwiązaniem może być oddzielenie interfejsów od kodu.

Czy są opiekunami twojego kodu? Zatem utrzymywanie interfejsów i klas razem może być najlepsze.

Usiądź i stwórz kilka przypadków użycia. Wtedy najlepsza organizacja może pojawić się przed tobą.

shawnhcorey
źródło
-2

Myślę, że lepiej jest przechowywać interfejsy w osobnej bibliotece. Po pierwsze, tego rodzaju projektowanie zwiększa zależność. Dlatego implementację tych interfejsów można wykonać w innym języku programowania.

Larissa Savchekoo
źródło