【发布时间】:2015-11-25 23:17:09
【问题描述】:
我正在寻找一种设计模式/方式来动态交换我的应用程序的(持久性)层(最好是在运行时)。
为什么?
我希望能够决定是否将某些数据保存到 XML 或基于“每个实例”的数据库。所以我可能会决定一个项目使用 XML 作为后端,而另一个项目使用数据库。我想在这里灵活,并且能够轻松添加另一个“驱动程序”,例如Json 之类的。
现在假设以下设置:
我们有一个控制器,我们想管理一些数据。我们可以在 SQL 和 XML 实现之间进行选择。
一种可能的(可行的)解决方案:
BasicController.scala
val myPersistenceLayer: PersistenceLayer = SQLPersistenceLayer
val apples: Seq[Apple] = myPersistenceLayer.getApples()
trait PersistenceLayer
{
def getApples(): Seq[Apple]
def getBananas(): Seq[Banana]
}
object SQLPersistenceLayer extends PersistenceLayer
{
override def getApples(): Seq[Apple] = {...}
override def getBananas(): Seq[Banana] = {...}
}
这是一个相当讨厌的解决方案,因为必须为每个新模型添加方法(想想水果!;))不仅在特征中,而且在每个实现中。我喜欢我的单一职责,所以我宁愿将其委托给模型,例如:
trait PersistenceLayer
{
def getAll(model: Model): Seq[Model] = { model.getAll() }
}
trait Model
{
def getAll(): Seq[Model]
}
package "SQL"
class Apple extends Model
{
def getAll(): Seq[Apple] = { // do some SQL magic here }
}
package "XML"
class Apple extends Model
{
def getAll(): Seq[Apple] = { // do some XML magic here instead }
}
现在最大的问题是,即使我实现了一个具体的 PersistenceLayer,如下所示:
object SQLPersistenceLayer extends PersistenceLayer {}
我如何告诉应用程序使用正确包的模型?
如果我使用 SQLPersistenceLayer:
val apples = myPersistenceLayer.get(Apple)
我需要导入正确的“Apple”类,这违背了整个目的,因为这样我就可以删除所有其他类,导入正确的类,然后在其上使用通用的“getAll()”方法。
所以我需要在多行更改实现,这是我想要避免的。
我想到了一些东西,比如给一个带有包名的字符串,比如
val package = "sql" 并在控制器中从正确的包中导入它,但这并不可行,也不容易实现,而且对于我显然缺少的东西来说,这是一个相当讨厌的 hack。
长话短说:我希望能够动态地切换包以用于我的持久性需求。在一些动态类型的语言中,我可以想出一个解决方案,但不是在 Scala 或任何静态类型的语言中,所以我想我在这里不知道某种设计模式
** 编辑 **
发生了一个想法(是的,有时它会发生;))现在我想知道这样的事情是否会导致我想要的:
namespace tld.app.persistence
trait PersistenceLayer
{
proteced val models: mutable.HashMap[String, Model] = new mutable.HashMap[String, Model]
def registerModel(key: String, model: Model): Unit =
{
models.remove(key)
models.put(key, model)
}
def get(model: String): Seq[Future[Model]] =
{
val m: Model = models.getOrElse(model, throw new Exception("No such model found!"))
m.get
}
}
trait Model
{
def get(): Seq[Future[Model]]
}
namespace tld.app.persistence.sql
object SQLPersistenceLayer extends PersistenceLayer
class Person extends Model
{
def get(): Seq[Future[Model]] =
{
// ... query the database
}
}
namespace tld.app.persistence.xml
object XMLPersistenceLayer extends PersistenceLayer
class Person extends Model
{
def get(): Seq[Future[Model]] =
{
// ... read in from the appropriate xml-file
}
}
object Settings
{
var persistenceLayer: PersistenceLayer = SQLPersistenceLayer // Default is SQLPersistenceLayer
}
Somewhere in the application:
Settings.persistenceLayer.get("person")
// Then a user-interaction happens
Settings.persistenceLayer = XMLPersistenceLayer
Settings.persistenceLayer.get("person")
persistenceLayer 通常保持不变,但用户可以决定更改它。一旦有时间,我将对其进行更深入的研究。但也许有人会立即发现这种方法存在问题。
【问题讨论】:
-
你的需求中
PersistenceLayer的接口看起来很奇怪,getAll的参数是什么? -
来自 sql 或 xml 命名空间的具体模型必须扩展另一个特征(当然是“模型”),以便将实际逻辑委托给(具体)模型。
-
getAll() 方法的语义并不意味着输入参数,您对类型感兴趣。这向我表明您正在泄漏实现细节(反射以决定方法内的适当实现)。此外,当
getAll返回Seq[Model]时,您甚至会失去这种类型。 -
> 在一些动态类型语言中... 1. Rapture 确实使用
language.dynamics来实现类似的机制。 2. 隐含(使用Type Classes 或不使用)也可能对您有所帮助,如前所述 3. 在 FP 中,您可以将实现特定行为的函数作为参数传递给平淡无奇的方法/函数(甚至返回一个函数)。所以我会尝试在设置中选择这样一个(一组)功能;即关于苹果或橙子的行为(同样适用于xml或sql)。 -
关于您的编辑:具有可变的全局持久性切换机制将不可避免地在多线程上下文中产生令人惊讶的结果。再说一次,你读的是
Model,而不是Person。
标签: java scala design-patterns dependency-injection persistence