我需要在我的程序中存储/写入有关不同和多种数据支持的信息(H2、SQLite、NEO4J、文本等),我一一创建导出器,因此接口(interface)需要易于扩展。
我制作模拟程序,所以我需要在每一步存储信息(需要数据库对象,以及要存储的各种数据)。
我的第一个想法是使用带有泛型方法 initializeWriter()
、stepWriter()
、closeWriter()
的依赖注入(inject)模式,如下所示:
trait OutputWriter { Simulation =>
def path:String
def initializeWriter()
def stepWriter()
def closeWriter()
}
需要实现 writer 的用户扩展了此特征,并覆盖方法。 通常'database val'包含由initializeWriter初始化后的DB对象。 数据对象包含我想要写入的所有数据。
我在OutputWriter和Simulation之间存在依赖关系,因为实际上我需要从模拟访问对象和函数来写入最终结果。我认为这不是一个很好的解决方案,并且考虑到 @x3ro 的答案,这是一个更好的解决方案,可以删除这些依赖项,为作者提供一些更好的通用数据对象。
我的问题是在这种情况下,如果我需要添加一些复杂的信息以将此数据正确写入数据库,我可以在哪里放置包含例如 sqlquery 或其他特定编写器命令的特定代码?
所以,此时我的问题是:
1 - 我创建一个数据对象,其中包含要在模拟的每个步骤写入的数据。该数据对象的结构是相同的,只是结果值发生了移动。
2 - 任何输出或多个输出编写器,其中包含在输出上正确写入的所有方法:TextWriter、SQLITEWriter 等。
3 - 代码的特定部分,它在 (1) -> (2) 之间建立关系。将 double 的 indexedSeq 写入文本文件没有问题,但写入 SQL 数据库时,我需要向编写器提供表结构、插入数据的查询等。
我的第一个想法是将此代码设置到 Writer 方法的 stepWriter() 中,但也许有更好的解决方案,因为我认为这里我打破了 writer 的通用性
object DB
object MyData
trait DBWriter extends OutputWriter {
val database:DB = DB
def initializeWriter() = { ... }
def stepWriter(dataToWrite:MyData) = { ... }
}
之后,如果我需要导出我的模拟,我会像这样添加优秀的作家:
new Simulation (...) with DBWriter {
override def path = "my-path-for-db"
initializeWriter()
// computation loop
(0 until 50){ var result:MyData= computeData() ; stepWriter(result) }
closeWriter()
}
您是否经常使用其他更强大或更灵活的模式(在文献中或根据您的经验)来做到这一点?
非常感谢您的体验返回, SR。
最佳答案
我认为您在示例中呈现的内容不属于“依赖注入(inject)”(DI)类别。这是因为 DI 的目的是减少代码库中的依赖性,而您的类/特征实际上都是相互依赖的:
我建议您阅读this article on Dependency Injection如果您想了解更多信息。
可能的问题
对于您的示例,问题是您的模拟取决于 DBWriter
,并且您需要更改您的实现才能使用其他编写器。模拟应该只依赖于OutputWriter
特质。
另一个问题似乎是 stepWriter()
你的方法DBWriter
实现需要特定于数据库编写器的参数,因此不是通用的(并且根本不符合特征 OutputWriter
)。
第三个问题是您的 OutputWriter
特质实际上取决于模拟,对此我实在找不到原因。为了保留您的OutputWriter
尽可能通用和可重用,您不应该使其依赖于 Simulation
。您添加此依赖项的原因是什么?
我的方法
我将进行以下更改以改善您的依赖性情况。
使 OutputWriter 和 DBWriter 通用:您的 OutputWriter
实际上应该只是一个接口(interface),如下所示:
trait OutputWriter {
def initializeWriter()
def stepWriter(dataToWrite:Data)
def closeWriter()
}
使 DBWriter 成为一个实际的类:
class DBWriter(database:DB) extends OutputWriter {
def initializeWriter() { ... }
def stepWriter(dataToWrite:Data) { ... }
def closeWriter() { ... }
}
在实例化模拟时将编写器传递给模拟:
object Foo extends App {
val database:DB = ...
val writer:OutputWriter = new DBWriter(database)
new Simulation(..., writer)
}
像这样,您将能够简单地更改传递给 Simulation
的 writer。构造函数,甚至同时使用多个编写器!这还允许编写器通过外部配置文件进行配置(请参阅上面提到的有关依赖项注入(inject)的文章)。
解决编辑中的问题
1 - Data i want to write a each step of my simulation. Structure of this data is the same, only the result value move. For example the simulation step return the same indexedSeq[Double] which contain different values to write on output at each step.
2 - Any output or multiple output writer, which contain all the method to write correctly on the output : TextWriter, SQLITEWriter, etc.
3 - A specific part of code which make the relation between (1) -> (2). There is no problem to write an indexedSeq of double into text file, but into an SQL Database, i need to give to the writer structure of table, query to insert data, etc.
将 SQL 逻辑放入 DBWriter 中是完全可以的(然后我将其称为 SimulationSQLWriter
)。这当然使得实际的实现不那么通用,但这一定不是一件坏事。始终使您的代码尽可能通用,但根据需要尽可能具体,这样您就不必添加大量复杂性来使代码通用,而您根本不需要通用。
始终在时间、精力和 yield 之间保持合理的比例!
现在来看一个 SimulationSQLWriter
的具体例子:
class SimulationSQLWriter(database:DB, table:String) extends OutputWriter {
def initialize() { ... }
def stepWriter(dataToWrite:Data) {
val preparedData = prepareDataForInsertion(dataToWrite)
insertIntoDatabase(preparedData)
}
def closeWriter() { ... }
def prepareDataForInsertion(dataToWrite:Data) = { ... }
def insertIntoDatabase(preparedData:<whatever type you need>) = { ... }
}
prepareDataForInsertion
例如,可以准备给定的数据并将其放入将 SQL 字段名称映射到值的映射中,或者此类映射元素的列表中。然后可以将该结果放入 insertIntoDatabase
,根据传递给构造函数的参数将其插入到 database
中表table
.
关于scala - 添加和扩展通用编写器特征以进行逐步数据存储的最佳方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/11691131/