ビルダーは、1つのクラスではなく、一連のクラスを持つDSLのように考える必要があります。たとえビルダーパターンを貼っていても。ビルダークラスが現在アクティブな文法の変更のコンテキスト。
ユーザーは、ビルダーの感触を保ち、HTTP(デフォルト)とHTTPSの間で選択した場合にのみ、フォークビルダークラスという単純なオプションを指定して起動するのをしてみましょう:
私たちが作るために使用します迅速な拡張機能きれい流暢な方法:
fun <T: Any> T.fluently(func:()->Unit): T {
return this.apply { func() }
}
今メインコード:
// our main builder class
class HttpServerBuilder internal constructor() {
private var host: String = "localhost"
private var port: Int? = null
private var context: String = "/"
fun withHost(host: String) = fluently { this.host = host }
fun withPort(port: Int) = fluently { this.port = port }
fun withContext(context: String) = fluently { this.context = context }
// !!! transition to another internal builder class !!!
fun withSsl(): HttpsServerBuilder = HttpsServerBuilder()
fun build(): Server = Server(host, port ?: 80, context, false, null, null)
// our context shift builder class when configuring HTTPS server
inner class HttpsServerBuilder internal constructor() {
private var keyStore: String? = null
private var keyStorePassword: String? = null
fun withKeystore(keystore: String) = fluently { this.keyStore = keyStore }
fun withKeystorePassword(password: String) = fluently { this.keyStorePassword = password }
// manually delegate to the outer class for withPort and withContext
fun withPort(port: Int) = fluently { [email protected] = port }
fun withContext(context: String) = fluently { [email protected] = context }
// different validation for HTTPS server than HTTP
fun build(): Server {
return Server(host, port ?: 443, context, true,
keyStore ?: throw IllegalArgumentException("keyStore must be present for SSL"),
keyStorePassword ?: throw IllegalArgumentException("KeyStore password is required for SSL"))
}
}
}
とヘルパーのfu私たちは、ビルダーのいくつかの値を操作し続け、必要に応じて独自の値を運ぶとユニークなことができ、内部クラスを使用するこのモデルでは
fun serverBuilder(): HttpServerBuilder {
return HttpServerBuilder()
}
:上記の質問に、あなたのコードに合わせて、ビルダーをオフに開始するnction最終的なbuild()
の検証。ビルダーはwithSsl()
呼び出しでこの内部クラスにユーザーのコンテキストを移行します。
したがって、ユーザーは、「道路内の分岐」で許可されるオプションのみに制限されます。 withSsl()
の前にwithKeystore()
を呼び出すことはできません。あなたはあなたが望むエラーがあります。
ここで問題となるのは、引き続き動作させたい設定を内部クラスから外部クラスに手動で委譲する必要があることです。これが多数だった場合、これは迷惑になる可能性があります。代わりに、インターフェイスに共通の設定を行い、class delegationを使用して、ネストされたクラスから外部クラスに委譲することができます。外側には、このインタフェースを介して、ネストされたクラスの委譲で
private interface HttpServerBuilderCommon {
var host: String
var port: Int?
var context: String
fun withHost(host: String): HttpServerBuilderCommon
fun withPort(port: Int): HttpServerBuilderCommon
fun withContext(context: String): HttpServerBuilderCommon
fun build(): Server
}
:だからここ
は共通のインタフェースを使用するリファクタリングビルダーです
class HttpServerBuilder internal constructor(): HttpServerBuilderCommon {
override var host: String = "localhost"
override var port: Int? = null
override var context: String = "/"
override fun withHost(host: String) = fluently { this.host = host }
override fun withPort(port: Int) = fluently { this.port = port }
override fun withContext(context: String) = fluently { this.context = context }
// transition context to HTTPS builder
fun withSsl(): HttpsServerBuilder = HttpsServerBuilder(this)
override fun build(): Server = Server(host, port ?: 80, context, false, null, null)
// nested instead of inner class that delegates to outer any common settings
class HttpsServerBuilder internal constructor (delegate: HttpServerBuilder): HttpServerBuilderCommon by delegate {
private var keyStore: String? = null
private var keyStorePassword: String? = null
fun withKeystore(keystore: String) = fluently { this.keyStore = keyStore }
fun withKeystorePassword(password: String) = fluently { this.keyStorePassword = password }
override fun build(): Server {
return Server(host, port ?: 443, context, true,
keyStore ?: throw IllegalArgumentException("keyStore must be present for SSL"),
keyStorePassword ?: throw IllegalArgumentException("KeyStore password is required for SSL"))
}
}
}
私たちは、同じ正味の効果で終わります。追加の分岐がある場合は、引き続き継承のインターフェイスを開き、各レベルの新しい子孫の各レベルの設定を追加できます。
設定の数が少ないため、最初の例は小さくなる可能性がありますが、はるかに多くの設定がある場合は逆になる可能性がありますが、道路にはさらに設定が増えていますインターフェイス+委任モデルは多くのコードを保存することはできませんが、委任する特定のメソッドを忘れたり、予想よりも異なるメソッドシグネチャを忘れる可能性が低くなります。
2つのモデルの主観的な違いです。代わりに、DSLスタイルビルダーの使用について
:
あなたの代わりにDSLモデルを使用した場合、例えば:
Server {
host = "localhost"
port = 80
context = "/secured"
ssl {
keystore = "mystore.kstore"
password = "[email protected]!"
}
}
あなたが設定を委任を心配する必要がないという利点を持っていますDSL内では部分ビルダーのスコープに入り込んだり終了したりする傾向があるため、すでにコンテキストがシフトしているため、メソッド呼び出しの順番が異なります。ここでの問題は、DSLの各部分に暗黙的な受信者を使用しているため、スコープが外部オブジェクトから内部オブジェクトに流出する可能性があることです。これは可能です:
Server {
host = "localhost"
port = 80
context = "/secured"
ssl {
keystore = "mystore.kstore"
password = "[email protected]!"
ssl {
keystore = "mystore.kstore"
password = "[email protected]!"
ssl {
keystore = "mystore.kstore"
password = "[email protected]!"
port = 443
host = "0.0.0.0"
}
}
}
}
HTTPプロパティの一部がHTTPSスコープに流出するのを防ぐことはできません。これはKT-11551で修正されています。詳細はこちらを参照してください。Kotlin - Restrict extension method scope
"HTTPプロパティの一部がHTTPSスコープに流出するのを防ぐことはできません。 - 誰もが参照できるように、これはKotlin 1.1で修正されています( '@ DslMarker'アノテーションを使います)。 –