在Django中使用观察者模式的问题

4 投票
4 回答
3630 浏览
提问于 2025-04-16 03:54

我正在做一个网站,专门卖产品(有一个叫做 Sale 的类和一个叫做 Product 的类)。每当我卖出一个产品时,我想把这个动作记录在一个历史表里,所以我决定使用观察者模式来实现这个功能。

简单来说,我的 Sale 类是“主题”,而 History 类是“观察者”。每当我调用 Sale 类里的 save_sale() 方法时,就会通知所有的观察者。(我选择这个模式是因为以后我还想发送邮件、通知管理员等等。)

这是我的主题类(Sale 类是从这个类扩展而来的)

class Subject:
    _observers = []

    def attach(self, observer):
        if not observer in self._observers:
            self._observers.append(observer)

    def detach(self, observer):
        try:
            self._observers.remove(observer)
        except ValueError:
            pass

    def notify(self,**kargs):
        for observer in self._observers:
            observer.update(self,**kargs)

在视图中,我做了类似这样的操作

sale = Sale()
sale.user = request.user
sale.product = product
h = History() #here I create the observer
sale.attach(h) #here I add the observer to the subject class
sale.save_sale() #inside this class I will call the notify() method

这是 History 类里的更新方法

def update(self,subject,**kargs):
    self.action = "sale"
    self.username = subject.user.username
    self.total = subject.product.total
    self.save(force_insert=True)

第一次运行的时候一切正常,但当我尝试再卖一个产品时,就出现了错误,提示我不能插入到 History 表,因为有主键约束的问题。

我猜测,当我第二次调用视图时,第一个观察者还在主题类里,这样就变成了两个历史观察者在监听 Sale 类,但我不太确定这是不是问题所在(真希望能用 php 的 print_r 来调试一下)。

我哪里做错了?我什么时候需要“附加”观察者?或者有没有更好的方法来实现这个功能?

顺便说一下:我使用的是 Django 1.1,并且无法安装任何插件。

4 个回答

1

@Andrew Sledge回答提供了一种很好的解决这个问题的方法。我想推荐另一种方法。

我遇到过类似的问题,最开始是用信号(signals)来处理的。信号工作得不错,但我发现我的单元测试变得很慢,因为每次我用一个固定的实例加载相关类时,信号都会被调用。这让测试的运行时间增加了几十秒。虽然有解决办法,但我觉得有点麻烦。我定义了一个自定义的测试运行器,在加载固定实例之前把我的函数从信号中断开,然后再重新连接。

最后,我决定完全放弃信号,而是重写了模型中合适的save()方法。在我的情况下,每当一个Order被修改时,OrderHistory表中会自动创建一行记录,除此之外还有其他操作。为了实现这个功能,我添加了一个创建OrderHistory实例的函数,并在Order.save()方法中调用它。这样也使得我可以分别测试save()和这个函数。

可以看看这个SO问题。里面讨论了什么时候应该重写save(),什么时候又应该使用信号。

9

这可能不是一个完全合适的回答,因为它更像是关于架构的讨论,但你有没有考虑过使用信号来通知系统发生了变化?看起来你正在尝试做的事情正是信号设计的目的。Django的信号功能和观察者模式的效果是一样的。

http://docs.djangoproject.com/en/1.1/topics/signals/

4

我觉得这是因为 _observers = [] 就像一个静态的共享字段。所以每个 Subject 的实例都会改变这个 _observers 的实例,这样就会产生一些不想要的副作用。

把这个变量在构造函数里初始化:

class Subject:

    def __init__(self):
        self._observers = []

撰写回答