Aby być wymiennymi i testowalnymi, zwykle usługi logiczne muszą mieć interfejs, np
public class FooService: IFooService
{ ... }
Pod względem projektowym zgadzam się z tym, ale jedną z rzeczy, która przeszkadza mi w tym podejściu, jest to, że w przypadku jednej usługi musisz zadeklarować dwie rzeczy (klasę i interfejs), aw naszym zespole zwykle dwa pliki (jeden dla klasy i jeden dla interfejsu). Innym dyskomfortem jest trudność w nawigacji, ponieważ użycie „Idź do definicji” w IDE (VS2010) wskaże interfejs (ponieważ inne klasy odnoszą się do interfejsu), a nie rzeczywistą klasę.
Myślałem, że napisanie IFooService w tym samym pliku, co FooService, zmniejszy powyższą dziwność. W końcu IFooService i FooService są bardzo powiązane. Czy to dobra praktyka? Czy istnieje dobry powód, dla którego IFooService musi znajdować się we własnym pliku?
źródło
Odpowiedzi:
Nie musi to znajdować się we własnym pliku, ale Twój zespół powinien zdecydować się na standard i trzymać się go.
Masz również rację, że „Przejdź do definicji” prowadzi do interfejsu, ale jeśli masz zainstalowany Resharper , wystarczy jedno kliknięcie, aby otworzyć listę pochodnych klas / interfejsów z tego interfejsu, więc nie jest to wielka sprawa. Dlatego trzymam interfejs w osobnym pliku.
źródło
Myślę, że powinieneś przechowywać je jako osobne pliki. Jak powiedziałeś, celem jest pozostanie testowalnym i wymiennym. Umieszczając interfejs w tym samym pliku, co implementacja, wiążesz interfejs z konkretną implementacją. Jeśli zdecydujesz się utworzyć próbny obiekt lub inną implementację, interfejs nie będzie logicznie oddzielony od FooService.
źródło
Według SOLID, nie tylko należy utworzyć interfejs, i nie tylko powinien on znajdować się w innym pliku, powinien znajdować się w innym zestawie.
Czemu? Ponieważ każda zmiana pliku źródłowego, który kompiluje się w zestawie, wymaga ponownej kompilacji zestawu, a każda zmiana w zestawie wymaga ponownej kompilacji dowolnego zestawu zależnego. Tak więc, jeśli twoim celem, opartym na SOLID, jest możliwość zastąpienia implementacji A implementacją B, podczas gdy klasa C zależna od interfejsu I nie musi znać różnicy, musisz upewnić się, że zestaw z I nie zmienia się, chroniąc w ten sposób zwyczaje.
„Ale to tylko rekompilacja”. Słyszę, że protestujesz. Może i tak, ale w aplikacji na smartfony, co jest łatwiejsze w przypadku przepustowości danych użytkowników; pobieranie jednego pliku binarnego, który się zmienił, czy pobieranie tego pliku binarnego i pięciu innych z zależnym od niego kodem? Nie każdy program jest napisany do użytku przez komputery stacjonarne w sieci LAN. Nawet w takim przypadku, gdy przepustowość i pamięć są tanie, mniejsze wydania poprawek mogą mieć wartość, ponieważ są trywialne w przekazywaniu do całej sieci LAN za pośrednictwem Active Directory lub podobnych warstw zarządzania domeną; Twoi użytkownicy będą czekać tylko kilka sekund, aż zostanie zastosowany przy następnym logowaniu, zamiast kilku minut, aby cała instalacja została ponownie zainstalowana. Nie wspominając o tym, że im mniej zestawów należy ponownie skompilować podczas budowania projektu, tym szybciej będzie on budował,
Zastrzeżenie: Nie zawsze jest to możliwe lub wykonalne. Najłatwiej to zrobić, tworząc scentralizowany projekt „interfejsów”. Ma to swoje wady; kod staje się mniej przydatny, ponieważ do projektu interfejsu ORAZ do projektu implementacji należy odwoływać się w innych aplikacjach, ponownie wykorzystując warstwę trwałości lub inne kluczowe komponenty aplikacji. Możesz rozwiązać ten problem, dzieląc interfejsy na bardziej ciasno połączone zespoły, ale wtedy masz więcej projektów w swojej aplikacji, co sprawia, że pełna kompilacja jest bardzo bolesna. Kluczem jest równowaga i utrzymanie luźno sprzężonej konstrukcji; zazwyczaj możesz przenosić pliki w razie potrzeby, więc gdy zobaczysz, że klasa będzie wymagała wielu zmian, lub że nowe implementacje interfejsu będą potrzebne regularnie (być może do połączenia z nowo obsługiwanymi wersjami innego oprogramowania,
źródło
Dlaczego oddzielne pliki są niewygodne? Dla mnie jest o wiele ładniejszy i czystszy. Powszechne jest tworzenie podfolderu o nazwie „Interfejsy” i umieszczanie tam plików IFooServer.cs, jeśli chcesz zobaczyć mniej plików w Eksploratorze rozwiązań.
Interfejs jest zdefiniowany we własnym pliku z tego samego powodu, dla którego klasy są zwykle definiowane we własnym pliku: zarządzanie projektem jest prostsze, gdy struktura logiczna i struktura plików są identyczne, więc zawsze wiesz, który plik jest zdefiniowany dla danej klasy w. Może to ułatwić Ci życie podczas debugowania (ślady stosu wyjątków zwykle dają numery plików i wierszy) lub podczas scalania kodu źródłowego w repozytorium kontroli źródła.
źródło
Często dobrą praktyką jest, aby pliki kodu zawierały tylko jedną klasę lub jeden interfejs. Ale te praktyki kodowania są środkiem do celu - lepszej struktury kodu i ułatwienia pracy. Jeśli ty i twój zespół będziecie łatwiej pracować, jeśli klasy będą trzymane razem z ich interfejsami, to na pewno to zrobicie.
Osobiście wolę mieć interfejs i klasę w tym samym pliku, gdy jest tylko jedna klasa, która implementuje interfejs, na przykład w twoim przypadku.
Jeśli chodzi o problemy z nawigacją, mogę gorąco polecić ReSharper . Zawiera kilka bardzo przydatnych skrótów do bezpośredniego przejścia do metody implementującej określoną metodę interfejsu.
źródło
Wiele razy chcesz, aby Twój interfejs znajdował się nie tylko w osobnym pliku dla klasy, ale nawet w osobnym zestawie .
Na przykład interfejs umowy serwisowej WCF może być współdzielony zarówno przez klienta, jak i usługę, jeśli masz kontrolę nad oboma końcami przewodu. Przenosząc interfejs do własnego zestawu, będzie on miał mniej własnych zależności zestawu. To znacznie ułatwia klientowi konsumpcję, poluzowując sprzężenie z jego implementacją.
źródło
Rzadko, jeśli w ogóle, ma sens jedna implementacja interfejsu 1 . Jeśli umieścisz interfejs publiczny i klasę publiczną implementującą ten interfejs w tym samym pliku, istnieje duże prawdopodobieństwo, że nie potrzebujesz interfejsu.
Gdy klasa, którą kolokujesz z interfejsem, jest abstrakcyjna i wiesz, że wszystkie implementacje interfejsu powinny dziedziczyć tę klasę abstrakcyjną, zlokalizowanie dwóch w tym samym pliku ma sens. Nadal powinieneś przeanalizować swoją decyzję o użyciu interfejsu: zadaj sobie pytanie, czy sama klasa abstrakcyjna byłaby w porządku, i porzuć interfejs, jeśli odpowiedź jest twierdząca.
Ogólnie jednak powinieneś trzymać się strategii „jedna klasa publiczna / interfejs odpowiada jednemu plikowi”: jest łatwa do naśladowania i ułatwia nawigację w drzewie źródłowym.
1 Jednym z godnych uwagi wyjątków jest konieczność posiadania interfejsu do celów testowych, ponieważ wybór frameworku nakłada dodatkowe wymagania na kod.
źródło
sealed
. Jeśli podejrzewasz, że w przyszłości możesz dodać drugą podklasę, od razu umieść interfejs. PS Przyzwoity asystent refaktoryzacji może być twój po skromnej cenie jednej licencjivirtual
.Interfejsy należą do ich klientów, a nie do ich implementacji, zgodnie z definicją zwinnych zasad, praktyk i wzorców autorstwa Roberta C. Martina. Zatem połączenie interfejsu i implementacji w tym samym miejscu jest sprzeczne z jego zasadą.
Kod klienta zależy od interfejsu. Umożliwia to kompilację i wdrożenie kodu klienta i interfejsu bez implementacji. Następnie możesz mieć różne implementacje działające jak wtyczki do kodu klienta.
AKTUALIZACJA: To nie jest zasada zwinna. Gang czterech wzorców projektowych już w 94 roku mówi już o klientach stosujących się do interfejsów i programujących interfejsy. Ich pogląd jest podobny.
źródło
Ja osobiście nie lubię „ja” przed interfejsem. Jako użytkownik takiego typu nie chcę widzieć, że jest to interfejs, czy nie. Typ jest interesujący. Pod względem zależności FooService to JEDNA możliwa implementacja IFooService. Interfejs powinien znajdować się w pobliżu miejsca, w którym jest używany. Z drugiej strony wdrożenie powinno odbywać się w miejscu, w którym można je łatwo zmienić bez wpływu na klienta. Wolałbym więc dwa pliki - normalnie.
źródło
new
z nim korzystać, ale z drugiej strony mogę go używać w różnych wariantach. II
jest uznanym konwencja nazewnictwa w .NET, więc jest to dobry powód, aby stosować się do niego, jeśli tylko pod kątem spójności z innymi rodzajami.I
w interfejsie było jedynie wstępem do moich argumentów dotyczących rozdzielenia dwóch plików. Kod jest lepiej czytelny i sprawia, że odwracanie zależności jest bardziej wyraźne.I
w twoim otoczeniu jest normalne: użyj go! (Ale mi się nie podoba :))Kodowanie interfejsów może być złe, w rzeczywistości jest traktowane jako anty-wzorzec dla niektórych kontekstów i nie jest prawem. Zwykle dobrym przykładem kodowania anty-wzorców interfejsów jest posiadanie interfejsu, który ma tylko jedną implementację w Twojej aplikacji. Innym byłoby, gdyby plik interfejsu stał się tak uciążliwy, że trzeba ukryć jego deklarację w pliku zaimplementowanej klasy.
źródło
Umieszczenie klasy i interfejsu w tym samym pliku nie nakłada żadnych ograniczeń na to, jak można z nich korzystać. Interfejs może być nadal używany do próbnych prób itp. W ten sam sposób, nawet jeśli znajduje się w tym samym pliku co klasa, która go implementuje. Pytanie to można postrzegać wyłącznie jako wygodę organizacyjną, w którym to przypadku powiedziałbym, że rób wszystko, co czyni twoje życie łatwiejszym!
Oczywiście można argumentować, że posiadanie dwóch w tym samym pliku może sprawić, że nieostrożni uznają je za bardziej sprzężone programowo niż są w rzeczywistości, co stanowi ryzyko.
Osobiście, gdybym natknął się na bazę kodów, która korzystała z jednej konwencji nad drugą, nie byłbym zbytnio zaniepokojony.
źródło