2012-06-28 9 views
31

Scalaで正しく機能する設定可能なオブジェクトを作成するにはどうすればよいですか?私はReaderモナドのTony Morrisのビデオを見てきましたが、私はまだドットを接続することができません。Scalaの構成データ - Readerモナドを使用する必要がありますか?

私はClientオブジェクトのハードコーディングされたリストを持っている:私はClient.clientsプロパティからそれを読んですると、データベースからファイルまたはいずれかの柔軟性と、実行時に決定することにしたい

class Client(name : String, age : Int){ /* etc */} 

object Client{ 
    //Horrible! 
    val clients = List(Client("Bob", 20), Client("Cindy", 30)) 
} 

を。 Javaの世界では、私は、インターフェイスを定義するソースの2種類を実装し、クラス変数を割り当てるためにDIを使用したい:

trait ConfigSource { 
    def clients : List[Client] 
} 

object ConfigFileSource extends ConfigSource { 
    override def clients = buildClientsFromProperties(Properties("clients.properties")) 
    //...etc, read properties files 
} 

object DatabaseSource extends ConfigSource { /* etc */ } 

object Client { 
    @Resource("configuration_source") 
    private var config : ConfigSource = _ //Inject it at runtime 

    val clients = config.clients 
} 

これは私にはかなりきれいな解決策(ない多くのコードのように思えます、明確な意図)が、そのvar私はそれ once-and-only-onceの)注入されます知っているので、は、OTOH、それは私に本当に面倒なようではありません(飛び出しありません。

この状況では、Readerモナドはどのように見えますか?私が5のように私に説明すると、その利点は何ですか?

+1

'val's *は反射を使って変更することができるので、あなたの依存性注入ライブラリは" valを注入 "する可能性があります – gerferra

+2

@gerferra varがある場合、リフレクションによって修正されたvalのポイントは何ですか? –

+0

なぜ 'Client'を引数を持つクラスにしないと、configは' Client'のインスタンスに渡すことができますか? –

答えて

45

あなたのアプローチとReaderのアプローチの単純な、表面的な違いから始めましょう。これは、configにどこかでハングアップする必要がなくなったからです。私が今までにいくつかの機能のためのConfigSourceが必要な場合は、リスト内のクライアント番目N」を取得する機能は、私はそれを宣言することができると言う、今

type Configured[A] = ConfigSource => A 

:あなたは、次の漠然と賢い型シノニムを定義しましょう機能「構成された」など:

def nthClient(n: Int): Configured[Client] = { 
    config => config.clients(n) 
} 

だから我々は、本質的に、空中から、我々は1を必要とする任意の時間をconfigを引っ張っています!依存症の注射のような臭いですよね?今度は、(それらが存在すると仮定して)私たちは、リスト内の第一、第二、第三の顧客の年齢をしたいとしましょう:このため

def ages: Configured[(Int, Int, Int)] = 
    for { 
    a0 <- nthClient(0) 
    a1 <- nthClient(1) 
    a2 <- nthClient(2) 
    } yield (a0.age, a1.age, a2.age) 

、もちろん、あなたがmapflatMapのいくつかの適切な定義が必要です。私はここには入っていませんが、Scalaz(またはRúnar's awesome NEScala talk、または既に見たTony's)が必要とするすべてを提供すると言います。

ここで重要な点は、ConfigSource依存性とそのいわゆる注入がほとんど隠されていることです。ここでは、agesがではなくConfigured[(Int, Int, Int)]であることがわかります。 configを明示的に参照する必要はありませんでした。余談として

、これは私はほとんど常にモナドについて考えるのが好きな方法である:彼らはが明示的に効果を宣言しながら、それは、あなたのコードの流れを汚染していない彼らの影響を隠す型シグネチャで言い換えれば、関数の戻り値の型で "やあ、この関数はの効果X"を扱っており、これ以上のことは繰り返さないでください。

この例では、もちろん、いくつかの固定環境から読み取ることです。おなじみのモナド効果には、エラー処理が含まれています。Optionはエラー処理ロジックを隠し、メソッドの型でエラーが発生する可能性があると言うことができます。あるいは、読み方の逆の一種である、Writerモナドは、型システムで明示的にその存在を明示している間に、書いているものを隠します。

はついに、私たちは通常、(例えばXMLファイルのように、どこかのコントロールのいつもの流れの外)DIフレームワークをブートストラップする必要があるのと同様に、我々はまた、この好奇心モナドをブートストラップする必要があります。それは非常に単純されて終わる

def run: Configured[Unit] = // ... 

:確かに、私たちのような、我々のコードにいくつかの論理的なエントリーポイントがあるでしょうConfigured[A]が機能ConfigSource => Aのためだけのタイプの同義語であることから、私たちはそのに関数を適用することができます「環境」:

run(ConfigFileSource) 
// or 
run(DatabaseSource) 

Ta-da!したがって、従来のJavaスタイルのDIアプローチとは対照的に、ここでは「魔法」は発生しません。唯一の魔法は、われわれのConfiguredタイプの定義とそれがモナドとして動作する方法にカプセル化されています。最も重要なことは、タイプシステムは私たちを正直に保ちますについて "realm"依存性注入が発生しています:タイプがConfigured[...]のものはDIワールドにあり何もありません。私たちは古い学校のDIでこれを取得しません。すべてがである可能性があります。そのため、コードのどの部分がDIフレームワーク外で再利用するのが安全か分かりません(たとえば、単体テスト、または他のプロジェクト全体で)。


更新:私は、より詳細にReaderを説明blog postを書きました。

+0

ちょうど私に起こった、私も言う必要があります:** "設定可能なオブジェクト"を作ることについて心配しないでください。**設定可能なオブジェクトは、本当に、コンストラクタパラメータを持つものです。これらのパラメータはどこから得られますか?コンストラクタの呼び出し側は、もちろん(もし私があなたにそれを試してもらうならば)、それらを読者(この場合、 'Configured [...]'環境)から取得します。それはすべてあなたのオブジェクトの勇気ではなく、他の関数を呼び出す関数についてです。 – mergeconflict

+0

うーん...ですから、最終的には 'run(ConfigFileSource)'または 'run(DatabaseSource)'を選択したfnまで 'Configured [PriorReturnedType]'を返すようにfnシグネチャをすべてリファクタリングする必要がありますか? argsとして 'ConfigSource'を渡すのがなぜ優れていますか?そして、私は「ここにはどのような「魔法」も起こっていない」ということに従わない。コマンドライン引数や環境変数、あるいはDIの "魔法"によって 'run(ConfigFileSource)'や 'run(DatabaseSource)'を選択する必要がありますか? –

+1

re: "最終的には、私たちはまだすべてのfnシグネチャをリファクタリングする必要があります..." - それらのうちのいくつかは、すべてではありません。グローバル設定に依存するものは 'Configured [...]'シグニチャーが必要ですが、あなたの純粋な関数はそうではありません。再び、これら2つの世界を明確に識別できるようにすることは良いことです。 – mergeconflict

関連する問題