如果主题是一个巨大的容器,如何有效地实现观察者模式?
我们都知道观察者模式:您有一个能够通知和更新列表的主题其状态变化的观察者。 现在假设你要观察的主体是一个容器,你想观察容器本身,即元素的添加和删除,以及所包含的元素,即容器元素的状态更新。
当您在容器中存储大量对象时,如何实现更新机制,以便快速插入和删除元素? 特别是,
- 您会在观察者的本地副本中使用相同类型的容器吗?
- 观察者是否应该明智地选择使用容器? (例如,即使您正在观察链表,始终使用平衡树会更快吗?)
- 如何快速将迭代器转换为观察者容器中的迭代器? (对于数组来说很简单,对于链表来说很难?)
如果您的容器是链表,那么您可以在恒定时间内插入元素。 如果 m 个观察者必须迭代包含 n 个元素的列表,则更新需要 O(n * m) 预期时间。
如果你的容器是一个数组,那么更改一个元素需要常数时间,如果你传递元素的索引,更新 m 个观察者需要 O(m) 时间,如果观察者必须迭代数组则需要 O(n * m) 时间。
如果有帮助,请考虑以下示例:
示例 1. 您正在编写一个操作系统。 您想要观察的主题是文件系统及其文件。 您的视图是文件浏览器、索引器和其他应用程序。 您希望在添加、删除或修改文件时更新观察者。
示例 2. 您正在编写一个地址簿应用程序,该应用程序应该能够处理纽约大小的城市。 您想要观察的主题是您的记录的容器(一个人及其地址、电话号码、电子邮件......)。 您的观察者是多个视图,当您添加、删除或修改记录时,这些视图应该自动更新。 (人们可以想象一个视图包含居住在 53 号的人员列表,另一个视图在地图上为每个姓 Doe 的人画点)。
如何处理完整目录子树被删除或“53rd St”重命名为“Dijkstra St”的情况?
We all know the observer pattern: You have a subject which is able to notify and update a list of observers of its state changes. Now suppose that the subject you would like to observe is a container, and you would like to observe the container itself, i.e. element addition and deletion of elements, and also the contained elements, i.e. state updates of the container elements.
How would you implement the update mechanism so that it is fast with respect to element insertion and deletions when you store massive amounts of objects in your container? In particular,
- would you use the same type of container in the local copy of the observers?
- is there a smart choice of container which the observers should use? (For instance, would it be faster to, say, always use balanced trees, even if you are observing a linked list?)
- how do you quickly translate an iterator into the observed container into an iterator into the observer's container? (Trivial for arrays, hard for linked lists?)
If your container is a linked list for instance, then you can insert elements in constant time. If m observers have to iterate through the list containing n elements, then updating takes O(n * m) expected time.
If your container is an array, then changing an element takes constant time, and updating m observers takes O(m) if you pass the element's index, O(n * m) if observers have to iterate through the array.
If it helps, consider the following examples:
Example 1. You are writing an operating system. The subject you would like to observe is the filesystem and its files. Your views are a file explorer, an indexer, and other applications. You would like to update the observers when files are added, deleted or modified.
Example 2. You are writing an address book application which should be able to handle a city of the size of New York. The subject you would like to observe is the container of your records (a person with its address, phone numbers, email...). Your observers are several views, which should update automatically when you add, delete or modify a record. (One could image one view containing a list of people who live on 53rd and another drawing dots on a map for each person whose last name is Doe).
How do you handle the case that a complete directory-subtree is deleted or that "53rd St" is renamed to "Dijkstra St"?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
无论如何,你必须把容器变成一个主题。
这里的主要问题是找到一种有效的方法来注意到变化。 大多数时候,当你遇到这个问题时,因为你想要观察的事物没有提供有效的通知机制(可能是因为观察者设计模式在编写它们时还没有发明) )。
[编辑]既然你要求一种有效的方法,一般的答案是“这取决于”。 设计模式没有“一刀切”的解决方案。 它们是如何解决问题的一般规则。 在特定情况下如何实施规则是你在特定情况下解决的问题。
一般来说,如果您的观察者需要识别小的更改(即属性更改或添加元素),则通知消息应包含足够的信息,以便他们可以有效地执行此操作。 因此,如果您有一个大列表和一个插入,请发送列表和新元素的索引加上“插入的项目”。
对于属性的改变,有两种解决方案。 一种是为列表中的每个元素添加一个观察者。 这可能很慢并且需要大量 RAM,但这意味着您可以将多种类型添加到同一列表中。
或者,您可以使用“修改列表服务中的项目”。 这意味着禁止直接更改项目,您必须始终使用该服务。 然后,该服务可以作为主题工作,并发送包含该项目、旧值和更改值以及可能包含列表中索引的通知。
[EDIT2] 一般规则是收集尽可能多的有关变化的信息并将其传递给观察者。 但这实际上取决于您的具体问题。 假设观察者坐在远程机器上。 在这种情况下,没有有效的方法向其发送整个列表。 您只能发送“项目 X 已插入”并希望这就足够了。 如果容器无法注意到变化(例如,网站上的新网页),则容器必须一次又一次地遍历整个站点以找到变化,然后才能以有效的方式告诉观察者。
同样,细节确实取决于具体情况。 谷歌运行着数千个网络蜘蛛,每小时访问数百万个网页。 长期以来,这是“有效的”(就像“唯一的方法”)。 不久前,实施了“站点地图”协议,该协议允许管理员将其网站转变为可以告诉 Google 观察者有关更改的主题。
因此,除非您能给出更具体的示例,否则我无法给您更具体的答案。 对于设计模式,有一个时刻你需要坐下来,解决一个真正的问题并开动你的大脑。
[EDIT3] 以下是观察者模式的几个使用示例:
许多 UI 框架使用此模式将事件传播给感兴趣的各方。 在 Qt 中,您有一个中心位置,所有主题都可以在其中注册其信号(他们将发送的通知),并且观察者可以在其中附加到主题。 这意味着所有连接都在一个位置进行管理。 优点是您不需要将此数据结构添加到每个对象中。 此外,来自外部的对象(非 Qt 对象)可以发送和接收消息。 由于一切都在一个地方,因此可以轻松优化该数据结构。 缺点是这种结构可能会变得非常大,因此当涉及的各方较多时(甚至是完全不相关的各方),发送消息将花费更多时间。
Google 使用站点地图协议将网站转变为主题,因为这比一遍又一遍地遍历整个网站要高效得多,即使您只请求 URL 的最后修改时间(HTTP HEAD 而不是 HTTP GET)。< /p>
Windows 和 Linux 中的文件系统提供通知来告知应用程序有关新文件或已删除文件的信息。 这里的主要问题是当应用程序未运行时文件发生更改时会发生什么。 假设您有一个应用程序可以维护目录中文件的校验和。 显然,您想了解应用程序关闭时的更改,但这意味着通知服务必须跟踪它发送的最后一次更改。 因此,在这里,应用程序必须在启动时读取整个树,以查看它可能错过的任何内容,并且需要使用观察者模式来了解运行时发生的更改。
邮件客户端是一个观察者。 它将告诉邮件服务器它所看到的最后一封电子邮件的 ID,服务器将告诉它任何新的电子邮件。
当复杂模型中有大量属性更改时,这通常是集中所有更改(使它们在单个位置运行)并将观察者附加到那里的唯一方法(而不是将 N 个观察者附加到 M 个单独的对象)。 在此实现中,观察者可以说“我对任何地方的任何变化感兴趣”或“任何主题中领域 X 的变化”或“主题 Y 中的任何变化”(最后一个通常兼作“领域变化”) X in subject Y” - 观察者将简单地忽略对字段的更改!= X)。
Somehow, you must turn the container into a subject.
The main problem here is to find an efficient way to notice changes. Most of the time when you run into this problem, it's because the thing you want to observe doesn't offer an efficient notification mechanism (probably because the observer design pattern wasn't invented when they thing was written).
[EDIT] Since you ask for an efficient way, the general answer is "it depends". Design patterns don't have a "one-size-fits-all" solution. They are general rules how to approach a problem. How you need to implement the rules in a specific situation is something that you solve when you're in the situation.
Generally, if your observers need to identify small changes (i.e. an attribute change or adding an element), the notification message should contain enough information that they can do this efficiently. So if you have a big list and an insert, send the list and the index of the new element plus "item as inserted".
As for attribute changes, there are two solutions. One is to add an observer to every element in the list. This can be slow and need a lot of RAM but it means you can add several types into the same list.
Alternatively, you can have a "modify item in list service". This means that it's forbidden to change items directly, you must always use the service. The service can then work as a subject and send notifications with the item, the old and changed value and possibly with the index in the list.
[EDIT2] The general rule is to collect as much information about the change as possible and pass that to the observers. But it really depends on your specific problem. Let's say the observer is sitting on a remote machine. In this case, there is no efficient way to send it the whole list. You can only send it "item X was inserted" and hope that's enough. If the container has no way to notice changes (for example, new web pages on a web site), the container has to traverse the whole site again and again to find changes which it can then tell the observers in an efficient manner.
Again, the details really depend on the specific situation. Google runs thousand of web spiders which visit millions of web pages every hour. For a long time, this was "efficient" (as in "the only way"). A while ago, the "sitemap" protocol was implemented which allows admins to turn their web sites into subjects that can tell the Google observer about changes.
So unless you can give a more specific example what you need to do, I can't give you a more specific answer. With design patterns, there is a point where you need to sit down, take a real problem and turn on your brain.
[EDIT3] Here are a couple of examples for uses of the observer pattern:
Many UI frameworks use this pattern to spread events to interested parties. In Qt, you have a central spot where all subjects can register their signals (notifications they will send) and where observers can attach to subjects. This means there is a single spot where all connections are managed. The advantage is that you don't need to add this data structure to every object. Also, objects from outside (non-Qt objects) can send and receive messages. Since everything is in a single place, this data structure can be optimized easily. The drawback is that this structure can become very big, so sending a message will take more time when there are more parties involved (even ones which are completely unrelated).
Google uses the sitemap protocol to turn web sites into subjects since that's much more efficient than traversing the whole site again and again, even if you only request the last modification time of a URL (HTTP HEAD instead of HTTP GET).
Filesystems in Windows and Linux offer notifications to tell applications about new or deleted files. The main problem here is what should happen when files change while an application doesn't run. Say you have an app which maintains checksums of files in a directory. Obviously, you'd like to know about changes when the app was down but that would mean the notification service would have to keep track of the last change it sent. So here, the app has to read the whole tree in at startup to see anything it might have missed and it needs to use the observer pattern for changes happening while it runs.
A mail client is an observer. It will tell the mail server the ID of the last email it has seen and the server will tell it about any new ones.
When you have lots of attribute changes in a complex model, it's usually the only way to centralize all changes (make them run through a single place) and attach the observers there (instead of attaching N observers to M individual objects). In this implementation, the observers can say "I'm interested in any change anywhere" or "a change of the field X in any subject" or "any change in subject Y" (the last one usually doubles as a "change of field X in subject Y" - the observer will simply ignore changes to fields != X).
为什么不使用观察者模式本身呢?
主体需要向观察者告知有趣的事件。 然后观察者将其发送给感兴趣的各方(订阅者)。
主题的性质在这里并不重要。 (除非我错误地理解了你的问题)。
Why not observer pattern itself ?
The subject needs to inform the observer about the interesting events. Then the observer shall dispatch it to interested parties (subscribers).
The nature of the subject is not of any significance here. (Unless I understood your question wrong).