观察者模式和带有CRUD静态方法的DAO类

3
我正在审查一些DAO类,它们公开了CRUD静态方法create()、delete()等等。每个DAO类通过检查Notification.java类中数据库的变化来实现观察者模式,并在接收到一个变化通知时从数据库中提取对象。
简化后的代码类似于:
OfficeDAO.java
public class OfficeDAO implements PropertyChangeListener
{
    public OfficeDAO()
    {
        /* 
         * Below we add ourselves to the observers of class Notifications
         * (see observer pattern in Java) in practice we are notified
         * when one of our objects is changed by a remote client user
         */
        Notification.addChangeListener(this);
    }

    public static void create(Office office)
    {
      Connection connection = DBConnection.getConnection();

      //... stuff with the database insertion

      Notification.notifyDatabaseChanges("ocreate-" + officeId);
    }
}

现在的问题是addChangeListener(this)在构造函数中,但由于该DAO类是通过其静态方法使用的,因此构造函数永远不会被调用。
为了解决这个问题,在应用程序的main()方法中(顺便提一下,这是EDT线程内部),有一个类似于以下的调用:
new OfficeDAO(); //ignore this, it is needed only to start the DAO constructor

这似乎很不专业,因此我认为应该添加一个。
static {
  Notification.addChangeListener(this);
}

在OfficeDAO.java类中,但当然静态初始化器中不存在“this”引用,所以我无解。 从DAO方法中删除static是不可行的,因为这些方法在整个应用程序中都被调用,并且在没有类实例的情况下被调用。 是否有任何干净的解决方案或解决方法,我目前是否遗漏了?

PropertyChangeListener是否也只包含在每个DAO中重新定义的静态方法?你能展示一下PropertyChangeListener和Notification类的代码吗?另外,在经典的观察者模式中,是Observable通知观察者其状态的变化。 - Chetan Kinger
3个回答

2

看起来这是一个相当混乱的情况,创建一个新对象只是为了向通知添加监听器更像是一种反模式。我的假设是这是一些遗留代码的一部分,不太可能进行大量的代码重构。我可以说,由于DAO层的作用更像是单例,您可以嵌入预先创建的DAO实例,并通过静态引用访问它。

private static OfficeDAO myDAO = new OfficeDAO(); //The constructor code remains the same

是的,这是一些遗留代码,如果我确定如何重构它,那么它可以被重构,因为有很多DAO并且项目本身相当大。如果我将DAOs设置为静态的,那么我会破坏Notification.addChangeListener(this)的调用,这对应用程序正常工作至关重要。 - sarah.ferguson
@sarah.ferguson 我认为你不需要将DAO设为“静态”。这个答案的目的是解释一下,你可以拥有一个对DAO的“静态”引用,而不仅仅是说“new DAO()”;虽然这会使代码看起来不那么丑陋,但肯定会适得其反。为什么要使用类的引用来访问静态方法?你的IDE会在你的代码中添加关于此的警告。 - Chetan Kinger

1
您可以将所有DAO类更改为Singleton。我同意不需要创建DAO实例,因为DAO没有状态,这不是一个理想的解决方案。但是,您不是在寻找理想的解决方案,而是一种更干净的hack,需要最少更改客户端代码。我不确定您是否在项目中使用IoC框架,但如果您决定在将来使用一个,将DAO转换为Singleton将为此设置基础。
让我们将OfficeDAO转换为Singleton:
public class OfficeDAO implements PropertyChangeListener {

    private static volatile OfficeDAO INSTANCE;

    private OfficeDAO() {
        if (INSTANCE != null) {// prevent reflection attacks
            throw new InstantiationError("Illegal attempt to create more than one instance of OfficeDAO");
        }
        Notification.addChangeListener(this);
    }

    public static OfficeDAO getInstance() {
        OfficeDAO localInstance = INSTANCE;
        if (INSTANCE == null) {
            synchronized (OfficeDAO.class) {
                localInstance = INSTANCE;
                if (localInstance== null) {
                    INSTANCE = localInstance = new OfficeDAO();
                }
            }
        }
        return localInstance;
    }

    public void create(Office office) {
        Connection connection = DBConnection.getConnection();

        // ... stuff with the database insertion

        Notification.notifyDatabaseChanges("ocreate-" + officeId);
    }
}

如果您以类似的方式更改所有DAO,那么在客户端代码中唯一需要更改的是将 ClassName.staticMethod()更改为 ClassName.getInstance().staticMethod()
例如:OfficeDAO.getInstance().create(..) * 话虽如此,看起来您的观察者也是主题,这不是实现观察者模式的经典方式。此外,您可以避免双重检查锁定并采用不懒惰实例化的单例。如何实现Singleton是您选择的问题,与问题没有直接关系。
如果您的项目开始使用Spring或Guice等Ioc框架,则可以摆脱私有构造函数和getInstance方法。然后,最好将DAO中的所有静态方法更改为实例方法,并让IoC框架创建和注入DAO到所有需要它们的类中。这有几个优点:
  1. 大多数IoC框架允许您决定在请求时是否提供类的一个对象或每次请求时都提供一个新对象。因此,您可以在不需要更改DAO的情况下选择Singleton vs非Singleton DAO。
  2. 您的数据源可以从数据库更改为.csv文件,而无需更改使用DAO的客户端代码。
  3. 您实际上可以在服务类中模拟DAO以进行单元测试。

1
单例模式是一种较少使用hacky的解决方案,但由于我将涉及许多类,因此我希望提出一个“干净”的解决方案。你所说的“双重检查锁定”是什么意思?观察者也可以是主题吗?这是一种反模式吗? - sarah.ferguson
@sarah.ferguson 你需要做的修改量与你的代码质量成反比。使用我的解决方案,你将会涉及到很多类,但实际上你不会真正改变太多东西,只是改变了客户端代码访问方法的方式。你可以在这里阅读更多关于双重检查锁定的信息。你不需要用这种方式实现单例模式。搜索网站以寻找不同的单例模式实现方式。一个观察者也可以是主题,这没有任何问题。 - Chetan Kinger

0

DAO设计是一个非常复杂的主题,您可以开始阅读Balusc(本论坛上的用户)关于DAO设计的文章,并最终选择一个处理DAO和连接管理的框架。

一个非常轻量级的库Butterfly Persistence可以为您提供正确的DAO实现起点,它将为您创建DAOFactory并管理连接,而无需导入100MB的Spring或Hibernate jar库。

至于为数据库操作添加监听器和通知器,您应该完全避免这样做,而是使用支持事件通知的数据库,例如Oracle或Firebird。

这样,您只需要监听事件,数据库会通知您其表格的任何更改。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接