Java 使用尽可能少的更改动态更改持久层(在运行时)

Java 使用尽可能少的更改动态更改持久层(在运行时),java,scala,design-patterns,dependency-injection,persistence,Java,Scala,Design Patterns,Dependency Injection,Persistence,我正在寻找一种设计模式/方法来动态地(最好是在运行时)交换应用程序的(持久性)层 为什么? 我希望能够决定是将某些数据保存为XML还是基于“每个实例”的数据库。因此,我可能决定一个项目使用XML作为后端,另一个项目使用数据库。我希望在这里灵活一些,并且能够轻松地为Json或其他任何东西添加另一个驱动程序“ 现在假设以下设置: 我们有一个控制器,我们想管理一些数据。我们可以在SQL和XML实现之间进行选择 一种可能的(有效的)解决方案: 巴斯克控制器 val myPersistenceLayer:

我正在寻找一种设计模式/方法来动态地(最好是在运行时)交换应用程序的(持久性)层

为什么?

我希望能够决定是将某些数据保存为XML还是基于“每个实例”的数据库。因此,我可能决定一个项目使用XML作为后端,另一个项目使用数据库。我希望在这里灵活一些,并且能够轻松地为Json或其他任何东西添加另一个驱动程序“

现在假设以下设置:

我们有一个控制器,我们想管理一些数据。我们可以在SQL和XML实现之间进行选择

一种可能的(有效的)解决方案:

巴斯克控制器

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中,而且在每个实现中,都必须为每个新模型(想想fruit!;)添加方法。我喜欢我的单一责任,因此我宁愿将其委托给模型,例如:

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”并在控制器中从正确的包导入它,但这不是真正可行的,也不是真正容易实现的,而且这是一个相当讨厌的黑客攻击,因为我显然缺少了一些东西

长话短说:我希望能够动态切换包以满足持久性需求。在一些动态类型的语言中,我可以想出一个解决方案,但在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通常保持不变,但用户可以决定对其进行更改。只要有时间,我会更深入地研究一下。但是可能有人会立即发现这种方法的问题。

DI允许您在编译时连接实现。在Scala中有很多方法可以进行DI(蛋糕模式、阅读器Monad、DI框架等)

如果您想在应用程序启动时连接依赖项,那么常规的依赖项机制可以工作。您只需根据某些条件创建所需依赖项(SQL、XML)的实例,并将其传递给代码


如果您希望在应用程序执行期间在依赖项之间不断切换,例如,有时保存为SQL,有时保存为XML,那么您可以使用类似的内容,另请参见我的答案-选项2。

您可以使用运行时反射来完成此操作。您需要在运行时指定并创建类/对象,并将其传递到持久性层,然后调用generic
getAll
方法

有关反射库的详细信息->

最好是让同伴对象
Apple
,它的
getAll
方法对于每个持久性层都有不同的实现

然后使用完整的包名访问带有反射的Apple对象

val apple:sql.Apple = //Reflection library object access
val apple:xml.Apple = //Reflection library object access


val apples = myPersistenceLayer.get(apple)

我认为您可以通过隐式ITS+TypeTags实现基于模块的包含,其中包含以下内容

object SqlPersistence {
  implicit def getAll[T: TypeTag](): Seq[T] = {/* type-based sql implementation*/}
}

object JsonPersistence {
  implicit def getAll[T: TypeTag](): Seq[T] = {/* type-based json implementation*/}
}

object PersistenceLayer {
  def getAll[T](implicit getter: Unit => Seq[T]): Seq[T] = getter
}

// somewhere else ...
import SqlPersistence._

PersistenceLayer.getAll[Apple]
其优点是,您可以通过引入相应的导入来当场决定持久性层。主要的缺点是相同的:您需要在每次调用时确定持久层,并确保它是您所想的。另外,根据我个人的经验,编译器在处理复杂的隐式转角情况时帮助不大,因此可能需要花费更多的时间进行调试

如果您为一个应用程序设置了一次持久层,那么DI就可以了,例如。但同样,您需要为每个类提供一个方法,或者求助于反射。如果没有思考,它可能看起来是这样的:

trait PersistenceLayer {
  def getApples(): Apples
}

trait SqlPersistenceLayer extends PersistenceLayer {
  override def getApples() = // sql to get apples 
}

trait Controller {
  this: PersistenceLayer =>

  def doMyAppleStuff = getApples()
}

// somewhere in the main ...
val controller = new Controller with SqlPersistence {}
controller.doMyAppleStuff

我认为存储库模式是您的解决方案

编辑:

嗯。谢谢“-1”没关系,因为我没有解释我的想法背后

我的例子只是其他许多例子中的一个。所以我希望这对其他人有用

我将尝试解释我关于使用存储库和工厂模式的想法

为此,我制作了一个github存储库,其示例代码如下:

我的设置与你的问题几乎相同。但区别如下:

  • 我没有使用scala。但概念是一样的
  • “我的设置”仅包含存储库工厂的“标志”
  • “模型”对象是持久性对象。这意味着用户不知道如何持久化。这是存储库所关心的问题
  • 我手工进行依赖项注入,因为这对于示例来说应该足够了
  • 我没有“控制器”,但我有“应用服务”
每次调用create()方法时,都会在工厂内对所使用的实现做出决定

域层对所使用的基础架构实现一无所知。应用层正在协调域服务和基础设施服务(在我的示例中,仅存储库)

如果你有任何DI容器,那么工厂可以由一个生产商或其他东西。。。依赖于DI容器

包结构:

我还做了一个简单的集成测试

public class AppleServiceIT {

    private Settings settings;
    private AppleService appleService;

    @Before
    public void injectDependencies() {
        settings = new Settings();
        final JdbcAppleRepository jdbcAppleRepository = new JdbcAppleRepository();
        final JsonAppleRepository jsonAppleRepository = new JsonAppleRepository();
        final AppleRepositoryFactory appleRepositoryFactory = new AppleRepositoryFactory(jdbcAppleRepository, jsonAppleRepository);
        appleService = new AppleService(settings, appleRepositoryFactory);
    }

    @Test
    public void test_findAppleById() {
        // test with jdbc
        settings.setRepositoryType(RepositoryTypeEnum.JDBC);
        assertEquals("JDBC-135", appleService.findAppleById(135l).getMessage());

        // test with json
        settings.setRepositoryType(RepositoryTypeEnum.JSON);
        assertEquals("JSON-243", appleService.findAppleById(243l).getMessage());
    }

    @Test
    public void test_getApples() {
        // test with jdbc
        settings.setRepositoryType(RepositoryTypeEnum.JDBC);
        assertEquals(2, appleService.getApples().size());

        // test with json
        settings.setRepositoryType(RepositoryTypeEnum.JSON);
        assertEquals(3, appleService.getApples().size());
    }

}

如果有帮助的话,也可以使用类似的方法。

中的
PersistenceLayer