2010-12-15 21 views
9

Oglądałem ostatnio filmy z Greg Young i próbuję zrozumieć, dlaczego negatywny stosunek do seterów na obiektach Domain. Sądziłem, że obiekty domeny mają być "ciężkie" z logiką w DDD. Czy istnieją dobre przykłady online tego złego przykładu, a następnie sposób, aby to poprawić? Wszelkie przykłady lub objaśnienia są dobre. Czy dotyczy to tylko zdarzeń przechowywanych w sposób CQRS, czy dotyczy to wszystkich DDD?Domain Driven Design, Domain objects, nastawienie do seterów

Odpowiedz

11

Wkładam tę odpowiedź, aby uzupełnić odpowiedź Rogera Alsinga na temat niezmienników z innych powodów.

semantyczna informacji

Roger wyraźnie wyjaśnił, że setery własności nie posiadają informacje semantyczne. Zezwalanie na ustawianie dla właściwości takiej jak Post.PublishDate może powodować zamieszanie, ponieważ nie możemy być pewni, czy post został opublikowany, czy tylko, jeśli ustawiono datę publikacji. Nie możemy wiedzieć na pewno, że to wszystko, co jest potrzebne do opublikowania artykułu. Obiekt "interfejs" wyraźnie nie pokazuje "zamiaru".

Uważam jednak, że właściwości takie jak "Włączone" przenoszą wystarczającą ilość znaczeń semantycznych zarówno dla "otrzymywania", jak i "ustawiania". Jest to coś, co powinno być natychmiast skuteczne i nie widzę potrzeby stosowania metod SetActive() lub Activate()/Deactivate() z samego powodu brakującej semantyki w ustawiaczu właściwości.

Niezmienniki

Roger mówił również o możliwości zerwania niezmienników przez ustawiaczy własności. Jest to absolutnie słuszne i należy tworzyć właściwości, które działają w tandemie, aby zapewnić "kombinowaną niezmienną wartość" (jako kąty trójkąta, aby użyć przykładu Rogera) jako właściwości tylko do odczytu i utworzyć metodę, aby ustawić je wszystkie razem, co może sprawdź wszystkie kombinacje w jednym kroku.

inicjalizacji zamówienie nieruchomości/ustawienia zależności

ten jest podobny do problemu z niezmienników gdyż powoduje problemy z właściwościami, które powinny być zatwierdzone/zmienionych razem. Wyobraź sobie następujący kod:

public class Period 
    { 
     DateTime start; 
     public DateTime Start 
     { 
      get { return start; } 
      set 
      { 
       if (value > end && end != default(DateTime)) 
        throw new Exception("Start can't be later than end!"); 
       start = value; 
      } 
     } 

     DateTime end; 
     public DateTime End 
     { 
      get { return end; } 
      set 
      { 
       if (value < start && start != default(DateTime)) 
        throw new Exception("End can't be earlier than start!"); 
       end = value; 
      } 
     } 
    } 

Jest to naiwny przykład sprawdzania poprawności "settera", który powoduje zależności kolejności dostępu. Poniższy kod ilustruje ten problem:

 public void CanChangeStartAndEndInAnyOrder() 
     { 
      Period period = new Period(DateTime.Now, DateTime.Now); 
      period.Start = DateTime.Now.AddDays(1); //--> will throw exception here 
      period.End = DateTime.Now.AddDays(2); 
      // the following may throw an exception depending on the order the C# compiler 
      // assigns the properties. 
      period = new Period() 
      { 
       Start = DateTime.Now.AddDays(1), 
       End = DateTime.Now.AddDays(2), 
      }; 
      // The order is not guaranteed by C#, so either way may throw an exception 
      period = new Period() 
      { 
       End = DateTime.Now.AddDays(2), 
       Start = DateTime.Now.AddDays(1), 
      }; 
     } 

Ponieważ nie możemy zmienić datę rozpoczęcia przeszłości dacie zakończenia okresu na obiekcie (chyba że jest to „pusty” okres, w obu terminach określonych domyślnych (DateTime) - tak, to nie jest świetny projekt, ale masz na myśli to, co mam na myśli ...), próbując ustawić datę początkową jako pierwszy, wyrzucisz wyjątek.

To staje się poważniejsze, gdy używamy inicjalizatorów obiektów. Ponieważ C# nie gwarantuje żadnej kolejności przypisania, nie możemy podać żadnych bezpiecznych założeń, a kod może lub nie może wyrzucać wyjątek w zależności od wyboru kompilatora. ZŁY!

Jest to ostatecznie problem z DESIGNem klas. Ponieważ właściwość nie może "wiedzieć", że aktualizujesz obie wartości, nie może "wyłączyć" sprawdzania poprawności, dopóki obie wartości nie zostaną faktycznie zmienione. Powinieneś albo uczynić obie właściwości tylko do odczytu, i dostarczyć metodę do ustawienia w tym samym czasie (utratę funkcji inicjalizatorów obiektów) lub usunąć kod walidacji z właściwości łącznie (być może wprowadzając inną właściwość tylko do odczytu, taką jak IsValid, lub sprawdzając poprawność gdy jest to konieczne).

ORM "uwodnienie" *

Hydration, w uproszczonym widzenia oznacza uzyskanie danych trwało powrotem do obiektów. Dla mnie jest to naprawdę największy problem z dodawaniem logiki za ustawianiem właściwości.

Wiele/większość ORM odwzorowuje utrwaloną wartość na właściwość. Jeśli posiadasz logikę lub logikę walidacji, która zmienia stan obiektu (inne elementy) wewnątrz ustawiaczy właściwości, spróbujesz zweryfikować obiekt "niekompletny" (który wciąż jest ładowany). Jest to bardzo podobne do problemu inicjalizacji obiektu, ponieważ nie można kontrolować porządku, w którym pola są "nawodnione".

Większość ORMów pozwala mapować trwałość na prywatne pola zamiast właściwości, a to pozwoli na uwodnienie obiektów, ale jeśli logika walidacji znajduje się głównie w ustawieniach nieruchomości, może być konieczne skopiowanie jej gdzie indziej, aby sprawdzić, czy załadowany obiekt jest ważny lub nie.

Ponieważ wiele narzędzi ORM obsługuje leniwe ładowanie (podstawowy aspekt ORM!) Poprzez użycie właściwości (lub metod) wirtualnych mapowanie do pól spowoduje, że ORM nie będzie mógł wczytać obiektów odwzorowanych w pola.

Wnioski

Więc w końcu, aby uniknąć powielania kodu, pozwalają ORMs wykonywać najlepiej jak mogli, zapobiegania zaskakujące wyjątki w zależności od kolejności pola są ustawione, dobrze jest przenieść logikę z dala od właścicieli nieruchomości.

Wciąż zastanawiam się, gdzie powinna być ta logika "walidacji". Gdzie sprawdzamy niezmienne aspekty obiektu? Gdzie umieszczamy walidacje wyższego poziomu? Czy używamy haków na ORMach do sprawdzania poprawności (OnSave, OnDelete, ...)? Itd. Itd. Itd. Ale to nie jest zakres tej odpowiedzi.

+1

, więc w twoim przykładzie powinniśmy użyć jednej funkcji, takiej jak 'period.setDateBoundries (startDate, endDate)' aby uniknąć seterów? pojedyncza funkcja, która ma znaczenie biznesowe zamiast ustawiania manekinów? – Songo

+1

@Songo to właśnie bym zrobił. Ale wtedy można by narzekać na obie daty, aby zmienić tylko jedną (wyobraź sobie, że gdybyśmy chcieli przedłużyć termin, musielibyśmy podać obie daty! ... ale potem, hej, dlaczego nie stworzymy metody Extend ?) Pomyślmy tak: kiedy kończysz na własności zależnej od innej, dobrym pomysłem może być wprowadzenie metod ich zmiany lub metod, które dodadzą więcej wartości semantycznych (jak np. Okres.)! – Loudenvier

+0

Zastanawiam się tylko nad przekazywaniem tablic. Nie jest to dobre dla podpowiedzi IDE, ale pozwala mi przekazywać dane w dowolnej kolejności. Mogę również dodawać lub pomijać dane w zależności od okoliczności (np. Pełny zakres dat lub tylko jedna data). Sprawdzanie poprawności odbywa się w każdym razie, więc nie wydaje się zbyt dużym obciążeniem, aby sprawdzić, czy parametr został podany lub pominięty. – prograhammer

1

Seter po prostu ustawia wartość. To nie powinno być "heavy" with logic.

Metody na twoich obiektach, które mają dobre nazwy opisowe, powinny być tymi "heavy" with logic i mieć analogię w samej domenie.

7

Powodem tego jest fakt, że sama jednostka powinna być odpowiedzialna za zmianę jej stanu. Tak naprawdę nie ma powodu, dla którego powinieneś ustawić własność poza jednostką.

Prostym przykładem może być encja o nazwie. Jeśli masz publicznego ustawiacza, będziesz mógł zmienić nazwę podmiotu z dowolnego miejsca w twojej aplikacji. Jeśli zamiast tego usuniesz tego setera i umieścisz w nim metodę taką jak ChangeName(string name), która będzie jedyną metodą zmiany nazwy. W ten sposób możesz dodać dowolną logikę, która zawsze będzie działać po zmianie nazwy, ponieważ istnieje tylko jeden sposób na jej zmianę. Jest to również o wiele jaśniejsze niż po prostu ustawienie nazwy czegoś.

Zasadniczo oznacza to, że zachowujesz się publicznie w swoich jednostkach, podczas gdy państwo zachowujesz prywatnie.

+0

I znowu będzie seter. Ale w tym przypadku masz metodę (lub konsument komunikatów) zamiast własności i sprawiasz, że twój obiekt domeny jest aktywny (nie tylko DTO). I dlaczego potrzebujesz (lub nie potrzebujesz), aby to wyjaśnić w artykule http://martinfowler.com/bliki/AnemicDomainModel.html –

+0

@Vsevolod Parfenov, Dokładnie. To był bardzo prosty przykład. Ale czasami może być konieczne dodanie logiki do tej metody. Może chcesz również zaktualizować inne pole. –

+0

SETTER dla właściwości jest po prostu syntaktycznym cukrem dla metody. Jeśli właściwość ma logikę, czyni także obiekt domeny aktywnym obiektem, a nie tylko DTO, ponieważ w rzeczywistości właśnie dodałeś do niego metodę, aczkolwiek "ukrytą" jako właściwość ustawiającą. Nadal będziesz mieć tylko jedno miejsce do zmiany nazwy jednostki: właściwość ustawiająca dla właściwości Nazwa. Nie są to więc powody negatywnego nastawienia do właścicieli nieruchomości. Są inne powody (które rozwiążę w odpowiedzi): niezmienniki, zależność sekwencji, problemy z nawodnieniem jednostek z narzędzia ORM. – Loudenvier

2

Oryginalne pytanie jest oznaczone .net, więc przedstawię pragmatyczne podejście do kontekstu, w którym chcesz powiązać swoje jednostki bezpośrednio z widokiem.

Wiem, że to jest zła praktyka, i że prawdopodobnie powinieneś mieć model widoku (jak w MVVM) lub coś podobnego, ale w przypadku niektórych małych aplikacji po prostu ma sens, aby nie nadwerężyć IMHO.

Korzystanie z właściwości to sposób, w jaki domyślne powiązanie danych działa w .net. Być może kontekst zakłada, że ​​powiązanie danych powinno działać w obie strony, a zatem wdrożenie INotifyPropertyChanged i podniesienie PropertyChanged jako części logiki ustawiającej ma sens.

Jednostka może np. dodaj element do zbitej kolekcji reguł lub czegoś podobnego (wiem, że CSLA miał tę koncepcję kilka lat temu i może nadal), gdy klient ustawia nieprawidłową wartość, a kolekcja może być wyświetlana w interfejsie użytkownika. Jednostka pracy odmówiłaby później utrzymania nieważnych obiektów, gdyby zaszła tak daleko.

Próbuję uzasadnić oddzielenie, niezmienność i tak dalej w dużym stopniu. Mówię tylko, że w niektórych kontekstach wymagana jest prostsza architektura.

+0

+1 Jestem sturggling z koncepcją z dokładnych powodów wymienionych na liście. Jestem zaznajomiony z Silverlight MVVM i starym doświadczeniem z niektórymi CSLA. Chciałbym zobaczyć komentarze innych programistów .NET. – BuddyJoe

10

Setery nie przenoszą żadnych informacji semantycznych.

np.

blogpost.PublishDate = DateTime.Now; 

Czy to oznacza, że ​​post został opublikowany? Czy tylko ustawiono datę publikacji?

Rozważmy:

blogpost.Publish(); 

To wyraźnie pokazuje, że zamiar blogpost powinny być publikowane.

Ponadto setery mogą łamać niezmienniki obiektu. Na przykład, jeśli mamy obiekt "Trójkąt", niezmiennikiem powinno być to, że suma wszystkich kątów powinna wynosić 180 stopni.

Assert.AreEqual (t.A + t.B + t.C ,180); 

Teraz, jeśli mamy ustawiające, możemy łatwo złamać niezmienników:

t.A = 0; 
t.B = 0; 
t.C = 0; 

Więc mamy trójkąt gdzie suma wszystkich kątów wynoszą 0 ... Czy to naprawdę trójkątem? Powiedziałbym nie.

Wymiana ustawiaczy z metod może zmusić nas do zachowania niezmienników:

t.SetAngles(0,0,0); //should fail 

Takie połączenie powinno wyjątek informacją, że to powoduje nieprawidłowy stan dla swojej jednostki.

Otrzymujesz semantykę i niezmienniki metodami zamiast setterów.

+0

tak długo, jak nie łamie się niezmiennika za pomocą seterów, jest OK? Chodzi mi o to, że jeśli użytkownik edytuje informacje o kliencie (imię, drugie imię, nazwisko) i jedyny niezmiennik to żaden z nich nie może być pusty lub NULL, to używanie setera dla każdego pola jest dopuszczalne? – Songo

-1

Mogę być tutaj, ale uważam, że setery powinny być używane do ustawienia, w przeciwieństwie do metod setera. Mam kilka powodów.

a) Ma to sens w .Net. Każdy programista wie o właściwościach. W ten sposób ustawiasz obiekty na obiekcie. Po co od tego odrywać obiekty domeny?

b) Setery mogą mieć kod. Przed 3,5 wierzę, ustawienie obiekt składał się z zmiennej wewnętrznej i podpisem własności

private object _someProperty = null; 
public object SomeProperty{ 
    get { return _someProperty; } 
    set { _someProperty = value; } 
} 

Jest to bardzo proste i imo elegancki umieścić walidacji w seter. W IL, gettery i setery są i tak przekształcane w metody. Dlaczego kopiować kod?

W przedstawionym powyżej przykładzie metody Publish() całkowicie się zgadzam. Są chwile, kiedy nie chcemy, aby inni deweloperzy ustawiali własność. To powinno być obsługiwane za pomocą metody. Czy jednak ma sens metoda ustawiająca dla każdej właściwości, gdy .Net zapewnia już wszystkie funkcje, których potrzebujemy w deklaracji właściwości?

Jeśli masz obiekt Person, po co tworzyć metody dla każdej właściwości, jeśli nie ma powodu?