I admit honestly, I have not met the description of this pattern, respectively, I invented its name. If anyone has information about the correct name, I will be very happy to hear. The pattern is not language bound, but in this article I will use
C#
.
Picture to attract attention:
')
So, imagine a system consisting of a service that provides other parts of the system to track a set of objects. This can be a simulation service that provides a list of simulated objects or any other similar.
Objects generated and destroyed by the system:
public interface IObject { }
Service providing access to objects:
public delegate void ServiceChangedHandle(IService sender, IObject item, bool injected); public interface IService { IEnumerable<IObject> Items { get; } event ServiceChangedHandle OnServiceChanged; }
Those systems that need to work with objects are subscribed to an event in order to track the appearance of new objects and the disappearance of current ones.
Typical listener example:
public class Listener { public void Initialise() { foreach (var item in service.Items) RegisterItem(item); service.OnServiceChanged += OnServiceChanged; } public void Shutdown() { service.OnServiceChanged -= OnServiceChanged; foreach (var item in service.Items) UnregisterItem(item); } private void OnServiceChanged(IService sender, IObject item, bool injected) { if (injected) RegisterItem(item); else UnregisterItem(item); } private void RegisterItem(IObject item) { ... } private void UnregisterItem(IObject item) { ... } private IService service; }
When a listener is activated, it processes all objects that are already present in the service and then subscribes to changes in the list of objects. Upon completion of the work of the listener, it is necessary to perform the reverse actions
With multithreaded programming, everything becomes more complicated, because between the polling and the subscription, the list of objects may change. The service will need to provide a synchronization object and the listener will block the list change between the list traversal and the subscription.
Multithreading service:
public interface IService { ...
Listener with multithreaded service support (Internal synchronization is omitted):
public class Listener { public void Initialise() {
You can slightly simplify the subscription system, if you guarantee that at the time of subscription and unsubscribe, there is not a single object in the service. It is difficult to give such a guarantee, especially in systems where the time of appearance of services is not certain.
But you can emulate this guarantee for each subscriber, this is the essence of the pattern. When subscribing, the service will forcefully send an object appearance event for all already existing objects and, when unsubscribing, send a disappearing event. In this case, the listener is simplified, and for the multi-threaded and single-threaded version, it will look the same.
Subscriber for multi-threaded and single-threaded service option (Internal synchronization is omitted):
public class Listener { public void Initialise() { service.OnServiceChanged += OnServiceChanged; } public void Shutdown() { service.OnServiceChanged -= OnServiceChanged; } ... }
Implementing a service for a single-threaded version:
public class Service : IService { ... public event ServiceChangedHandle OnServiceChanged { add {
Like any pattern, this version of the listener has its advantages, disadvantages, and scope.
Pros:
- Subscribers are simplified, just a simple subscription and unsubscribe
- Same code for multi-threaded or single-threaded version
Minuses:
- When using, you need to know this feature of the service so that the objects do not process twice
From the minuses and pluses, you can select the scope of the pattern:
- The presence of a small number of services and a large number of subscribers
- Internal product of the company or very personal, to give out such behavior is dangerous
- Strict discipline of design and development. Every developer should be aware of such behavior and where this pattern is specifically used.
Update1
Some explanations:
In Sharpe, the “Observer” pattern has already been implemented; instead of the observer and the observed, we have an event and subscribers.
VIP is not a synonym for the word “one” but a synonym for the word “special.” In this case, all subscribers are special, because the observed object for each individual observer behaves separately. Namely, it generates events that the observer could skip or not wait.
Thank you all for your attention!