You can’t throw a rock at a computer without hitting an application built using the Model-View-Controller architecture, and underlying that is the Observer pattern. Observer is so pervasive that Java put it in its core library (java.util.Observer) and C# baked it right into the language (the event keyword). 你随手朝一台电脑扔块石头,它砸到的应用可能就是使用Model-View-Controller架构开发的,而MVC模式里面就包含有观察者模式(Observer). 观察者模式应用是如此广泛,Java里面甚至直接把它集成到了系统库里面(java.util.Observer). 而C#更是直接在语言层面就集成了它(它有一个event关键字)
Like so many things in software, MVC was invented by Smalltalkers in the seventies. Lispers probably claim they came up with it in the sixties but didn’t bother writing it down. Observer is one of the most widely used and widely known of the original Gang of Four patterns, but the game development world can be strangely cloistered at times, so maybe this is all news to you. In case you haven’t left the abbey in a while, let me walk you through a motivating example. 和软件设计领域的很多东西一样,MVC也是在70年代的时候被Smalltalk程序员们发明的。Lisp程序员可能会说他们在60年代就已经发明了,但是他们不屑于写下来。观察者模式是GoF设计模式里面使用最为广泛,最为人所孰知的设计模式。但是,游戏开发领域有时候会显得有点与外界隔离。所以,它对你而言可能会有点陌生。假如,你还没有还没有在这个行业毕业,那就让我先给你讲一个故事先。
##Achievement Unlocked ##解琐成就 Say we’re adding an achievements system to our game. It will feature dozens of different badges players can earn for completing specific milestones like “Kill 100 Monkey Demons”, “Fall off a Bridge”, or “Complete a Level Wielding Only a Dead Weasel”. 假设我们往游戏里面添加一个成就系统。玩家在玩游戏的过程中可能会解琐N多的成就,比如:杀死100个猴子恶魔,第一次落水,完成一个关卡。
Achievement: Weasel Wielder 成就:Weasel Wielder.
I swear I had no double meaning in mind when I drew this. 我发誓当我写下这个的时候,我头脑里面决无二意。
This is tricky to implement cleanly since we have such a wide range of achievements that are unlocked by all sorts of different behaviors. If we aren’t careful, tendrils of our achievement system will twine their way through every dark corner of our codebase. Sure, “Fall off a Bridge” is somehow tied to the physics engine, but do we really want to see a call to unlockFallOffBridge() right in the middle of the linear algebra in our collision resolution algorithm? 要很优雅地实现这个功能会比较tricky,因为玩家可能通过N种不同的行为来获取N种不同的成就。如果我们不小心,就有可能会把成就系统弄得很糟糕,并且会使得代码变得很难维护。当然,“第一次落水”可能会和物理引擎相关联,但是,我们真的想在我们的线性代数碰撞检测算法里面插入一个unlockFallOffBridge()函数吗。
This is a rhetorical question. No self-respecting physics programmer would ever let us sully their beautiful mathematics with something as pedestrian as gameplay. What we’d like, as always, is to have all the code concerned with one facet of the game nicely lumped in one place. The challenge is that achievements are triggered by a bunch of different aspects of gameplay. How can that work without coupling the achievement code to all of them? 这只是一个随口说说的问题。没有哪个优秀的物理程序员会让我们在他写的优雅的数学算法里面加入一些游戏的玩法进去。而作为游戏程序员,我们的任务就是要把所有与游戏玩法相关的代码组装到一起。这里的挑战是,成就的触发可能跟玩家在游戏世界里面的很多行为相关。我们要怎样实现这些成就系统而不去耦合系统里面的其它代码呢?
That’s what the observer pattern is for. It lets one piece of code announce that something interesting happened without actually caring who receives the notification. 这就是观察者模式大显身手的时候到了。它可以通知一些对象它们感兴趣的消息,而不用关心具体是谁接收到了这些消息。
For example, we’ve got some physics code that handles gravity and tracks which bodies are relaxing on nice flat surfaces and which are plummeting toward sure demise. To implement the “Fall off a Bridge” badge, we could just jam the achievement code right in there, but that’s a mess. Instead, we can just do: 比如,我们有一个物理代码来处理重力并且判断刚体是否在一个平面上面。为了实现“Fall off a Brigde”成就,我们可以通过以下代码实现之。虽然代码有些丑,但是至少它们是可以完成功能的:
void Physics::updateEntity(Entity& entity)
{
bool wasOnSurface = entity.isOnSurface();
entity.accelerate(GRAVITY);
entity.update();
if (wasOnSurface && !entity.isOnSurface())
{
notify(entity, EVENT_START_FALL);
}
}
All it does is say, “Uh, I don’t know if anyone cares, but this thing just fell. Do with that as you will.” 这里完成的功能就是"我不关心是谁,但是它开始下落了。开始处理你自己的逻辑吧。"
The physics engine does have to decide what notifications to send, so it isn’t entirely decoupled. But in architecture, we’re most often trying to make systems better, not perfect. The achievement system registers itself so that whenever the physics code sends a notification, the achievement system receives it. It can then check to see if the falling body is our less-than-graceful hero, and if his perch prior to this new, unpleasant encounter with classical mechanics was a bridge. If so, it unlocks the proper achievement with associated fireworks and fanfare, and it does all of this with no involvement from the physics code. 物理引擎确实还需要关心发送消息的类型,所以,它还不是完全解耦。但是,在架构领域,我们经常会试着让系统变得更好,而不是更完美。成就系统注册它本身,当物理系统里面发出一个通知的时候,成就系统便会收到它。然后它便会检查是否这个掉落的刚体是否是我们的主角,如果是的话,那么便会触发成就系统并放射出一个礼花。并且,这一切与物理系统完全是解耦的。
In fact, we can change the set of achievements or tear out the entire achievement system without touching a line of the physics engine. It will still send out its notifications, oblivious to the fact that nothing is receiving them anymore. 事实上,我们可以修改成就系统集合,或者我们可以破坏整个成就系统而不用去修改物理引擎一行代码。它还是照样可以发送通知消息,只是,此时,已经没有对象会收到这些消息了。
##How it Works ##这一切是怎么工作的 If you don’t already know how to implement the pattern, you could probably guess from the previous description, but to keep things easy on you, I’ll walk through it quickly. 如果你还不知道怎么实现这个模式,你从前面的描述中已经大概略知一二了,但是,为了你考虑,我还是会快速地过一遍。 ###The observer ###观察者 We’ll start with the nosy class that wants to know when another object does something interesting. These inquisitive objects are defined by this interface: 我们将从接收通知的对象(Observer)开始,它的接口定义如下:
class Observer
{
public:
virtual ~Observer() {}
virtual void onNotify(const Entity& entity, Event event) = 0;
};
Any concrete class that implements this becomes an observer. In our example, that’s the achievement system, so we’d have something like so: 任何实际类,它实现这个接口就是一个观察者。在我们的示例里面,它就是成就系统,我们可以这样实现它们:
class Achievements : public Observer
{
public:
virtual void onNotify(const Entity& entity, Event event)
{
switch (event)
{
case EVENT_ENTITY_FELL:
if (entity.isHero() && heroIsOnBridge_)
{
unlock(ACHIEVEMENT_FELL_OFF_BRIDGE);
}
break;
// Handle other events, and update heroIsOnBridge_...
}
}
private:
void unlock(Achievement achievement)
{
// Unlock if not already unlocked...
}
bool heroIsOnBridge_;
};
###The subject ###主题 The notification method is invoked by the object being observed. In Gang of Four parlance, that object is called the “subject”. It has two jobs. First, it holds the list of observers that are waiting oh-so-patiently for a missive from it: 调用观察者来接收通知方法的对象。在四人帮的术语里面,这个对象叫做"Subject".它有两个任务,首先,它拥有一系列的观察者,它们随时候命准备接收各种各样的通知:
class Subject
{
private:
Observer* observers_[MAX_OBSERVERS];
int numObservers_;
};
The important bit is that the subject exposes a public API for modifying that list: 最重要的部分是,这个subject对象暴露了一个公开的API,可以用来修改一个这个观察者列表:
class Subject
{
public:
void addObserver(Observer* observer)
{
// Add to array...
}
void removeObserver(Observer* observer)
{
// Remove from array...
}
// Other stuff...
};
That allows outside code to control who receives notifications. The subject communicates with the observers, but it isn’t coupled to them. In our example, no line of physics code will mention achievements. Yet, it can still talk to the achievements system. That’s the clever part about this pattern. 这样允许外部的代码来控制谁可以接收通知事件。这个subject对象负责和观察者对象进行沟通,但是,它并不与它们耦合。在我们的例子里面,没有一行物理代码会涉及到成就系统。当然,它是可以直接与成就系统打交道的。这就是这个观察者模式的聪明之处。
It’s also important that the subject has a list of observers instead of a single one. It makes sure that observers aren’t implicitly coupled to each other. For example, say the audio engine also observes the fall event so that it can play an appropriate sound. If the subject only supported one observer, when the audio engine registered itself, that would un-register the achievements system. 同时,subject对象拥有一个观察者对象的集合,而不是单个观察者,这也是很重要的。它保证了观察者们并不会隐式地耦合到一起。比如,声音引擎也注册了“第一次落水”的事件,这样在该成就达成的时候,就可以播放一个合适的声音。如果Subject不支持多个观察者的话,当声音引擎注册这个事情的时候,成就系统就无法注册该事件了。
That means those two systems would interfere with each other — and in a particularly nasty way, since the second would disable the first. Supporting a list of observers ensures that each observer is treated independently from the others. As far as they know, each is the only thing in the world with eyes on the subject. 这意味着,那两个系统会相互干扰对方--而且是以一种很恶心的方式,因为第二个观察者使第一个观察者失效了。支持观察者集合,可以让每一个观察者都互相不干扰。在它们各自的眼里,都认为Subject眼里只有它自己。
The other job of the subject is sending notifications: Subject对象还有一个职责就是“发送通知”:
class Subject
{
protected:
void notify(const Entity& entity, Event event)
{
for (int i = 0; i < numObservers_; i++)
{
observers_[i]->onNotify(entity, event);
}
}
// Other stuff...
};
###Observable physics ###可被观察的物理模块 Now, we just need to hook all of this into the physics engine so that it can send notifications and the achievement system can wire itself up to receive them. We’ll stay close to the original Design Patterns recipe and inherit Subject: 现在,我们只需要在物理引擎里面设置一些东西,这样,当成就触发的时候,我们的成就系统可以收到对应的通知。我们会尽可能地按照经典的设计模式来继承Subject类:
class Physics : public Subject
{
public:
void updateEntity(Entity& entity);
};
This lets us make notify() in Subject protected. That way the derived physics engine class can call it to send notifications, but code outside of it cannot. Meanwhile, addObserver() and removeObserver() are public, so anything that can get to the physics system can observe it. 这种方式让我们可以把notify()方法变成被保护的方法。这样,派生的物理引擎类就可以调用它来发送通知,但是,在物理引擎外部的代码是不行的。同时,addObserver()和removeObserver()方法是公开的,所以,任何可以操作物理系统的地方都可以调用这两个接口。
In real code, I would avoid using inheritance here. Instead, I’d make Physics have an instance of Subject. Instead of observing the physics engine itself, the subject would be a separate “falling event” object. Observers could register themselves using something like: 在实际的代码中,我会尽量避免使用继承。这里,我们让Physics系统有一个Subject实例。与观察物理引擎相反,我们的subject会是一个单独的"下落事件"对象。观察者会使用下面的代码来注册事件:
physics.entityFell()
.addObserver(this);
To me, this is the difference between “observer” systems and “event” systems. With the former, you observe the thing that did something interesting. With the latter, you observe an object that represents the interesting thing that happened. 对于我而言,这就是“观察者”系统和“事件”系统的区别。前者,你观察一个事情,它做了一些你感兴趣的事。后者,你观察一个对象,这个对象代表了感兴趣的事情 。
Now, when the physics engine does something noteworthy, it calls notify() like in the motivating example before. That walks the observer list and gives them all the heads up. 现在,当物理系统做了一些事情以后,它调用notify()方法来通知其它对象。它会遍历观察者列表,然后逐个给他们发送消息。
Pretty simple, right? Just one class that maintains a list of pointers to instances of some interface. It’s hard to believe that something so straightforward is the communication backbone of countless programs and app frameworks. 很简单,对吧?只有一个类,它维护了一个满足特定接口的对象列表。这很难让人想象,通过应用此模式后,应用程序框架间的通讯会变得如此简单。
But the Observer pattern isn’t without its detractors. When I’ve asked other game programmers what they think about this pattern, they bring up a few complaints. Let’s see what we can do to address them, if anything. 但是,观察者模式并不是完美的。当我问另外的游戏程序员他们是如何看待这个模式的,他们也会带来一些抱怨。让我们来看看这些具体的抱怨是什么吧。
##“It’s Too Slow” ##“它太慢了!” I hear this a lot, often from programmers who don’t actually know the details of the pattern. They have a default assumption that anything that smells like a “design pattern” must involve piles of classes and indirection and other creative ways of squandering CPU cycles. 我听到这句话很多次了,尤其是经常从一些不甚了解此模式的程序员口中听说。他们会有一些默认的假设,凡是和设计模式沾边的东西,都会引入一些间接和其它形式的CPU时钟的消耗。
The Observer pattern gets a particularly bad rap here because it’s been known to hang around with some shady characters named “events”, “messages”, and even “data binding”. Some of those systems can be slow (often deliberately, and for good reason). They involve things like queuing or doing dynamic allocation for each notification. 观察者模式尤其会获得一些特别的差评,因为只要谈到它,“事件”,“消息“和“事件数据绑定”等词就冒出来了。这些系统里面,有些是很慢的(而且它们是出于某个正当的理由去慢)。它们额外引入了一些东西,比如队列以及为每一个消息动态分配内存。
This is why I think documenting patterns is important. When we get fuzzy about terminology, we lose the ability to communicate clearly and succinctly. You say, “Observer”, and someone hears “Events” or “Messaging” because either no one bothered to write down the difference or they didn’t happen to read it. 这也是为什么我认为必须给设计模式写文档是很重要的原因。当我们对于一个东西理解很模糊的时候,我们就丧失了可以清楚正确地沟通的能力。你说“观察者”,而其他人理解的却是"消息",因为没有人愿意写下这两者之间的区别是什么,而且也没人会花时间去读这些内容。
That’s what I’m trying to do with this book. To cover my bases, I’ve got a chapter on events and messages too: Event Queue. 这也是为什么我要写这本书的原因。为了扩展自己的知识体系,我也专门写了一章关于事件和消息的模式:事件队列。
But, now that you’ve seen how the pattern is actually implemented, you know that isn’t the case. Sending a notification is simply walking a list and calling some virtual methods. Granted, it’s a bit slower than a statically dispatched call, but that cost is negligible in all but the most performance-critical code. 但是,现在你已经看到这个模式是怎样实现的了,你清晰事实不是他们所想的那样。发送一个通知,只不过需要遍历一个列表,然后调用一些虚函数。老实讲,它比普通的函数调用开销会大一些,但是这里的开销是很小的,而且性能瓶颈大多不可能在这里。
I find this pattern fits best outside of hot code paths anyway, so you can usually afford the dynamic dispatch. Aside from that, there’s virtually no overhead. We aren’t allocating objects for messages. There’s no queueing. It’s just an indirection over a synchronous method call. 我发现这个模式适合于除代码热点之外的其他地方,这样你可以实现动态分配。除此之外,这里也并没有什么开销。我们并没有为消息分配对象。它只是一个间接的方法调用。
###It’s too fast? ###它太快了? In fact, you have to be careful because the Observer pattern is synchronous. The subject invokes its observers directly, which means it doesn’t resume its own work until all of the observers have returned from their notification methods. A slow observer can block a subject. 实际上,你不得不很小心,因为观察者模式是同步的。subject对象可以直接调用观察者们,这意味着,所有的观察者们都必须挨个调用通知方法,其中任何一个观察者对象都有可能阻塞subject。
This sounds scary, but in practice, it’s not the end of the world. It’s just something you have to be aware of. UI programmers — who’ve been doing event-based programming like this for ages — have a time-worn motto for this: “stay off the UI thread”. 这听起来有点可怕,但是,在实际中,它并没有想象那么坏。这是你必须考虑的一些事情。UI程序员----他们进行基于事件的编程已经N年了。它们有一个至理名言:“远离UI线程”。
If you’re responding to an event synchronously, you need to finish and return control as quickly as possible so that the UI doesn’t lock up. When you have slow work to do, push it onto another thread or a work queue. 如果你按照同步的方式来处理,你需要马上完成响应,然后把控件权回到UI代码,这样UI界面才不会卡住。当有一些很慢的操作的时候,我们可以让它们在另外一个工作线程里面执行。
You do have to be careful mixing observers with threading and explicit locks, though. If an observer tries to grab a lock that the subject has, you can deadlock the game. In a highly threaded engine, you may be better off with asynchronous communication using an Event Queue. 你真的需要很小心去处理线程和显式锁。如果一个观察者想要去取得subject的锁,那就有可能会让整个游戏死锁。在一个多线程的引擎里面,你最好是使用事件队列(Event Queue)来处理异步通信问题。
##“It Does Too Much Dynamic Allocation” ##“它做了太多的动态内存分配了” Whole tribes of the programmer clan — including many game developers — have moved onto garbage collected languages, and dynamic allocation isn’t the boogie man that it used to be. But for performance-critical software like games, memory allocation still matters, even in managed languages. Dynamic allocation takes time, as does reclaiming memory, even if it happens automatically. 大量的程序员--包括游戏程序员--开始转到拥有垃圾收集器的语言了,动态内存分配不在是一个害人的问题了。但是,对于一些性能要求很高的程序,比如游戏,内存分配还是很有用的,甚至在一些托管语言中也是如此。动态分配消耗时间,重新获取内存也是一样,即使这一切是自动完成的。
Many game developers are less worried about allocation and more worried about fragmentation. When your game needs to run continuously for days without crashing in order to get certified, an increasingly fragmented heap can prevent you from shipping. 许多游戏程序员并不是很担心内存分配问题,而是担心内存碎片问题。当你的游戏需要连续运行几天而不会crash,如果你的程序内存碎片太多,可能就会影响你的游戏发布。
The Object Pool chapter goes into more detail about this and a common technique for avoiding it. 在对象池(Object Pool)一章中,我们详细介绍了一个公共的处理技术来解决这个问题。
In the example code before, I used a fixed array because I’m trying to keep things dead simple. In real implementations, the observer list is almost always a dynamically allocated collection that grows and shrinks as observers are added and removed. That memory churn spooks some people. 在上面的样例代码中,我使用了一个固定大小的数组,因为我想尽可能保持简单。在实际的实现中,观察者列表总是一个动态分配的集合,当添加或者删除观察者的时候,该集合会动态地扩展或者收缩。这种内存的分配有时候会很坑人。
Of course, the first thing to notice is that it only allocates memory when observers are being wired up. Sending a notification requires no memory allocation whatsoever — it’s just a method call. If you hook up your observers at the start of the game and don’t mess with them much, the amount of allocation is minimal. 当然,第一件事情需要注意的就是,只有当观察者被串起来的时候才会分配内存。发送一个消息并不会有任何内存分配---它只是一个方法调用。如果你在游戏启动的时候,给你的观察者们添加一个钩子。你会发现内存的分配是很小的。
If it’s still a problem, though, I’ll walk through a way to implement adding and removing observers without any dynamic allocation at all. 如果你觉得它还是一个问题的话,我将会告诉你一个方法,这可以动态地添加或者删除观察者而不会动态分配一点内存。
###Linked observers ###链式观察者 In the code we’ve seen so far, Subject owns a list of pointers to each Observer watching it. The Observer class itself has no reference to this list. It’s just a pure virtual interface. Interfaces are preferred over concrete, stateful classes, so that’s generally a good thing. 从我们已经看过的代码中,Subject类拥有一个观察者的指针列表。观察者类本身并没有一个指向此列表的引用。它只是一个纯虚接口。优先使用接口而不是具体的有状态的类,它通常是一个好的设计。
But if we are willing to put a bit of state in Observer, we can solve our allocation problem by threading the subject’s list through the observers themselves. Instead of the subject having a separate collection of pointers, the observer objects become nodes in a linked list: 但是,如果我们愿意往观察者类里面添加一些状态,我们就可以解决我们的分配问题. 这里我们不是让Subject类拥有一系列观察者的集合,而是让观察者们变成一个链式列表。
To implement this, first we’ll get rid of the array in Subject and replace it with a pointer to the head of the list of observers: 为了实现这个,首先,我将会从Subject类中移除数组,然后保存一个头指针,让这个头指针指向这个观察者列表:
class Subject
{
Subject()
: head_(NULL)
{}
// Methods...
private:
Observer* head_;
};
Then we’ll extend Observer with a pointer to the next observer in the list: 然后,我们可以通过在观察者类里面放置一个next指针,让它可以索引到观察者列表中的下一个观察者:
class Observer
{
friend class Subject;
public:
Observer()
: next_(NULL)
{}
// Other stuff...
private:
Observer* next_;
};
We’re also making Subject a friend class here. The subject owns the API for adding and removing observers, but the list it will be managing is now inside the Observer class itself. The simplest way to give it the ability to poke at that list is by making it a friend. 这里,我们把Subject类作为一个友元类。subject类拥有添加和删除观察者的API,但是,现在我们想在观察者类本身来维护这个列表。最简单的方式就是把subject类作为一个友元类。
Registering a new observer is just wiring it into the list. We’ll take the easy option and insert it at the front: 注册一个新的观察者只需要把它插入到这个列表头就可以了。
void Subject::addObserver(Observer* observer)
{
observer->next_ = head_;
head_ = observer;
}
The other option is to add it to the end of the linked list. Doing that adds a bit more complexity. Subject has to either walk the list to find the end or keep a separate tail_ pointer that always points to the last node. 还有一种方式就是在这个链表后面插入一个观察者。那样做的话,可能会有一点点复杂。Subject要么从头至尾遍历一次来找到最后一个节点,要么通过维护一个tail_指针,让这个指针永远指向最后一个结点。
Adding it to the front of the list is simpler, but does have one side effect. When we walk the list to send a notification to every observer, the most recently registered observer gets notified first. So if you register observers A, B, and C, in that order, they will receive notifications in C, B, A order. 把观察者每次都添加到链表表头会更简单一些,但是这样做有一个负面影响。当我们从头至尾遍历这个链表来给每一个观察者发送通知的时候,最近注册的观察者会最先收到通知。所以,如果你按照A、B、C的顺序来注册观察者,那么收到通知的观察者的顺序便是C、B、A。
In theory, this doesn’t matter one way or the other. It’s a tenet of good observer discipline that two observers observing the same subject should have no ordering dependencies relative to each other. If the ordering does matter, it means those two observers have some subtle coupling that could end up biting you. 理论上,从头至尾遍历或者从尾至头遍历都没什么关系。这里有一个原则就是,如果两个观察者观察同一个Subject对象,它们两个不会因为注册顺序而受到影响。如果注册顺序对观察者有影响的话,那么这两个观察者便是耦合到一起了,这种设计可能随时会坑你哦。
Let’s get removal working: 现在,让我们看看删除操作怎么定义:
void Subject::removeObserver(Observer* observer)
{
if (head_ == observer)
{
head_ = observer->next_;
observer->next_ = NULL;
return;
}
Observer* current = head_;
while (current != NULL)
{
if (current->next_ == observer)
{
current->next_ = observer->next_;
observer->next_ = NULL;
return;
}
current = current->next_;
}
}
Because we have a singly linked list, we have to walk it to find the observer we’re removing. We’d have to do the same thing if we were using a regular array for that matter. If we use a doubly linked list, where each observer has a pointer to both the observer after it and before it, we can remove an observer in constant time. If this were real code, I’d do that. 因为我们是一个意向链表,所以,我们必须从头至尾遍历一次才可以删除特定位置的结点。如果我们使用一个普通的数组作为数据结构,我们也要这样遍历才行。如果我们使用一个双向链表的话,每一个观察者同时拥有它前面一个观察者和后面一个观察者指针。我们可以在常量时间内删除一个结点。如果是生产代码的话,我会采用双向链表的方式。
The only thing left to do is send a notification. That’s as simple as walking the list: 接下来,我们只需要发送消息就可以了。它和遍历链表的操作差不多:
void Subject::notify(const Entity& entity, Event event)
{
Observer* observer = head_;
while (observer != NULL)
{
observer->onNotify(entity, event);
observer = observer->next_;
}
}
Not too bad, right? A subject can have as many observers as it wants, without a single whiff of dynamic memory. Registering and unregistering is as fast as it was with a simple array. We have sacrificed one small feature, though. 这种实现还不算太坏,对吧?一个Subject可以包含任意多个观察者,而且添加和删除观察者并不会造成任何动态内存分配。注册观察者和移除观察者的操作和普通数组操作一样快。但是,我们这样做是以牺牲了一个功能特性为代价的。
Since we are using the observer object itself as a list node, that implies it can only be part of one subject’s observer list. In other words, an observer can only observe a single subject at a time. In a more traditional implementation where each subject has its own independent list, an observer can be in more than one of them simultaneously. 因为我们的观察者对象本身也是链表的一个节点,所以,这意味着我们的观察者必须是subject的观察链表的一部分。换句话说,一个观察者在任意时刻只可以观察一个subject。在一些更一般的实现中,每一个subject都维护一个独立的观察者链表,那样一个观察者就可以同时观察多个subject了。
You may be able to live with that limitation. I find it more common for a subject to have multiple observers than vice versa. If it is a problem for you, there is another more complex solution you can use that still doesn’t require dynamic allocation. It’s too long to cram into this chapter, but I’ll sketch it out and let you fill in the blanks… 虽然有这样的限制,但是实际应用应该没有什么影响。因为,我发现,一个subject对象包含多个观察者应该是更普遍的情况,而反过来却不是那么常见。如果这种实现方式不满足你的需求的话,我们还有其它更复杂的方案,你也能够避免动态内存分配。如果再去介绍这些内容,那本章的内容就更臃肿了,因为我还是想让你们去填补这些空白。
###A pool of list nodes ###链表结点池
Like before, each subject will have a linked list of observers. However, those list nodes won’t be the observer objects themselves. Instead, they’ll be separate little “list node” objects that contain a pointer to the observer and then a pointer to the next node in the list. 和之前一样,每一个subject都维护一个观察者列表。但是,现在这些链表结点并不是观察者本身。相反,我们维护一个链表,这个链表里面的节点包含一个指向观察者对象的指针和一个指针下一个结点的指针。
Since multiple nodes can all point to the same observer, that means an observer can be in more than one subject’s list at the same time. We’re back to being able to observe multiple subjects simultaneously. 由于多个链表节点可以指向同一个观察者,那意味着一个观察者可以同时观察多个subject. 现在,我们又可以同时观察多个subject了。
Linked lists come in two flavors. In the one you learned in school, you have a node object that contains the data. In our previous linked observer example, that was flipped around: the data (in this case the observer) contained the node (i.e. the next_ pointer). 使用链表有两个好处。有一个好处是你在学校里面学到的,你有一个链表结点可以包含数据。在我们前面的链表观察者示例中,刚好是反过来的,观察者是数据 ,它包含一个指针下一个链表节点的指针。
The latter style is called an “intrusive” linked list because using an object in a list intrudes into the definition of that object itself. That makes intrusive lists less flexible but, as we’ve seen, also more efficient. They’re popular in places like the Linux kernel where that trade-off makes sense. 后面一种形式的链表叫做“侵入式”链表,因为它的链表结点对象包含一个自身对象。那样使得侵入式链表不那么灵活,但是,正如我们所看到的,会更加高效。它们存在一些Linux内核折中算法中。
The way you avoid dynamic allocation is simple: since all of those nodes are the same size and type, you pre-allocate an object pool of them. That gives you a fixed-size pile of list nodes to work with, and you can use and reuse them as you need without having to hit an actual memory allocator. 我们避免动态内存分配的方法很简单:由于所有的结点都是同样的大小和类型,你可以通过预先分配一个内存对象池。这样你就有了一个固定大小的链表节点池,并且你可以根据需要去重用而不用自己处理一个内存分配子。
##Remaining Problems ##余下的问题 I think we’ve banished the three boogie men used to scare people off this pattern. As we’ve seen, it’s simple, fast, and can be made to play nice with memory management. But does that mean you should use observers all the time? 我想我已经把观察者模式介绍给那些对它恐惧的人了。正如我们所看到的,观察者模式是很简单的,快速的,并且可以与内存管理结合地很紧密。但是,这意味着你在任何时刻都可以使用它吗?
Now, that’s a different question. Like all design patterns, the Observer pattern isn’t a cure-all. Even when implemented correctly and efficiently, it may not be the right solution. The reason design patterns get a bad rap is because people apply good patterns to the wrong problem and end up making things worse. 那么,这又是另外一个问题了。和所有的设计模式一样,观察者模式也不是银弹。即使你正确并且高效地实现了它,它有时候也不总是正确的解决方案。为什么设计模式会遭人诟病,大部分是由于人们对一个错误的问题应用了一个好的设计模式,而且把事情变得更加坏了。
Two challenges remain, one technical and one at something more like the maintainability level. We’ll do the technical one first because those are always easiest. 还存在两个问题,一个是技术性的问题,另外一个是可维护性级别。我们首先来看看技术性的问题,因为其它问题都是最简单的。
###Destroying subjects and observers ###销毁subject和观察者 The sample code we walked through is solid, but it side-steps an important issue: what happens when you delete a subject or an observer? If you carelessly call delete on some observer, a subject may still have a pointer to it. That’s now a dangling pointer into deallocated memory. When that subject tries to send a notification, well… let’s just say you’re not going to have a good time. 我们目前看到的代码示例是健壮的,但是它也显示出了一个重要的问题:当你删除一个观察者或者subject的时候呢?如果你粗心地对观察者对象调用delete方法,此时subject对象可能还持有被删除观察者的引用。此时,我们就有一个悬挂的指针指向了一块被删除的内存。当subject对象尝试对这个指针发送通知的时候,此时……我们的噩梦就来了。
Not to point fingers, but I’ll note that Design Patterns doesn’t mention this issue at all. 不要指向悬挂的指针,但是,我发现设计模式根本没有提到这个问题。
Destroying the subject is easier since in most implementations, the observer doesn’t have any references to it. But even then, sending the subject’s bits to the memory manager’s recycle bin may cause some problems. Those observers may still be expecting to receive notifications in the future, and they don’t know that that will never happen now. They aren’t observers at all, really, they just think they are. 销毁一个subject在大部分实现里面都会更容易一些,因为观察者没有一个指向subject的引用。但是,即使是这样,把subject的内存直接放到回收池里面也容易导致问题。这些观察者还是期望在之后收到通知,但是,现在它们并不清楚这一切。这些观察者已经不再是观察者了。但是,它们还自以为是。
You can deal with this in a couple of different ways. The simplest is to do what I did and just punt on it. It’s an observer’s job to unregister itself from any subjects when it gets deleted. More often than not, the observer does know which subjects it’s observing, so it’s usually just a matter of adding a removeObserver() call to its destructor. 你可以用多种不同的方法来处理这个问题。最简单的方法就是按照我在这里介绍的做。当一个subject对象被删除的时候,观察者本身应该负责把它自身从subject类中移除。
As is often the case, the hard part isn’t doing it, it’s remembering to do it. 和其它情况一样,最难的部分不是做,而是记住要做。
If you don’t want to leave observers hanging when a subject gives up the ghost, that’s easy to fix. Just have the subject send one final “dying breath” notification right before it gets destroyed. That way, any observer can receive that and take whatever action it thinks is appropriate. 如果当一个subject对象被删除的时候,我们不想让观察者来处理的问题,我们可以修复一下。我们只需要在subject对象被删除的时候,给所有的观察者发送一个“死亡通知”就ok了。这样,所有的已注册的观察者都可以收到通知并进行相应的处理。
Mourn, send flowers, compose elegy, etc. People — even those of us who’ve spent enough time in the company of machines to have some of their precise nature rub off on us — are reliably terrible at being reliable. That’s why we invented computers: they don’t make the mistakes we so often do.
A safer answer is to make observers automatically unregister themselves from every subject when they get destroyed. If you implement the logic for that once in your base observer class, everyone using it doesn’t have to remember to do it themselves. This does add some complexity, though. It means each observer will need a list of the subjects it’s observing. You end up with pointers going in both directions. 一个更靠谱的答案是每一个subject对象被删除的时候,所有的观察者都自动取消注册自身。如果你在你的观察者基类里面实现这些逻辑,每一个人都不用记住它。这样做确实添加了不少复杂度,但是,它意味着每一个观察者需要维护一个它观察的subject列表。最后,观察者里面会维护一个双向的指针。
###Don’t worry, I’ve got a GC ###不用担心,我们有GC All you cool kids with your hip modern languages with garbage collectors are feeling pretty smug right now. Think you don’t have to worry about this because you never explicitly delete anything? Think again! 现在很多现代编程语言都有垃圾回收机制了。想一想,你完全不用显示地调用delete操作?再仔细想想!
Imagine this: you’ve got some UI screen that shows a bunch of stats about the player’s character like their health and stuff. When the player brings up the screen, you instantiate a new object for it. When they close it, you just forget about the object and let the GC clean it up. 想象一下:你有一个UI界面,它显示了玩家的许多信息,比如血条,经验值等等。当玩家进入这个状态的时候,你会创建一个新的UI实例。当你把UI界面关闭的时候,你完全可以忘记这个对象,因为垃圾收集器会处理它。
Every time the character takes a punch to the face (or elsewhere, I suppose), it sends a notification. The UI screen observes that and updates the little health bar. Great. Now what happens when the player dismisses the screen, but you don’t unregister the observer? 每一次角色的脸上被打一下(或者其他的事情),它就会发送一个通知。UI界面接收到了这个事件,并且更新血条显示。太好了。那么,当玩家离开场景,并且你没有反注册观察者呢?
The UI isn’t visible anymore, but it won’t get garbage collected since the character’s observer list still has a reference to it. Every time the screen is loaded, we add a new instance of it to that increasingly long list. 此时UI界面不再可见,但是,它也不可能被垃圾回收,因为角色对象的观察者仍然持有玩家的引用。每一次场景重新加载时,我们会添加一个新的UI界面实例到越来越长的观察者链表中。
The entire time the player is playing the game, running around, and getting in fights, the character is sending notifications that get received by all of those screens. They aren’t on screen, but they receive notifications and waste CPU cycles updating invisible UI elements. If they do other things like play sounds, you’ll get noticeably wrong behavior. 玩家整个时间就是玩游戏,跑来跑去,打来打去,我们可以在任意场景里面侦听这个消息。虽然我们的场景可能没有显示,但是,它还是一样会收到通知,一样会消耗这些不可见的CPU时钟。如果它们做其它一些事件,比如播放音乐,你会发现一个更明显的错误 。
This is such a common issue in notification systems that it has a name: the lapsed listener problem. Since subjects retain references to their listeners, you can end up with zombie UI objects lingering in memory. The lesson here is to be disciplined about unregistration. 这是一个在通知系统中普遍存在的问题,叫:消失的观察者。由于subject对象持有其它对象的引用,你最后会导致一些僵尸UI对象。我们学到的经验就是,要及时处理删除观察者。
###What’s going on? ###接下来呢? The other, deeper issue with the Observer pattern is a direct consequence of its intended purpose. We use it because it helps us loosen the coupling between two pieces of code. It lets a subject indirectly communicate with some observer without being statically bound to it. 另外,更深层次的问题是,观察者模式是它的意图的直接结果。我们使用它,是因为它让我们两处代码解耦合了。这种模式让我们能让一个东西间接地与其他观察者通信,而不用静态绑定到它。
This is a real win when you’re trying to reason about the subject’s behavior, and any hangers-on would be an annoying distraction. If you’re poking at the physics engine, you really don’t want your editor — or your mind — cluttered up with a bunch of stuff about achievements. 这是一个正真的双赢,因为当你专注于一件事时,其他任何不相关的事情对于你来说都是恼人的杂事。比如对于物理引擎来说,你不想让你的编辑器,亦或是你的大脑,被一堆杂乱无章的成就搞得乱糟糟的。
On the other hand, if your program isn’t working and the bug spans some chain of observers, reasoning about that communication flow is much more difficult. With an explicit coupling, it’s as easy as looking up the method being called. This is child’s play for your average IDE since the coupling is static. 换句话说,如果你的代码无法工作,且在观察者之间散布一些bug,把这些观察者之间的通信梳理清楚就更为重要了。通过一个显式的耦合,我们可以更容易地理清方法调用的逻辑。
But if that coupling happens through an observer list, the only way to tell who will get notified is by seeing which observers happen to be in that list at runtime. Instead of being able to statically reason about the communication structure of the program, you have to reason about its imperative, dynamic behavior. 但是,如果耦合发生在观察链表之间,唯一的通知方式是检查通知发生时哪个观察者在列表中。为了不去静态地梳理程序的通讯结构,我们不得不去梳理它们动态的、命令式的行为。
My guideline for how to cope with this is pretty simple. If you often need to think about both sides of some communication in order to understand a part of the program, don’t use the Observer pattern to express that linkage. Prefer something more explicit. 我对这种情况的处理办法也非常简单。如果你经常需要为了理解程序的逻辑而去梳理模块之间的调用顺序,那么就不要用观察者模式来表达这种顺序依赖,换用其他更好的方法。
When you’re hacking on some big program, you tend to have lumps of it that you work on all together. We have lots of terminology for this like “separation of concerns” and “coherence and cohesion” and “modularity”, but it boils down to “this stuff goes together and doesn’t go with this other stuff”. 当你正在给一些大的程序打补丁的时候,你可能会涉及里面的很多模块。我们有许多术语来解决这个问题,“关注点分离”、“内聚和耦合”和“模块化”,它们一般都是把不相关的功能模块分离。
The observer pattern is a great way to let those mostly unrelated lumps talk to each other without them merging into one big lump. It’s less useful within a single lump of code dedicated to one feature or aspect. 观察者模式非常适合于一些不相关的模块之间的通信问题。它不适合于单个紧凑的模块内部的通信。
That’s why it fits our example well: achievements and physics are almost entirely unrelated domains, likely implemented by different people. We want the bare minimum of communication between them so that working on either one doesn’t require much knowledge of the other. 这也是为什么它适合我们的例子:成就系统和物理系统是完全不相关的领域,而且很有可能是由不同的人实现的。我们想让他们的通信尽可能地减少,这样任何一个模块都不用怎么依赖另一个模块就可以工作。
##Observers Today ##观察者模式的现状 Design Patterns came out in 1994. Back then, object-oriented programming was the hot paradigm. Every programmer on Earth wanted to “Learn OOP in 30 Days,” and middle managers paid them based on the number of classes they created. Engineers judged their mettle by the depth of their inheritance hierarchies. 设计模式在1994年开始出现。在那个时候,面向对象是一个很火的范式。每一个程序员都想在30天内学会面向对象。一些中级管理者还会为此支付一些付费课程。工程师会为此调整继承层次的结构。
That same year, Ace of Base had not one but three hit singles, so that may tell you something about our taste and discernment back then. 同一年,Ace of Base发行了三首单曲,而不是一首。这可能能让你明白我们的那时品味以及敏锐的洞察力。
The Observer pattern got popular during that zeitgeist, so it’s no surprise that it’s class-heavy. But mainstream coders now are more comfortable with functional programming. Having to implement an entire interface just to receive a notification doesn’t fit today’s aesthetic. 观察者在面向对象时期是很流行的,因此,也它基本上都是基于类来做的。但是,现在主流的程序员对于函数式编程更加熟悉了。为了接收一个通知而去实现整个接口并不符合现在编程美学。
It feels heavyweight and rigid. It is heavyweight and rigid. For example, you can’t have a single class that uses different notification methods for different subjects. 那样做看起来很重量级,并且很死板。它确实是厚重而且死板。比如,你不可以使用单一类来让不同的subject类拥有不同的通知方法。
This is why the subject usually passes itself to the observer. Since an observer only has a single onNotify() method, if it’s observing multiple subjects, it needs to be able to tell which one called it. 这就是为什么subject类有时候把自己传给观察者。因为一个观察者仅仅只有一个onNotify()方法,如果它观察多个对象的话,我们需要知道如何辨别是哪一个subject发送了通知。
A more modern approach is for an “observer” to be only a reference to a method or function. In languages with first-class functions, and especially ones with closures, this is a much more common way to do observers. 一个现代的方法是,对于每一个观察者,它只有一个函数指针或者引用。在一些把函数当作第一级函数(first-class)的语言里面,特别是有闭包的语言里面,这是一种更常见的实现观察者的方式。
These days, practically every language has closures. C++ overcame the challenge of closures in a language without garbage collection, and even Java finally got its act together and introduced them in JDK 8. 现在,基本上每一个编程语言都有闭包。C++通过不引入垃圾收集机制解决了闭包的问题,现在,在JDK8里面,Java也解决了闭包的问题。
If I were designing an observer system today, I’d make it function-based instead of class-based. Even in C++, I would tend toward a system that let you register member function pointers as observers instead of instances of some Observer interface. 如果你现在来实现一个观察者系统,我想把它设计成函数式的,而不是基于类的。甚至在c++里面,我可以让你注册成员函数指针作为观察者,而不用注册一些符合特定接口的指针。
For example, C# has “events” baked into the language. With those, the observer you register is a “delegate”, which is that language’s term for a reference to a method. In JavaScript’s event system, observers can be objects supporting a special EventListener protocol, but they can also just be functions. The latter is almost always what people use. 比如,C#在语言层面就有一个"event"关键字。通过这样,观察者变成了一个"代理",它是在C#里面对一个方法的称呼。在Javascript的事件系统里面,观察者可以是一些符合EventListener协议的对象,但是,它们也可以仅仅是函数。使用函数可能是更多的人愿意做的。
##Observers Tomorrow ##观察者模式的未来 Event systems and other observer-like patterns are incredibly common these days. They’re a well-worn path. But if you write a few large apps using them, you start to notice something. A lot of the code in your observers ends up looking the same. It’s usually something like: 事件系统和其它类似观察者的模式最近都非常流行了。它们是非常成熟的方案。但是,如果你写一些大型的app并使用观察者模式,你就会开始发现一些问题,很多观察者最后看起来都差不多。它经常可能是:
Get notified that some state has changed. 当一些状态改变的时候就会收到通知。
Imperatively modify some chunk of UI to reflect the new state. 修改UI的一些代码来反应新的状态。
It’s all, “Oh, the hero health is 7 now? Let me set the width of the health bar to 70 pixels.” After a while, that gets pretty tedious. Computer science academics and software engineers have been trying to eliminate that tedium for a long time. Their attempts have gone under a number of different names: “dataflow programming”, “functional reactive programming”, etc. 就是这样,“啊,主角的生命值是7了?让我来设置血条的宽度为70像素”过段时间以后,这样做就会感觉很无聊。计算机科学家和软件工程师致力于消除重复乏味的工作已经很多年了。它们还有其它一些名字,比如“数据流编程”,“函数工编程”。
While there have been some successes, usually in limited domains like audio processing or chip design, the Holy Grail still hasn’t been found. In the meantime, a less ambitious approach has started gaining traction. Many recent application frameworks now use “data binding”. 尽管观察者模式取得了一些成功,通常在一些声音处理和芯片设计里面,编程模式的圣杯还是没有被发现。同时,一些不那么雄心勃勃的解决方案也出来了,在许多现在的框架里面,我们都使用“数据绑定(data binding)”。
Unlike more radical models, data binding doesn’t try to entirely eliminate imperative code and doesn’t try to architect your entire application around a giant declarative dataflow graph. What it does do is automate the busywork where you’re tweaking a UI element or calculated property to reflect a change to some value. 和许激进的模式不同的是,数据绑定并不会整个消除命令式代码,也不要尝试去基于一个巨大的数据流图来架构你的整个应用。它做的只不过是自动化的帮你解决了一些繁琐的工作,比如调整ui元素或者重新计算被其他东西影响的内容。
Like other declarative systems, data binding is probably a bit too slow and complex to fit inside the core of a game engine. But I would be surprised if I didn’t see it start making inroads into less critical areas of the game like UI. 像其它声明式系统一样,数据绑定可能会比较慢,并且想要集成到引擎核心里面可能会比较困难。但是,如果我没有在类似UI这样的不太关键的地方看到数据绑定这种结构,我会觉得非常意外。
In the meantime, the good old Observer pattern will still be here waiting for us. Sure, it’s not as exciting as some hot technique that manages to cram both “functional” and “reactive” in its name, but it’s dead simple and it works. To me, those are often the two most important criteria for a solution. 同时,老的观察者模式还是可以等你来使用的。当然,它并没有像一些流行的技术,比如“函数式”和“交互式”一样热门,但是它非常地简单,并且它工作地很好。