Jak argumentować przeciwko temu „całkowicie publicznemu” myśleniu o projektowaniu klas obiektów biznesowych

9

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 Foojest dany obiekt, jako implementator. Argument został postawiony: „możemy nie mieć niezbędnych informacji do ustawienia Prop5w czasie budowy obiektu. Ok, chyba rozumiem to, ale jeśli tak jest, ustaw Prop5publicznie 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).

Kritner
źródło
3
Obiekt powinien zawsze znajdować się w poprawnym stanie; to znaczy, nie powinieneś mieć częściowo utworzonych obiektów . Niektóre informacje nie są częścią podstawowego stanu obiektu, podczas gdy inne informacje (np. Stół musi mieć nogi, ale tkanina jest opcjonalna); opcjonalne informacje można podać po utworzeniu instancji, podstawowe informacje należy podać przy tworzeniu instancji. Jeśli niektóre informacje są kluczowe, ale nie są znane przy tworzeniu, to powiedziałbym, że klasy wymagają refaktoryzacji. Trudno powiedzieć więcej, nie znając kontekstu zajęć.
Marvin
1
Powiedzenie „możemy nie mieć niezbędnych informacji, aby ustawić Prop5 w czasie budowy obiektu”, każe mi zapytać „czy mówisz, że będą chwile, że będziesz miał te informacje w czasie budowy, a innym razem, gdy nie będziesz masz tę informację? ” Zastanawiam się, czy faktycznie są dwie różne rzeczy, które ta klasa próbuje reprezentować, czy może ta klasa powinna zostać podzielona na mniejsze klasy. Ale jeśli zrobiono to „na wszelki wypadek”, to również źle, IMO; mówi mi to, że nie ma jasnego projektu, w jaki sposób obiekty są tworzone / wypełniane.
Dr Wily's Apprentice
3
Czy w przypadku prywatnych pól bez logiki we właściwościach istnieje powód, dla którego nie używasz właściwości automatycznych ?
Carson63000,
2
@Kritner cały sens auto-właściwości polega na tym, że jeśli potrzebujesz tam logiki w przyszłości, możesz bezproblemowo dodać ją w miejsce auto getlub set:-)
Carson63000,

Odpowiedzi:

10

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:

  • „prywatne pola zaplecza bez logiki we właściwościach”: Tak, masz rację, dla trywialnych programów pobierających i ustawiających jest to po prostu niepotrzebny „szum”. Aby uniknąć tego rodzaju „wzdęcia”, C # ma skróconą składnię dla metod pobierania / ustawiania właściwości:

Więc zamiast

   private string field1;
   public string Prop1
   { get { return field1; } }
   { set { field1 = value; } }

pisać

   public string Prop1 { get;set;}

lub

   public string Prop1 { get;private set;}
  • „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ć.

Doktor Brown
źródło
Dzięki, tak, wiem, że POCO / POJO mają swoje miejsce, a mój przykład jest dość abstrakcyjny. Jednak nie sądzę, żebym mógł się bardziej sprecyzować, nie wchodząc w powieść.
Kritner,
@Kritner: zobacz moją edycję
Doc Brown
tak, zwykle kiedy tworzę zupełnie nową klasę, wybieram trasę nieruchomości. Posiadanie prywatnych pól wsparcia „tylko dlatego, że” jest (moim zdaniem) niepotrzebne, a nawet powoduje problemy. Kiedy masz ~ 30 właściwości do klasy i ponad 100 klas, nie jest zbyt daleko idące stwierdzenie, że będzie jakieś ustawienie Właściwości lub uzyskiwanie z niewłaściwego pola zaplecza ... Już kilkakrotnie napotkałem to podczas naszego refaktoryzacji :)
Kritner,
dzięki, domyślna wartość łańcucha nullto 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ęki
Kritner,
Oprócz zbyt wielu nieuzbrojonych właściwości główny problem z tą klasą polega na tym, że ma ona zerową logikę (ZRP) i jest archetypem modelu anemicznego. Bez takiej potrzeby, którą można wyrazić słowami, np. DTO, jest to kiepski projekt.
user949300
2
  1. Mówiąc, że „Prywatne pola zaplecza bez logiki we właściwościach, wydają się niepotrzebne i nadymają klasę”, masz już porządny argument.

  2. Wygląda na to, że problemem nie jest wiele konstruktorów.

  3. 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.

  4. 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.

  5. 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.

Podejrzeć
źródło
1
Dzięki - nr 1 i nr 4 Zdecydowanie najbardziej czuję się swobodnie z argumentowaniem. Nie jestem pewien, co masz na myśli w punkcie 3. # 5 większość właściwości w klasach stanowi klucz podstawowy w db. Używamy kluczy konkurencyjnych, czasem do 8 kolumn - ale to już inna kwestia. Myślałem o próbie umieszczenia części tworzących klucz we własnej klasie / strukturze, co wyeliminowałoby wiele właściwości (przynajmniej w tej klasie) - ale jest to starsza aplikacja z tysiącami wierszy kodu z wieloma dzwoniący. Myślę, że mam dużo do przemyślenia :)
Kritner,
Eh Gdyby klasa była niezmienna, nie byłoby powodu pisać kodu synchronizacji wewnątrz klasy.
RubberDuck,
@RubberDuck Czy ta klasa jest niezmienna? Co masz na myśli?
Snoop,
3
To zdecydowanie nie jest niezmienne @StevieV. Każda klasa z ustawiającymi właściwości jest zmienna.
RubberDuck,
1
@Kritner W jaki sposób właściwości są używane jako klucze podstawowe? To prawdopodobnie wymaga pewnej logiki (kontroli zerowej) lub reguł (np. NAME ma pierwszeństwo przed WIEKIEM). Według OOP ten kod należy do tej klasy. Czy możesz to wykorzystać jako argument?
user949300
2

Jak powiedziałeś, Foojest 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 Foowygląda bardziej jak DTO i jest niezgodny ze wszystkimi zasadami OOP (zobacz Anemic Domain Model )!

Jako ulepszenia sugerowałbym:

  • Spraw, aby ta klasa była jak najbardziej niezmienna . Jak powiedziałeś, chcesz przeprowadzić testy jednostkowe. Jedną z obowiązkowych możliwości testów jednostkowych jest determinizm , a determinizm można osiągnąć poprzez niezmienność, ponieważ rozwiązuje on problemy skutków ubocznych .
  • Podziel tę boską klasę na wiele klas, które wykonują tylko 1 rzecz każda (patrz SRP ). Test jednostkowy klasy 30 atrybutów w naprawdę PITA .
  • Usuń nadmiarowość konstruktora, mając tylko 1 główny konstruktor, a pozostałe wywołują główny konstruktor.
  • Usuń niepotrzebne programy pobierające, poważnie wątpię, aby wszystkie programy pobierające były naprawdę przydatne.
  • Przywróć logikę biznesową w klasach biznesowych, tam właśnie należy!

Może to być potencjalnie refaktoryzowana klasa z następującymi uwagami:

public sealed class Foo
{
    private readonly string field1;
    private readonly string field2;
    private readonly string field3;
    private readonly string field4;

    public Foo() : this("default1", "default2")
    { }

    public Foo(string in1, string in2) : this(in1, in2, "default3", "default4")
    { }

    public Foo(string in1, string in2, string in3, string in4)
    {
        field1 = in1;
        field2 = in2;
        field3 = in3;
        field4 = in4;
    }

    //Methods with business logic

    //Really needed ?
    public Prop1
    { get; }

    //Really needed ?
    public Prop2
    { get; }

    //Really needed ?
    public Prop3
    { get; }

    //Really needed ?
    public Prop4
    { get; }

    //Really needed ?
    public Prop5
    { get; }
}
Cętkowany
źródło
2

Jak argumentować przeciwko temu „całkowicie publicznemu” myśleniu o projektowaniu klas obiektów biznesowych

  • „Istnieje kilka metod rozproszonych w klasie (klasach) wykonujących więcej niż„ zwykły obowiązek DTO ”. Należą one do siebie.”
  • „Może to być„ DTO ”, ale ma tożsamość biznesową - musimy zastąpić Equals”.
  • „Musimy je uporządkować - musimy wdrożyć IComparable”
  • „Łączymy kilka z tych właściwości. Zastąpmy ToString, aby każdy klient nie musiał pisać tego kodu”.
  • „Mamy wielu klientów robiących to samo z tą klasą. Musimy WYSUSZYĆ kod”.
  • „Klient manipuluje kolekcją tych rzeczy. To oczywiste, że powinna to być niestandardowa klasa kolekcji; a wiele wcześniejszych punktów sprawi, że ta niestandardowa kolekcja będzie bardziej funkcjonalna niż obecnie”.
  • „Spójrz na wszystkie żmudne, odsłonięte manipulacje ciągami! Podsumowanie, że w metodach biznesowych zwiększy naszą wydajność kodowania”.
  • „Połączenie tych metod razem w klasę sprawi, że będą one testowalne, ponieważ nie musimy radzić sobie z bardziej złożoną klasą klienta”.
  • „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:

    • Funkcjonalność staje wielokrotnego użytku.
    • To jest suche
    • Jest oddzielony od klientów.
    • kodowanie względem klasy jest szybsze i mniej podatne na błędy.
  • „Dobrze wykonana klasa ukrywa stan i ujawnia funkcjonalność. To jest propozycja początkowa do projektowania klas OO”.
  • „Ostateczny wynik znacznie lepiej wyraża domenę biznesową; odrębne podmioty i ich wyraźna interakcja”.
  • „Ta„ ogólna ”funkcjonalność (tj. Nieprecyzyjne wymagania funkcjonalne, ale trzeba to zrobić) wyraźnie wyróżnia się i jest zgodna z zasadą pojedynczej odpowiedzialności.
radarbob
źródło