Dobra ćwierć wieku temu, kiedy uczyłem się C ++, nauczono mnie, że interfejsy powinny wybaczać i, w miarę możliwości, nie przejmować się kolejnością wywoływania metod, ponieważ konsument może nie mieć dostępu do źródła lub dokumentacji zamiast to.
Jednak za każdym razem, gdy mentorowałem młodszych programistów i starszych deweloperów, słyszeli mnie, reagowali ze zdziwieniem, co spowodowało, że zastanawiałem się, czy to naprawdę było coś, czy też właśnie wyszło z mody.
Czysty jak błoto?
Rozważ interfejs z tymi metodami (do tworzenia plików danych):
OpenFile
SetHeaderString
WriteDataLine
SetTrailerString
CloseFile
Teraz możesz oczywiście po prostu przejrzeć je w kolejności, ale powiedz, że nie obchodzi Cię nazwa pliku (pomyśl a.out
) lub jaki nagłówek i ciąg przyczepy zostały uwzględnione, możesz po prostu zadzwonić AddDataLine
.
Mniej ekstremalnym przykładem może być pominięcie nagłówków i przyczep.
Jeszcze innym może być ustawianie ciągów nagłówka i przyczepy przed otwarciem pliku.
Czy jest to zasada rozpoznawania interfejsu, czy tylko POLA, zanim nadano mu nazwę?
Uwaga: nie daj się wciągnąć w szczegóły tego interfejsu, jest to tylko przykład dla tego pytania.
źródło
Odpowiedzi:
Jednym ze sposobów, w jaki możesz trzymać się zasady najmniejszego zdziwienia, jest rozważenie innych zasad, takich jak ISP i SRP , a nawet DRY .
W podanym konkretnym przykładzie sugeruje się, że istnieje pewna zależność od kolejności manipulowania plikiem; ale interfejs API kontroluje zarówno dostęp do pliku, jak i format danych, który pachnie trochę jak naruszenie SRP.
Edycja / aktualizacja: sugeruje również, że sam interfejs API prosi użytkownika o naruszenie DRY, ponieważ będzie musiał powtarzać te same kroki za każdym razem, gdy użyje interfejsu API .
Rozważ alternatywny interfejs API, w którym operacje IO są niezależne od operacji na danych. a gdzie sam API „jest właścicielem” zamówienia:
ContentBuilder
FileWriter
Przy powyższej separacji
ContentBuilder
nie trzeba „robić” niczego poza przechowywaniem wierszy / nagłówków / zwiastunów (może takżeContentBuilder.Serialize()
metodą znającą kolejność). Przestrzeganie innych zasad SOLID nie ma już znaczenia, czy ustawisz nagłówek czy zwiastun przed czy po dodaniu wierszy, ponieważ nic wContentBuilder
pliku nie jest zapisywane do pliku, dopóki nie zostanie przekazaneFileWriter.Write
.Ma również tę dodatkową zaletę, że jest nieco bardziej elastyczny; na przykład przydatne może być zapisanie zawartości do rejestratora diagnostycznego lub przekazanie jej przez sieć zamiast zapisywania bezpośrednio w pliku.
Projektując interfejs API, należy również wziąć pod uwagę raportowanie błędów, niezależnie od tego, czy jest to stan, wartość zwracana, wyjątek, wywołanie zwrotne lub coś innego. Użytkownik interfejsu API prawdopodobnie spodziewa się, że będzie w stanie programowo wykryć wszelkie naruszenia jego umów lub nawet inne błędy, których nie może kontrolować, takie jak błędy we / wy pliku.
źródło
SetHeader
lub maAddLine
znaczenie. Aby wyeliminować tę zależność od zamówienia, nie jest to ani ISP, ani SRP, to po prostu POLA.FileWriter
może następnie wymagać wartości z ostatniegoContentBuilder
krokuWrite
metody, aby upewnić się, że cała zawartość wejściowa jest kompletna, co czyni jąInvalidContentException
niepotrzebną.ContentBuilder
i umożliwieniaFileWriter.Write
kapsułkowania tej odrobiny wiedzy. Wyjątek byłby konieczny na wypadek, gdyby coś było nie tak z treścią (np. Brakujący nagłówek). Zwrot może również działać, ale nie jestem fanem przekształcania wyjątków w kody powrotne.Nie chodzi tylko o POLA, ale także o zapobieganie nieprawidłowemu stanowi jako możliwemu źródłu błędów.
Zobaczmy, jak możemy wprowadzić pewne ograniczenia do Twojego przykładu bez konkretnej implementacji:
Pierwszy krok: nie zezwalaj na wywołanie czegokolwiek przed otwarciem pliku.
Teraz powinno być oczywiste, że
CreateDataFileInterface.OpenFile
należy wywołać, aby pobraćDataFileInterface
instancję, w której można zapisać rzeczywiste dane.Drugi krok: upewnij się, że hedery i przyczepy są zawsze ustawione.
Teraz musisz podać wszystkie wymagane parametry z góry, aby uzyskać
DataFileInterface
: nazwę pliku, nagłówek i zwiastun. Jeśli ciąg zwiastuna nie jest dostępny, dopóki nie zostaną zapisane wszystkie wiersze, możesz również przenieść ten parametr doClose()
(ewentualnie zmienić nazwę metodyWriteTrailerAndClose()
), aby przynajmniej pliku nie można było zakończyć bez ciągu zwiastuna.Aby odpowiedzieć na komentarz:
Prawdziwe. Nie chciałem koncentrować się bardziej na przykładzie, niż jest to konieczne, aby wyrazić swoje zdanie, ale to dobre pytanie. W tym przypadku myślę, że nazwałbym to
Finalize(trailer)
i twierdzę, że nie robi to zbyt wiele. Napisanie zwiastuna i zamknięcie są jedynie szczegółami implementacyjnymi. Ale jeśli się nie zgadzasz lub masz podobną sytuację, w której jest inaczej, oto możliwe rozwiązanie:Tak naprawdę nie zrobiłbym tego na tym przykładzie, ale pokazuje, w jaki sposób przeprowadzić technikę.
Nawiasem mówiąc, założyłem, że metody muszą być wywoływane w tej kolejności, na przykład, aby sekwencyjnie zapisywać wiele wierszy. Jeśli nie jest to wymagane, zawsze wolałbym budowniczego, jak zasugerował Ben Cottrel .
źródło
WriteTrailerAndClose()
) Zbliża się do naruszenia SRP. (Jest to coś, z czym zmagałem się wiele razy, ale twoja sugestia wydaje się być możliwym przykładem.) Jak byś zareagował?OpenFile
przeciążenie, które jej nie wymaga.