2015-10-23 16 views
8

Betrachten Sie das folgende einfache Beispiel.Wiederholbare Mustererkennung

Wie Sie sehen können, wird die gleiche Mustervergleichslogik in zwei Funktionen wiederholt. Wenn ich OOP verwenden würde würde ich schaffen Schnittstelle IPaymentInstrument, definieren zwei Operationen:

PrintInstrumentName und PrintRequisites

und dann Klassen implementieren - eine pro Zahlungsinstrument. Um das Instrument abhängig von einigen externen Bedingungen zu instanziieren, würde ich (zum Beispiel) das Fabrikmuster() verwenden.

Wenn ich ein neues Zahlungsinstrument hinzufügen müsste, muss ich nur eine neue Klasse hinzufügen, die die Schnittstelle IPaymentInstrument implementiert und die Factory-Instanziierungslogik aktualisiert. Anderer Code, der diese Klassen verwendet, bleibt so wie er ist.

Aber wenn ich den funktionalen Ansatz verwende, sollte ich jede Funktion aktualisieren, wo Mustererkennung für diesen Typ existiert.

Wenn es viele Funktionen gibt, die PaymentInstrument verwenden, wird das ein Problem sein.

Wie kann dieses Problem mit einem funktionalen Ansatz behoben werden?

+0

Sie könnten diese Member-Funktionen machen, die etwas sauberer wären, aber immer noch die übereinstimmenden –

+7

Es ist eine Seite von [Ausdruck Problem] (http://c2.com/cgi/wiki?ExpressionProblem), während OOP-Ansatz ist das andere. Ergo, Sie können die Musterübereinstimmung wirklich nicht beseitigen. –

+0

Ich stimme dem Kommentar von @ PatrykĆwiek zu. Die Stärke der Musterübereinstimmung besteht darin, dass Sie das "Rauschen" der OOP-Mechanismen eliminieren, was in diesem Fall kaum nützlich ist. In realen Projekten sitzen die inneren Körper jeder Niederlassung in separaten Funktionen, was die Entwicklung und das Testen vereinfacht. OTOH, wenn an einem Tag "PaymentInstrument" groß wird und von verschiedenen Leuten entwickelt wird, dann können Sie schnell zum OOP-Ansatz wechseln, während Sie bestehende separate Verarbeitungsfunktionen beibehalten. – bytebuster

Antwort

1

Mit Mark Seemanns Antwort kam ich zu einer solchen Designentscheidung.

type PaymentInstrument = 
    | Check of string 
    | CreditCard of string * DateTime 

type Operations = 
    { 
     PrintInstrumentName : unit -> unit 
     PrintRequisites : unit -> unit 
    } 

let getTypeOperations instrument = 
    match instrument with 
    | Check number-> 
     let printCheckNumber() = printfn "check" 
     let printCheckRequisites() = printfn "check %s" number 
     { PrintInstrumentName = printCheckNumber; PrintRequisites = printCheckRequisites } 
    | CreditCard (number, expirationDate) -> 
     let printCardNumber() = printfn "card" 
     let printCardRequisites() = printfn "card %s %A" number expirationDate 
     { PrintInstrumentName = printCardNumber; PrintRequisites = printCardRequisites } 

Und Nutzung

let card = CreditCard("124", DateTime.Now) 
let operations = getTypeOperations card 
operations.PrintInstrumentName() 
operations.PrintRequisites() 

Wie Sie die getTypeOperations Funktion sehen führt die Rolle der Fabrik Muster. Um Funktionen in einem Bündel zu aggregieren benutze ich einen einfachen Datensatztyp (gemäß den F # Designrichtlinien http://fsharp.org/specs/component-design-guidelines/ werden Schnittstellen jedoch solchen Entscheidungen vorgezogen, aber ich bin daran interessiert, es in funktionalem Ansatz zu tun, um es jetzt besser zu verstehen).

Ich habe bekommen, was ich wollte - Pattern Matching ist nur an einem Ort für jetzt.

17

Wie Patryk Ćwiek in the comment above hervorhebt, treffen Sie auf die Expression Problem, also müssen Sie das eine oder andere wählen.

Wenn die Fähigkeit, weitere Datentypen hinzuzufügen, für Sie wichtiger ist als die Möglichkeit, einfach mehr Verhalten hinzuzufügen, ist ein interface-basierter Ansatz möglicherweise besser geeignet.

In F # können Sie noch objektorientierte Schnittstellen definieren:

type IPaymentInstrument = 
    abstract member PrintInstrumentName : unit -> unit 
    abstract member PrintRequisites : unit -> unit 

Sie auch Klassen, die diese Schnittstelle implementieren erstellen können. Hier ist Check, und ich werde CreditCard als Übung dem Leser überlassen:

type Check(number : string) = 
    interface IPaymentInstrument with 
     member this.PrintInstrumentName() = printfn "check" 
     member this.PrintRequisites() = printfn "check %s" number 

Doch wenn Sie die objektorientierte Art und Weise gehen wollen, sollten Sie die SOLID principles zu betrachten beginnen, eine davon ist die Interface Segregation Principle (ISP). Sobald Sie den ISP aggressiv, you'll ultimately end up with interfaces with a single member, like this starten Anwendung:

type IPaymentInstrumentNamePrinter = 
    abstract member PrintInstrumentName : unit -> unit 

type IPaymentInstrumentRequisitePrinter = 
    abstract member PrintRequisites : unit -> unit 

können Sie immer noch diese in Klassen implementieren:

type Check2(number : string) = 
    interface IPaymentInstrumentNamePrinter with 
     member this.PrintInstrumentName() = printfn "check" 
    interface IPaymentInstrumentRequisitePrinter with 
     member this.PrintRequisites() = printfn "check %s" number 

Dies beginnt sich jetzt etwas lächerlich zu sein scheinen. Wenn Sie F # verwenden, dann gehen Sie zu , um alle Probleme zu lösen, eine Schnittstelle mit einem einzelnen Mitglied zu definieren?

Warum nicht stattdessen Funktionen?

Beide gewünschten Schnittstellenelemente haben den Typ unit -> unit (nicht besonders "funktionell" aussehende Art), also warum nicht solche Funktionen herumreichen, und auf den Schnittstellenoverhead verzichten?

Mit den Funktionen printInstrumentName und printRequisites aus dem OP haben Sie bereits das gewünschte Verhalten. Wenn Sie sie in polymorphen ‚Objekte‘ machen wollen, dass ‚implementieren‘ die gewünschte Schnittstelle können Sie über sie schließen:

let myCheck = Check "1234" 
let myNamePrinter() = printInstrumentName myCheck 

In Functional Programming, wir diese Dinge nicht nennen Objekte, sondern Verschlüsse. Anstatt Daten mit Verhalten zu sein, sind sie Verhalten mit Daten.

+0

Danke für Ihre Antwort. Ich kam zu dieser Entscheidung http://stackoverflow.com/a/33319572/446115 – eternity