0

Scala では、以下の依存性注入の方法を使用することに問題はありますか。

// Define an interface
trait FileStorage {
  def readFile(filename:String):OutputStream
}

// And an implementation
class S3FileStorage extends FileStorage {
    def readFile(filename:String):OutputStream = ???
}

// Define our service as a trait with abstract fields that need to be
// injected in order to construct. All implementation details go here.
trait FileHTTPServer {
  val fileStorage:FileStorage

  def fetchFile( session:Session, filename:String ) = ???
}

今、私たちは物事を配線します

// Wire up a concrete file service that we actually use in code
// No implementation details should go here, we're simply wiring up a FileHttpServerl
// An entire project could be wired up this way in a central location if desired.
object S3FileHttpServer extends FileHTTPServer {
    val fileStorage = new S3FileStorage
}

// We could also do this anonymously
val myHttpServer = new FileHttpServer {
    val fileStorage = new S3FileStorage
} 

// Or create a mocked version for testing
val mockedHttpServer = new FileHttpServer {
    val fileStorage = mock[FileStorage]
}

明らかに、Cake パターンは (特に自己型に関して) より高い柔軟性を提供しますが、より単純なユース ケースでは、定型文がはるかに少なくなりますが、コンパイル時のチェックと明確で明確なインターフェイスが提供されます。

4

1 に答える 1

1

はい、これは絶対に良いアプローチです。はい、コンストラクター注入を使用できる場合もありますが、それも問題ありません。ただし、コンストラクター注入では、依存関係を手動で伝播する必要がありますが、ケーキ パターンでは、依存関係は自己型注釈を介して自動的に伝播されます。したがって、大規模なプロジェクトの場合、特に建設現場 (すべてのオブジェクトを作成し、それらの間の依存関係を設定する場所) では、コンストラクター インジェクションはケーキ パターンよりも多くのボイラープレートにつながります。

しかし、あなたが提示したのは本格的なケーキのパターンではありません。実際のケーキ パターンでは、ビジネス ロジック クラス、いわゆるコンポーネントの周りに追加のレイヤーがあり、ロジック クラスを直接接続するのではなく、代わりにコンポーネントを接続します。

trait FileStorageComponent {
  def fileStorage: FileStorage

  trait FileStorage {
    def readFile(filename: String): OutputStream
  }
}

trait S3FileStorageComponent extends FileStorageComponent {
  val fileStorage = new S3FileStorage

  class S3FileStorage extends FileStorage {
    def readFile(filename: String): OutputStream = ???
  }
}

trait FileHttpServerComponent {
  self: FileStorageComponent =>

  val fileHttpServer = new FileHttpServer

  class FileHttpServer {
    def fetchFile(session: Session, filename: String) = ???
  }
}

// Wiring

object S3FileHttpServer extends FileHttpServerComponent with S3FileStorageComponent

// Anonymous

val server = new FileHttpServerComponent with S3FileStorageComponent

// Mocking

object TestFileHttpServer extends FileHttpServerComponent with FileStorageComponent {
  val fileStorage = mock[FileStorage]
}

このアプローチでは、trait 定義により多くのボイラープレートがありますが、その見返りとして、使用場所での柔軟性が向上し、非常に明確な依存関係管理が得られます。たとえば、私のプロジェクトの 1 つのプログラム エントリ ポイントは次のようになります。

object Main
  extends MainUI
  with DefaultActorsManagerComponent
  with DefaultPreferencesAccessComponent
  with DefaultModelComponent
  with DefaultMainWindowViewComponent
  with DefaultMainWindowControllerComponent
  with MainWindowReporterComponent
  with DefaultClientActorComponent
  with DefaultResponseParserActorComponent
  with DefaultArchiverActorComponent
  with DefaultMainWindowAccessActorComponent
  with DefaultUrlParserComponent
  with DefaultListenerActorComponent
  with DefaultXmlPrettifierComponent

すべての主要なプログラム コンポーネントが 1 か所にあります。かなりきちんとしたIMO。

于 2013-10-07T08:30:53.797 に答える