2015-12-24 17 views
5

Wśród kilku innych typów związanych z państwem, mam następujące typy rekordów w moim kodu:Kopiowanie właściwości między rejestrami

type SubmittedSuggestionData = { 
    SuggestionId : Guid 
    SuggestionText : string 
    Originator : User 
    ParentCategory : Category 
    SubmissionDate : DateTime 
} 

type ApprovedSuggestionData = { 
    SuggestionId : Guid 
    SuggestionText : string 
    Originator : User 
    ParentCategory : Category 
    SubmissionDate : DateTime 
    ApprovalDate : DateTime 
} 

Te następnie podawany do następujących:

type Suggestion = 
    | SubmittedSuggestion of SubmittedSuggestionData 
    | ApprovedSuggestion of ApprovedSuggestionData 

To daje mi umiejętność pracy ze wzorcem w stylu maszyny państwowej w celu prowadzenia określonej logiki biznesowej w zależności od państwa. (To podejście zostało zaczerpnięte z: http://fsharpforfunandprofit.com/posts/designing-with-types-representing-states/)

Mam funkcję, która w swej najprostszej postaci, zmienia SubmittedSuggestion do ApprovedSuggestion:

let ApproveSuggestion suggestion = 
    match suggestion with 
    | SubmittedSuggestion suggestion -> ApprovedSuggestion {} 

Funkcja ta jest niekompletne w momencie jak co mam zmaga aby zrozumieć, kiedy Propozycja zmienia się z Przesłanej na Zatwierdzoną, w jaki sposób kopiujesz właściwości z przekazanego suggestion do nowo utworzonego ApprovedSuggestion, jednocześnie wypełniając nową właściwość ApprovalDate?

myślę, że to działa, jeśli zrobiłem coś takiego:

let ApproveSuggestion suggestion = 
    match suggestion with 
    | SubmittedSuggestion {SuggestionId = suggestionId; SuggestionText = suggestionText; Originator = originator; ParentCategory = category; SubmissionDate = submissionDate} -> 
     ApprovedSuggestion {SuggestionId = suggestionId; SuggestionText = suggestionText; Originator = originator; ParentCategory = category; SubmissionDate = submissionDate; ApprovalDate = DateTime.UtcNow} 

ale wygląda dość przerażające dla mnie.

Czy istnieje bardziej przejrzysty, bardziej zwięzły sposób uzyskania tego samego wyniku? Próbowałem użyć słowa kluczowego with, ale nie skompilowałem.

Dzięki

Odpowiedz

6

Jeśli istnieje duże podobieństwo między typami, często jest to dobry pomysł, aby myśleć o rozkładzie. Na przykład, rodzaje mógłby wyglądać następująco:

type SuggestionData = { 
    SuggestionId : Guid 
    SuggestionText : string 
    Originator : User 
    ParentCategory : Category 
    SubmissionDate : DateTime 
} 

type ApprovedSuggestionData = { 
    Suggestion : SuggestionData 
    ApprovalDate : DateTime 
} 

W zależności od sposobu użytkowania i różnice między rodzajami, można również rozważyć mieć zatwierdzonego typu tylko w dyskryminowanej unii, omijając drugi typ łącznie:

type Suggestion = 
    | SubmittedSuggestion of SuggestionData 
    | ApprovedSuggestion of SuggestionData * approvalDate : DateTime 

Powszechnym argumentem przeciwko takiej dekompozycji jest to, że dostęp do elementów typów głębiej w hierarchii staje się bardziej szczegółowy, np. approvedSuggestionData.Suggestion.Originator. Chociaż jest to prawdą, właściwości mogą być używane do przekazywania często używanych elementów składowych, jeśli gadatliwość staje się denerwująca, a wszelkie wady należy porównać z zaletami: w typach jest mniej duplikacji kodu, a każda operacja, którą oferują bardziej szczegółowe typy, może być udostępnione z typu złożonego.

Możliwość łatwego skonstruowania zatwierdzonej sugestii z niezatwierdzonej sugestii i daty zatwierdzenia to jeden przypadek, w którym jest to przydatne. Ale może być więcej: powiedzmy, że istnieje operacja, która sprawdza użytkowników i kategorie wszystkich sugestii, zatwierdzonych lub nie. Jeśli typy przechowujące elementy Originator i ParentCategory dla zatwierdzonych i niezatwierdzonych sugestii nie są powiązane, kod, który je otrzyma, musi zostać zduplikowany.(Może też trzeba utworzyć wspólny interfejs.)

+0

Hmmm, podoba mi się wygląd tego @Vandroiy. Moje stany nie zmienią się drastycznie, więc może to być dla mnie prawidłowe rozwiązanie. Dam ci szansę i dam ci znać :) – Stu1986C

4

chciałbym zmienić swoją sugestię

type SubmittedSuggestionData = { 
    SuggestionId : Guid 
    SuggestionText : string 
    Originator : User 
    ParentCategory : Category 
    SubmissionDate : DateTime 
    ApprovalDate : DateTime option 
} 

a następnie zatwierdzenie staje

let approve t = {t with AprovalDate =Some(System.DateTime.Now)} 
+1

Dzięki @John Palmer. Przyszło mi do głowy, aby to zrobić, ale jestem lekko nienawidzony, ponieważ wielokrotnie używałam wzorca stanu w języku C#, a rzeczą, która sprawiła, że ​​wzdrygnęłam się na moje poprzednie implementacje, jest obecność niepotrzebnych właściwości w określonym stanie. Idealnie chciałbym mieć czysty model domeny, w którym tylko odpowiednie właściwości znajdują się w odpowiednich typach rekordów. :) – Stu1986C

+0

Ta właściwość nie jest naprawdę "nieużywana", wskazuje ona, czy sugestia została zatwierdzona czy nie. –

4

Obie sugestie oferowane przez @Vandroiy i @ John Palmer są dobre, ale dla kompletności chciałbym przedstawić również trzecią perspektywę.

O ile mi wiadomo, nie ma konstrukcji językowej, która spowoduje, że kopiowanie wartości pomiędzy "podobnymi" typami będzie zwięzłe. Powodem tego jest to, że te typy są różne. Ich podobieństwo jest przypadkowe, ale widziane z systemu typów, są to zupełnie różne typy. Jeśli patrzysz na nie "matematycznie", są to po prostu typy A i B.

W takich przypadkach często bym po prostu zacisnąć zęby i dodać funkcję tłumaczenie że przekłada wartości jednego typu do wartości innego typu:

let approve (suggestion : SubmittedSuggestionData) = { 
    SuggestionId = suggestion.SuggestionId 
    SuggestionText = suggestion.SuggestionText 
    Originator = suggestion.Originator 
    ParentCategory = suggestion.ParentCategory 
    SubmissionDate = suggestion.SubmissionDate 
    ApprovalDate = DateTime.UtcNow } 

Chociaż wydaje się trochę rozwlekły, nadal zachowujesz go SUCHA, ponieważ takie kopiowanie wartości jest ograniczone do tej jednej funkcji.

Ponadto takiej funkcji często można nadać dobrą nazwę, co oznacza, że ​​funkcja staje się częścią Twojego Modelu Domeny.

Powiązane problemy