数据访问层的设计模式 [英] Design Patterns for Data Access Layer

查看:123
本文介绍了数据访问层的设计模式的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个使用数据库(MongoDB)存储信息的应用程序。过去我已经使用了一个充满静态方法来保存和检索数据的类,但是我已经意识到这并不是非常面向对象的或者将来的证明。



即使不太可能,我会改变数据库,我宁可不太关心Mongo的东西。我也希望能够使用该选项来缓存结果,从数据库刷新缓存的对象,但这并不重要,可以在另一个地方完成。



I已经查看了数据访问对象,但是它们似乎没有明确定义,我找不到任何实现的好例子(在Java或类似的语言中)。我也有很多关闭的案例,如找到用户名的标签完成,似乎不太适合,并将使DAO大和blo肿。



是否有任何设计模式方便获取和保存对象,而不是太具体数据库?实现的好例子将是有帮助的(最好在Java中)。

解决方案

嗯,java中数据存储的常用方法是正如你所说,不是非常面向对象。这本身既不好也不好:对象导向既不是优势也不是劣势,它只是许多范例之一,有时有助于良好的架构设计(有时不会)。



java中的DAO通常不是面向对象的原因正是您想要实现的 - 放松对数据库的依赖性。在一个更好的设计语言中,允许多重继承,这个或者过程可以非常优雅地以面向对象的方式完成,但是使用java,它似乎比值得的更麻烦。



在更广泛的意义上,非OO方法有助于将应用程序级数据与存储方式分离。这不仅仅是依赖于特定数据库的具体细节,而且还包括存储模式,这在使用关系数据库时尤其重要(不要让我在ORM上启动):您可以使用精心设计的关系模式所以,现在java中的大多数DAO本质上是你在开头提到的 - 充满静态方法的类 - 。一个区别在于,不是让所有的方法都是静态的,而是更好的是拥有一个静态的工厂方法(可能在不同的类中),返回一个(单例)的DAO实例,它实现一个特定的接口,由应用程序代码用于访问数据库:

  public interface GreatDAO {
用户getUser(int id);
void saveUser(User u);
}
public class TheGreatestDAO实现了GreatDAO {
protected TheGeatestDAO(){}
...
}
public class GreatDAOFactory {
private static GreatDAO dao = null;
保护静态同步GreatDao setDAO(GreatDAO d){
GreatDAO old = dao;
dao = d;
返回旧;
}
public static synchronized GreatDAO getDAO(){
return dao == null? dao = new TheGreatestDAO():dao;
}
}

public class App {
void setUserName(int id,String name){
GreatDAO dao = GreatDAOFactory.getDao();
用户u = dao.getUser(id);
u.setName(name);
dao.saveUser(u);
}
}

为什么这样做而不是静态方法?那么如果你决定切换到不同的数据库呢?当然,您将创建一个新的DAO类,实现新存储的逻辑。如果你正在使用静态方法,那么现在你需要经过你所有的代码,并且把它改成使用你的新类,对吗?这可能是一个巨大的痛苦。那么如果你改变主意并想切换回旧的数据库呢?



使用这种方法,您所需要做的就是更改 GreatDAOFactory.getDAO()并使其创建一个不同类的实例,并且所有的应用程序代码将使用新的数据库而不进行任何更改。



在现实生活中,通常完成对代码的任何更改:工厂方法通过属性设置获取实现类名称,并使用反射实例化所以,所有你需要做的切换实现是编辑一个属性文件。实际上,框架 - 像 spring guice - 为你管理这种依赖注入机制,但我赢了首先,因为它真的超出了您的问题的范围,同时也因为我不一定相信使用这些框架所带来的好处值得与大多数应用程序集成在一起。 p>

另一个(可能更有可能被利用)受益于这种工厂方法而不是静态是可测试性。想象一下,您正在编写单元测试,它应该独立于任何基础DAO来测试您的 App 类的逻辑。您不希望它使用任何真正的底层存储有几个原因(速度,必须设置,清理后,可能与其他测试的碰撞,可能会污染具有DAO问题的测试结果,与<$ c无关$ c>应用程序,实际上正在进行测试等)。



为此,您需要一个测试框架,如 Mockito ,它允许你嘲笑任何对象或方法的功能,用虚拟对象替换为预定义的行为(我会跳过细节,因为,这再次超出了范围)。所以,你可以创建这个虚拟对象来代替你的DAO,并使 GreatDAOFactory 返回你的虚拟代码,而不是通过调用 GreatDAOFactory.setDAO )在测试之前(并将其恢复)。如果你使用静态方法而不是实例类,那么这是不可能的。



另外一个好处就是类似于上面描述的切换数据库,就是用附加功能来增加你的数据。假设您的应用程序随着数据库中数据量的增长而变慢,并且您决定需要缓存层。实现一个包装类,它使用真正的dao实例(作为构造函数param提供)来访问数据库,并缓存它在内存中读取的对象,从而可以更快地返回它们。然后,您可以使您的 GreatDAOFactory.getDAO 实例化此包装器,以便应用程序利用它。



(这被称为委托模式...似乎在屁股的痛苦,特别是当你在DAO中定义了很多方法时:你将不得不在包装器中实现所有这些,甚至改变只有一个的行为,或者你可以简单地子类化你的dao,并以这种方式添加缓存,这将是一个很少无聊的编码,但是可能会变得有问题,当你做决定更改数据库,或者更糟的是,有可选择来回切换实现)。



同样广泛使用(但在我看来,劣等) 工厂方法的替代方法是使所有需要它的类成员变量 dao

  public class App {
GreatDao dao;
public App(GreatDao d){dao = d; }
}

这样,实例化这些类的代码需要实例化dao对象(仍然可以使用工厂),并提供它作为构造函数参数。上面提到的依赖注入框架通常做类似的事情。



这提供了我早先所说的工厂方法方法的所有好处,但是像我所说的那样,在我看来不是很好。这里的缺点是必须为每个应用程序类编写一个构造函数,执行相同的确切事情和结束,并且还不能在需要时轻松实例化类,并且一些丢失的可读性:具有足够大的代码库你的代码读者不熟悉,会很难理解使用dao的实际实现,它是如何实例化的,无论是单例,线程安全的实现,是保持状态还是缓存任何事情,如何做出关于选择特定实现的决定等。


I have an application which uses a database (MongoDB) to store information. In the past I have used a class full of static methods to save and retrieve data but I have since realised this is not very object-oriented-ish or future proof.

Even though it is very unlikely I will change database I would rather something that does not tie me too strongly to Mongo. I would also like to be able to cache results with the option to refresh the cached object from the database but this is not essential and can be done in another place.

I have looked at data access objects but they do not seem very well-defined and I can't find any good examples of implementation (In Java or a similar language). I also have many one off cases such as finding usernames for tab completion which seem not well suited and would make the DAO large and bloated.

Are there any design patterns that would facilitate getting and saving objects without being too database specific? Good examples of implementation would be helpful (preferably in Java).

解决方案

Well, the common approach to data storage in java is, as you noted, not at all very object-oriented. This in and of itself is neither bad or good: "object-orientedness" is neither advantage nor disadvantage, it's just one of many paradigms, that sometimes helps with good architecture design (and sometimes does not).

The reason DAOs in java aren't usually object-oriented is exactly what you want to achieve - relaxing your dependency on the database specific. In a better-designed language, that allowed for multiple inheritance, this, or course, can be done very elegantly in an object-oriented way, but with java, it just seems to be more trouble than it is worth.

In a broader sense, the non-OO approach helps decouple your application-level data from the way it is stored. This is more than (non) dependency on the specifics of a particular database, but also the storage schemas, which is especially important when using relational databases (don't get me started on ORM): you can have a well-designed relational schema seamlessly translated into the application OO model by your DAO.

So, what most DAOs are in java nowadays are essentially what you mentioned in the beginning - classes, full of static methods. One difference is that, instead of making all the methods static, it is better to have a single static "factory method" (probably, in a different class), that returns a (singleton) instance of your DAO, which implements a particular interface, used by application code to access the database:

public interface GreatDAO {
    User getUser(int id);
    void saveUser(User u);
}
public class TheGreatestDAO implements GreatDAO {
   protected TheGeatestDAO(){}
   ... 
}
public class GreatDAOFactory {
     private static GreatDAO dao = null;
     protected static synchronized GreatDao setDAO(GreatDAO d) {
         GreatDAO old = dao;
         dao = d;
         return old;
     }
     public static synchronized GreatDAO getDAO() {
         return dao == null ? dao = new TheGreatestDAO() : dao;
     }
}

public class App {
     void setUserName(int id, String name) {
          GreatDAO dao =  GreatDAOFactory.getDao();
          User u = dao.getUser(id);
          u.setName(name);
          dao.saveUser(u);
     }
}

Why do it this way as opposed to static methods? Well, what if you do decide to switch to a different database? Naturally, you'd create a new DAO class, implementing the logic for your new storage. If you were using static methods, you would now have to go through all your code, acessing the DAO, and change it to use your new class, right? This could be a huge pain. And what if then you change your mind and want to switch back to the old db?

With this approach, all you need to do is to change the GreatDAOFactory.getDAO() and make it create an instance of a different class, and all your application code will be using the new database without any changes.

In real life, this is often done without any changes to the code at all: the factory method gets the implementation class name via a property setting, and instantiates it using reflection, so, all you need to do to switch implementations is edit a property file. There are actually frameworks - like spring or guice - that manage this "dependency injection" mechanism for you, but I won't go into details, first, because it is really beyond the scope of your question, and also, because I am not necessarily convinced that the benefit you get from using those frameworks is worth the trouble integrating with them for most applications.

Another (probably, more likely to be taken advantage of) benefit of this "factory approach" as opposed to static is testability. Imagine, that you are writing a unit test, that should test the logic of your App class independently of any underlying DAO. You don't want it to use any real underlying storage for several reasons (speed, having to set it up, and clean up afterwords, possible collisions with other tests, possibility of polluting test results with problems in DAO, unrelated to App, which is actually being tested, etc.).

To do this, you want a test framework, like Mockito, that allows you to "mock out" the functionality of any object or method, replacing it with a "dummy" object, with predefined behavior (I'll skip the details, because, this again is beyond the scope). So, you can create this dummy object replacing your DAO, and make the GreatDAOFactory return your dummy instead of the real thing by calling GreatDAOFactory.setDAO(dao) before the test (and restoring it after). If your were using static methods instead of the instance class, this would not be possible.

One more benefit, which is kinda similar to switching databases I described above is "pimping up" your dao with additional functionality. Suppose that your application becomes slower as the amount of data in the database grows, and you decide that you need a cache layer. Implement a wrapper class, that uses the real dao instance (provided to it as a constructor param) to access the database, and caches the objects it reads in memory, so that they can be returned faster. You can then make your GreatDAOFactory.getDAO instantiate this wrapper, for the application to take advantage of it.

(This is called "delegation pattern" ... and seems like pain in the butt, especially when you have lots of methods defined in your DAO: you will have to implement all of them in the wrapper, even to alter behavior of just one. Alternatively, you could simply subclass your dao, and add caching to it this way. This would be a lot less boring coding upfront, but may become problematic when you do decide to change the database, or, worse, to have an option of switching implementations back and forth).

One equally broadly used (but, in my opinion, inferior) alternative to the "factory" method is making the dao a member variable in all classes that need it:

public class App {
   GreatDao dao;
   public App(GreatDao d) { dao = d; }
}

This way, the code that instantiates these classes needs to instantiate the dao object (could still use the factory), and provide it as a constructor parameter. The dependency injection frameworks I mentioned above, usually do something similar to this.

This provides all the benefits of the "factory method" approach, that I decribed earlier, but, like I said, is not as good in my opinion. The disadvantages here are having to write a constructor for each of your app classes, doing the same exact thing over, and over, and also not being able to instantiate the classes easily when needed, and some lost readability: with a large enough code base, a reader of your code, not familiar with it, will have hard time understanding which actual implementation of the dao is used, how it is instantiated, whether it is a singleton, a thread-safe implementation, whether it keeps state, or caches anything, how the decisions on choosing a particular implementation are made etc.

这篇关于数据访问层的设计模式的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆