在Django中使用观察者模式的问题
我正在做一个网站,专门卖产品(有一个叫做 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 个回答
@Andrew Sledge的回答提供了一种很好的解决这个问题的方法。我想推荐另一种方法。
我遇到过类似的问题,最开始是用信号(signals)来处理的。信号工作得不错,但我发现我的单元测试变得很慢,因为每次我用一个固定的实例加载相关类时,信号都会被调用。这让测试的运行时间增加了几十秒。虽然有解决办法,但我觉得有点麻烦。我定义了一个自定义的测试运行器,在加载固定实例之前把我的函数从信号中断开,然后再重新连接。
最后,我决定完全放弃信号,而是重写了模型中合适的save()
方法。在我的情况下,每当一个Order
被修改时,OrderHistory
表中会自动创建一行记录,除此之外还有其他操作。为了实现这个功能,我添加了一个创建OrderHistory
实例的函数,并在Order.save()
方法中调用它。这样也使得我可以分别测试save()
和这个函数。
可以看看这个SO问题。里面讨论了什么时候应该重写save()
,什么时候又应该使用信号。
这可能不是一个完全合适的回答,因为它更像是关于架构的讨论,但你有没有考虑过使用信号来通知系统发生了变化?看起来你正在尝试做的事情正是信号设计的目的。Django的信号功能和观察者模式的效果是一样的。
我觉得这是因为 _observers = []
就像一个静态的共享字段。所以每个 Subject
的实例都会改变这个 _observers 的实例,这样就会产生一些不想要的副作用。
把这个变量在构造函数里初始化:
class Subject:
def __init__(self):
self._observers = []