2015-10-23 10 views
8

以下の簡単な例を考えてみましょう。繰り返し可能なパターンマッチング

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

let printInstrumentName instrument = 
    match instrument with 
    | Check number-> printfn "check" 
    | CreditCard (number, expirationDate) -> printfn "card" 

let printRequisites instrument = 
    match instrument with 
    | Check number -> printfn "check %s" number 
    | CreditCard (number, expirationDate) -> printfn "card %s %A" number expirationDate 

同じパターンマッチングロジックが2つの機能で繰り返されることがわかります。クラスを

PrintInstrumentNamePrintRequisites

をしてから実装する - 決済手段ごとに1つずつ:私はOOPを使用する場合、私は、インターフェイスIPaymentInstrumentを作成する2つの操作を定義します。いくつかの外部条件に応じて機器をインスタンス化するには、例えば工場パターン(PaymentInstrumentFactory)を使用します。

新しい支払い手段を追加する必要がある場合は、IPaymentInstrumentインターフェイスを実装し、ファクトリインスタンス化ロジックを更新する新しいクラスを追加するだけです。これらのクラスを使用する他のコードはそのままです。

機能的なアプローチを使用すると、をそれぞれ更新する必要があります。このタイプのパターンマッチングが存在する機能です。

PaymentInstrumentタイプを使用すると多くの機能が問題になる場合があります。

機能的アプローチを使用してこの問題を解決するにはどうすればよいですか?

+0

これらのメンバ関数を少しきれいにすることができますが、繰り返しの一致があります。 –

+7

[式の問題](http://c2.com/cgi/wiki?ExpressionProblem)の片側ですが、OOPアプローチはもう片方。あなたは本当にパターンマッチを消すことはできません。 –

+0

私は@Patrykíwiekのコメントに同意します。パターンマッチの力は、OOPメカニズムの「ノイズ」を排除することです。この場合はほとんど役に立たないことです。現実のプロジェクトでは、各ブランチの内部のボディは、開発とテストを簡素化する別々の機能の中にあります。 OTOH、ある日に 'PaymentInstrument'が大きく成長し、それが異なる人々によって開発されると、既存の別々の処理機能を保持しながら、OOPアプローチに素早く切り替えることができます。 – bytebuster

答えて

1

Mark Seemannの答えを使用して、私はそのようなデザインの決定に来ました。あなたがgetTypeOperations機能を見ることができるように

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 } 

と利用

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

は、工場出荷時のパターンの役割を実行します。 1束の関数を集約するには、単純なレコードタイプを使用します(しかし、F#デザインガイドラインに従って、http://fsharp.org/specs/component-design-guidelines/インターフェイスがそのような決定より優先されますが、私はそれをより良く理解するために機能的アプローチで行うことに興味があります)。

パターンマッチングは今のところ1つの場所にあります。

17

PatrykÜwiekがthe comment aboveを指摘しているので、Expression Problemが発生しているため、いずれかを選択する必要があります。

より多くのデータ型を追加する能力が、より多くの動作を簡単に追加できる能力よりも重要な場合は、インタフェースベースのアプローチがより適切な場合があります。 F#ので

、あなたはまだ、オブジェクト指向のインタフェースを定義することができます。

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

はまた、このインタフェースを実装するクラスを作成することができます。ここでCheckだ、と私は読者への課題としてCreditCardを残しておきます:あなたはオブジェクト指向の道を行くしたい場合、あなたはInterface Segregation Principleそのうちの1つは、SOLID principlesを検討を開始する必要があり、

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

しかし、 (ISP)。あなたは、積極的にyou'll ultimately end up with interfaces with a single member, like thisをISPの適用を開始すると:

type IPaymentInstrumentNamePrinter = 
    abstract member PrintInstrumentName : unit -> unit 

type IPaymentInstrumentRequisitePrinter = 
    abstract member PrintRequisites : unit -> unit 

あなたはまだクラスでこれを実装することができます

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

これは、今、ちょっと滑稽に見えるし始めています。 F#を使用している場合は、なぜ単一のメンバーとのインターフェイスを定義するのに苦労しますか?

代わりに、機能を使用してください。

両方の希望するインターフェイスメンバーは、タイプがunit -> unitです(ただし、特に「機能的」なタイプではありません)。そのような関数を渡したり、インターフェイスのオーバーヘッドを払ったりしないでください。

OPのprintInstrumentNameおよびprintRequisitesの機能では、すでに目的の動作があります。

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

関数型プログラミングでは、我々はオブジェクトこれらの事を呼び出すことはありませんが、むしろ:あなたはポリモーフィック "オブジェクトの希望のインターフェース「を実装する」ことにそれらを有効にしたい場合は、それらの上に閉じることができます閉鎖。振る舞いがのデータではなく、データがの動作です。

+0

お返事ありがとうございます。それを使用して私はこの決定に至りましたhttp://stackoverflow.com/a/33319572/446115 – eternity

関連する問題