Powiedzmy, że mam procedurę, która wykonuje różne czynności :
void doStuff(initalParams) {
...
}
Teraz odkrywam, że „robienie rzeczy” to dość skomplikowana operacja. Procedura staje się duża, podzielę ją na wiele mniejszych procedur i wkrótce zdaję sobie sprawę, że posiadanie jakiegoś stanu byłoby przydatne podczas robienia rzeczy, więc muszę przekazywać mniej parametrów między małymi procedurami. Tak więc dzielę to na własną klasę:
class StuffDoer {
private someInternalState;
public Start(initalParams) {
...
}
// some private helper procedures here
...
}
A potem nazywam to tak:
new StuffDoer().Start(initialParams);
lub tak:
new StuffDoer(initialParams).Start();
I to jest złe. Korzystając z .NET lub Java API, nigdy nie dzwonię new SomeApiClass().Start(...);
, co mnie podejrzewa, że robię to źle. Jasne, mógłbym ustawić konstruktora StuffDoer na prywatny i dodać metodę statycznego pomocnika:
public static DoStuff(initalParams) {
new StuffDoer().Start(initialParams);
}
Ale wtedy miałbym klasę, której zewnętrzny interfejs składa się tylko z jednej metody statycznej, co również wydaje się dziwne.
Stąd moje pytanie: czy istnieje dobrze ustalony wzorzec dla tego typu klas, które
- mieć tylko jeden punkt wejścia i
- nie mają stanu „zewnętrznie rozpoznawalnego”, tj. stan instancji jest wymagany tylko podczas wykonywania tego jednego punktu wejścia?
bool arrayContainsSomestring = new List<string>(stringArray).Contains("somestring");
robiłem takie rzeczy, kiedy wszystko, na czym mi zależało, to ta konkretna informacja, a metody rozszerzenia LINQ nie są dostępne. Działa dobrze i mieści się wif()
stanie bez konieczności przeskakiwania przez obręcze. Oczywiście, jeśli chcesz pisać kod w ten sposób, potrzebujesz zbierającego śmieci języka.Odpowiedzi:
Istnieje wzorzec o nazwie Method Object, w którym wyróżniasz pojedynczą, dużą metodę z dużą ilością tymczasowych zmiennych / argumentów w oddzielnej klasie. Robisz to zamiast wyodrębniać części metody w osobne metody, ponieważ potrzebują one dostępu do stanu lokalnego (parametry i zmienne tymczasowe) i nie możesz udostępnić stanu lokalnego za pomocą zmiennych instancji, ponieważ byłyby lokalne dla tej metody (i metody wyodrębnione z niego) i pozostałyby nieużywane przez resztę obiektu.
Zamiast tego metoda staje się własną klasą, parametry i zmienne tymczasowe stają się zmiennymi instancji tej nowej klasy, a następnie metoda zostaje podzielona na mniejsze metody nowej klasy. Klasa wynikowa ma zwykle tylko jedną metodę instancji publicznej, która wykonuje zadanie zawarte w klasie.
źródło
Powiedziałbym, że przedmiotowa klasa (wykorzystująca pojedynczy punkt wejścia) jest dobrze zaprojektowana. Jest łatwy w użyciu i przedłużany. Dość SOLIDNY.
To samo dotyczy
no "externally recognizable" state
. To dobre kapsułkowanie.Nie widzę żadnych problemów z kodem, takich jak:
źródło
doer
, zmniejsza się dovar result = new StuffDoer(initialParams).Calculate(extraParams);
.StringComparer
klasa, której używasz, jestnew StringComparer().Compare( "bleh", "blah" )
dobrze zaprojektowana? Co z tworzeniem stanu, gdy nie ma takiej potrzeby? Okej, zachowanie jest dobrze zamknięte (przynajmniej dla użytkownika klasy, więcej na ten temat w mojej odpowiedzi ), ale domyślnie nie oznacza to, że jest to „dobry projekt”. Jeśli chodzi o twój przykład „doer-rezultat”. Miałoby to sens tylko, gdybyś kiedykolwiek używał godoer
osobnoresult
.one reason to change
. Różnica może wydawać się subtelna. Musisz zrozumieć, co to znaczy. Nigdy nie utworzy jednej klasy metodZ punktu widzenia zasad SOLID odpowiedź jgauffina ma sens. Nie należy jednak zapominać o ogólnych zasadach projektowania, np . Ukrywania informacji .
Widzę kilka problemów z danym podejściem:
Niektóre powiązane odniesienia
Być może głównym argumentem jest to, jak daleko należy rozciągnąć zasadę pojedynczej odpowiedzialności . „Jeśli dojdziesz do tego ekstremum i zbudujesz klasy, które mają jeden powód istnienia, możesz skończyć z tylko jedną metodą na klasę. Spowodowałoby to duży rozrost klas nawet dla najprostszych procesów, powodując, że system byłby trudne do zrozumienia i trudne do zmiany ”.
Kolejne istotne odniesienie do tego tematu: „Podziel swoje programy na metody, które wykonują jedno możliwe do zidentyfikowania zadanie. Zachowaj wszystkie operacje w metodzie na tym samym poziomie abstrakcji ”. - Kent Beck Key tutaj jest „tym samym poziomem abstrakcji”. Nie oznacza to „jednej rzeczy”, ponieważ jest często interpretowane. Ten poziom abstrakcji zależy wyłącznie od kontekstu, dla którego projektujesz.
Jakie jest właściwe podejście?
Trudno powiedzieć bez znajomości konkretnego przypadku użycia. Jest scenariusz, w którym czasami (nie często) stosuję podobne podejście. Kiedy chcę przetworzyć zestaw danych, bez potrzeby udostępniania tej funkcji dla całego zakresu klasy. Napisałem na blogu o tym, w jaki sposób lambdas może jeszcze bardziej poprawić enkapsulację . Zacząłem też pytanie na ten temat tutaj, na temat programistów . Poniżej znajduje się najnowszy przykład zastosowania tej techniki.
Ponieważ bardzo „lokalny” kod w
ForEach
pliku nie jest ponownie używany nigdzie indziej, mogę po prostu przechowywać go w dokładnej lokalizacji, w której ma znaczenie. Zarysowanie kodu w taki sposób, że kod, który opiera się na sobie, jest silnie zgrupowane razem, czyni moim zdaniem bardziej czytelnym.Możliwe alternatywy
źródło
Powiedziałbym, że jest całkiem dobry, ale twój interfejs API powinien nadal być metodą statyczną w klasie statycznej, ponieważ tego oczekuje użytkownik. Fakt, że metoda statyczna używa
new
do tworzenia obiektu pomocnika i wykonywania pracy, jest szczegółem implementacji, który powinien być ukryty przed tym, kto go wywołuje.źródło
Występuje problem z nieświadomymi programistami, którzy mogą korzystać z udostępnionej instancji i korzystać z niej
Potrzebuje to więc wyraźnej dokumentacji, że nie jest bezpieczny wątkowo, a jeśli jest lekki (tworzenie go jest szybkie, nie wiąże się z zasobami zewnętrznymi ani dużą ilością pamięci), to jest w porządku, aby utworzyć instancję w locie.
Pomaga w tym ukrywanie go w metodzie statycznej, ponieważ ponowne użycie instancji nigdy się nie zdarza.
Jeśli to ma jakieś drogie inicjalizacji, to może (nie zawsze) będzie korzystne, aby przygotować inicjowanie go raz i używając go wiele razy, tworząc kolejną klasę, która będzie zawierać tylko stan i uczynić go Cloneable. Inicjalizacja utworzy stan, w którym będzie przechowywany
doer
.start()
sklonuje go i przekaże metodom wewnętrznym.Pozwoliłoby to również na inne rzeczy, takie jak utrzymywanie stanu częściowego wykonania. (Jeśli zajmie to dużo czasu, a czynniki zewnętrzne, np. Awaria zasilania elektrycznego, mogą przerwać wykonanie). Ale te dodatkowe wymyślne rzeczy zwykle nie są potrzebne, więc nie warto.
źródło
Poza moją bardziej złożoną, spersonalizowaną inną odpowiedzią , uważam, że poniższa obserwacja zasługuje na osobną odpowiedź.
Istnieją oznaki, że możesz stosować się do anty-wzorca Poltergeists .
źródło
Istnieje kilka wzorów, które można znaleźć w P Martina Fowlera z katalogu EAA ;
źródło