Przeprowadzamy wiele testów jednostkowych i refaktoryzacji naszych obiektów biznesowych i wydaje mi się, że mam bardzo odmienne opinie na temat projektowania klas niż inni koledzy.
Przykładowa klasa, której nie jestem fanem:
public class Foo
{
private string field1;
private string field2;
private string field3;
private string field4;
private string field5;
public Foo() { }
public Foo(string in1, string in2)
{
field1 = in1;
field2 = in2;
}
public Foo(string in1, string in2, string in3, string in4)
{
field1 = in1;
field2 = in2;
field3 = in3;
}
public Prop1
{ get { return field1; } }
{ set { field1 = value; } }
public Prop2
{ get { return field2; } }
{ set { field2 = value; } }
public Prop3
{ get { return field3; } }
{ set { field3 = value; } }
public Prop4
{ get { return field4; } }
{ set { field4 = value; } }
public Prop5
{ get { return field5; } }
{ set { field5 = value; } }
}
W „prawdziwej” klasie nie wszystkie są ciągami, ale w niektórych przypadkach mamy 30 pól bazowych dla całkowicie publicznych właściwości.
Ja nienawidzę tej klasy, a ja nie wiem, czy jestem po prostu wybredna. Kilka ważnych rzeczy:
- Prywatne pola zaplecza bez logiki we właściwościach wydają się niepotrzebne i nadmuchują klasę
- Wiele konstruktorów (nieco w porządku), ale w połączeniu z
- wszystkie nieruchomości mają publiczną konfigurację, nie jestem fanem.
- Potencjalnie żadna właściwość nie miałaby przypisanej wartości z powodu pustego konstruktora, jeśli osoba dzwoniąca jest nieświadoma, możesz potencjalnie uzyskać bardzo niechciane i trudne do przetestowania zachowanie.
- To zbyt wiele właściwości! (w przypadku 30)
Znacznie trudniej jest mi naprawdę wiedzieć, w jakim stanie Foo
jest dany obiekt, jako implementator. Argument został postawiony: „możemy nie mieć niezbędnych informacji do ustawienia Prop5
w czasie budowy obiektu. Ok, chyba rozumiem to, ale jeśli tak jest, ustaw Prop5
publicznie tylko setter, nie zawsze do 30 właściwości w klasie.
Czy jestem po prostu wybredny i / lub szalony, że chcę klasy, która jest „łatwa w użyciu”, a nie „łatwa do napisania (wszystko publiczne)”? Klasy takie jak powyższe krzyczą do mnie, nie wiem, jak to będzie wykorzystane, więc na wszelki wypadek zamierzam upublicznić wszystko .
Jeśli nie jestem strasznie wybredny, jakie są dobre argumenty do walki z tego rodzaju myśleniem? Nie jestem zbyt dobry w artykułowaniu argumentów, ponieważ jestem bardzo sfrustrowany, próbując przekazać swój punkt widzenia (oczywiście nie umyślnie).
źródło
get
lubset
:-)Odpowiedzi:
Klasy całkowicie publiczne mają uzasadnienie dla niektórych sytuacji, a także innych ekstremalnych klas za pomocą tylko jednej metody publicznej (i prawdopodobnie wielu metod prywatnych). I zajęcia z niektórymi metodami publicznymi, a także prywatnymi.
Wszystko zależy od rodzaju abstrakcji, którą zamierzasz modelować za ich pomocą, jakie warstwy masz w swoim systemie, stopień enkapsulacji, jakiej potrzebujesz na różnych warstwach, i (oczywiście) jaką szkołę myśli autor klasy od. Wszystkie te typy można znaleźć w kodzie SOLID.
Napisano całe książki o tym, kiedy preferować rodzaj projektu, więc nie będę tu wymieniać żadnych zasad na ten temat, przestrzeń w tej sekcji nie byłaby wystarczająca. Jeśli jednak masz przykład z abstrakcyjnego świata, który chcesz modelować za pomocą klasy, jestem pewien, że społeczność tutaj chętnie pomoże ci ulepszyć projekt.
Aby zająć się innymi punktami:
Więc zamiast
pisać
lub
„Wiele konstruktorów”: nie jest to problem sam w sobie. Występuje problem, gdy występuje niepotrzebne duplikowanie kodu, jak pokazano w przykładzie lub hierarchia wywołań jest zawiła. Można to łatwo rozwiązać, przekształcając wspólne części w oddzielną funkcję i organizując łańcuch konstruktorów w jednokierunkowy sposób
„Potencjalnie żadna właściwość nie miałaby przypisanej wartości z powodu pustego konstruktora”: w języku C # każdy typ danych ma jasno określoną wartość domyślną. Jeśli właściwości nie są jawnie inicjowane w konstruktorze, przypisywana jest ta wartość domyślna. Jeśli jest to używane celowo , jest całkowicie w porządku - więc pusty konstruktor może być w porządku, jeśli autor wie, co robi.
„To zbyt wiele właściwości! (W przypadku 30)”: tak, jeśli masz swobodę zaprojektowania takiej klasy na zasadzie greenfield, 30 jest zbyt wielu, zgadzam się. Jednak nie każdy z nas ma ten luksus (czy nie napisałeś w komentarzu poniżej, jest to starszy system?). Czasami musisz zmapować rekordy z istniejącej bazy danych, pliku lub danych z interfejsu API innej firmy do swojego systemu. W tych przypadkach 30 atrybutów może być czymś, z czym trzeba żyć.
źródło
null
to nie jest? więc jeśli jedna z właściwości jest faktycznie używana w.ToUpper()
, ale dla niej nigdy nie jest ustawiona wartość, wygenerowałby wyjątek czasu wykonywania. Jest to kolejny przykład dobry, dlaczego wymagane dane dla klasy powinna mieć być ustawiony w trakcie budowy obiektu. Nie tylko pozostaw to użytkownikowi. DziękiMówiąc, że „Prywatne pola zaplecza bez logiki we właściwościach, wydają się niepotrzebne i nadymają klasę”, masz już porządny argument.
Wygląda na to, że problemem nie jest wiele konstruktorów.
Jeśli chodzi o posiadanie wszystkich właściwości jako publicznych ... Może pomyśl o tym w ten sposób, jeśli kiedykolwiek chciałbyś zsynchronizować dostęp do wszystkich tych właściwości między wątkami, miałbyś trudny czas, ponieważ być może będziesz musiał napisać kod synchronizacji wszędzie tam, gdzie są używany. Jeśli jednak wszystkie właściwości byłyby zawarte w getters / setters, wówczas można łatwo zbudować synchronizację w klasie.
Myślę, że kiedy mówisz „trudne do przetestowania zachowanie”, argument mówi sam za siebie. W teście może być konieczne sprawdzenie, czy nie jest to wartość zerowa lub coś w tym stylu (każde miejsce, w którym właściwość jest używana). Naprawdę nie wiem, bo nie wiem, jak wygląda twój test / aplikacja.
Masz rację, zbyt wiele właściwości, czy możesz spróbować użyć dziedziczenia (jeśli właściwości są wystarczająco podobne), a następnie mieć listę / tablicę przy użyciu ogólnych? Następnie możesz napisać metody pobierające / ustawiające, aby uzyskać dostęp do informacji i uprościć sposób interakcji ze wszystkimi właściwościami.
źródło
Jak powiedziałeś,
Foo
jest to obiekt biznesowy. Powinien zawierać pewne zachowania w metodach w zależności od domeny, a jego dane muszą pozostać jak najbardziej zamknięte.W pokazanym przykładzie
Foo
wygląda bardziej jak DTO i jest niezgodny ze wszystkimi zasadami OOP (zobacz Anemic Domain Model )!Jako ulepszenia sugerowałbym:
Może to być potencjalnie refaktoryzowana klasa z następującymi uwagami:
źródło
„Możemy teraz testować jednostkowo te refaktoryzowane metody. Ponieważ tak jest, z powodów x, y, z klient nie jest testowalny.
W zakresie, w jakim można przytoczyć dowolny z powyższych argumentów, łącznie:
źródło