OK .. po całej dyskusji zmieniam nieco moje pytanie, aby lepiej odzwierciedlić konkretny przykład, z którym mam do czynienia.
Mam dwie klasy ModelOne
i klasy ModelTwo
te wykonują podobny typ funkcjonalności, ale nie są ze sobą powiązane. Mam jednak trzecią klasę, CommonFunc
która zawiera pewną funkcjonalność publiczną, która jest zaimplementowana zarówno ModelOne
i ModelTwo
została uwzględniona zgodnie z DRY
. Dwa modele są tworzone w obrębie ModelMain
klasy (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 ModelMain
a 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.
źródło
Client1
trzebaIBase
, zapewniaDerived1
. W razieClient2
potrzebyIBase
IoC zapewniaDerived2
.interface
.interface
Jest naprawdę klasą abstrakcyjną ze wszystkimi członkami wirtualnych.Odpowiedzi:
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.
źródło
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.
źródło
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ą.
źródło
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.
źródło