Server [Introduction]
Microsoft's top technology guru Jeffrey Richter's work has always been a must-see. This article is no exception to help developers with this monograph on Observer mode. Observer mode is one of the most widely used and flexible models in classical design mode. This article in. NET technology under the framework of in-depth exploration of the connotation of the observer model, it is worth to savor.
While design patterns are not universal Dan, it is really a very powerful tool that developers or architects can use to actively participate in any project. Design patterns ensure that common problems are addressed through well-known and recognized solutions. The fact that a pattern exists is based on: Most problems may have been solved by other individuals or development teams. Therefore, patterns provide a way to share the available solutions between developers and organizations. Whatever the source of these patterns, these patterns take advantage of the accumulated knowledge and experience. This ensures faster development of the correct code and reduces the likelihood of errors in the design or implementation. In addition, design patterns provide common terminology among engineering team members. People who have participated in large development projects know that using a common set of design terms and guidelines is critical to successful project completion. Most importantly, design patterns can save you a lot of time if you can use them correctly.
. NET Framework Model
Although Gof's examples are limited to C + + and Smalltalk, design patterns are not bundled with specific languages or development platforms, Microsoft. NET Framework presents new opportunities and environments for analyzing design patterns. Microsoft has applied many GOF patterns during the development of the Framework class Library (FCL). Because. NET Framework provides a wide range of functionality, and a number of new patterns have been developed and presented.
Our research on design patterns starts with the Observer model.
Observer mode
One of the leading principles of object-oriented development is the proper partitioning of tasks within a given application. Each object in the system should focus on discrete abstractions in the problem domain. In short, an object should only do one thing and do it well. This approach ensures clear boundaries between objects, thus providing greater reusability and maintainability of the system.
A particularly important area is the interaction between the user interface and the underlying business logic. It is commonplace to change the user interface quickly during the development of an application and not have a knock-on effect on other parts of the application. In addition, business requirements can change, regardless of the user interface. People with extensive development experience know that in many cases both sets of requirements will change. If the UI and other parts of the application are not divided, any part of the modification will adversely affect the whole.
Many applications need to divide clear boundaries between the user interface and the business logic. As a result, many object-oriented frameworks support the partitioning of the user interface from other parts of the application since the advent of the GUI. Most of these applications use almost the same design pattern. This pattern is often called an observer, and it is very helpful in dividing clear boundaries between the various objects in the system. In addition, you will often see the use of this solution in a UI-independent part of a framework or application. The observer model is far more effective than its original idea.
Logical model
Although the Observer pattern has many variants, the basic premise of the pattern consists of two roles: Observer (Observer) and Principal (subject) (those who are familiar with Smalltalk MVC refer to these terms as view and model respectively). In the user interface environment, the observer is the object that is responsible for displaying the data to the user. On the other hand, the principal represents a business abstraction modeled from the problem domain. As depicted in Figure 1, there is a logical association between the observer and the subject. When a change occurs in a Principal object (for example, by modifying an instance variable), the Observer observes the change and updates its display accordingly.
For example, suppose we want to develop a simple application that tracks stock prices throughout the day. In this application, we specify a stock class to simulate the various stocks traded on Nasdaq. The class contains an instance variable that represents a stock price that fluctuates frequently over the whole day. To display this information to the user, the application uses a Stockdisplay class to write information to the STDOUT (standard output). In this application, a stock class instance is used as the principal and a Stockdisplay class instance as the Observer. As the stock price changes over time over the trading day, the current share price of the stock instance will change (it doesn't matter how it changes). Because the Stockdisplay instance is looking at the stock instance, these changes are displayed to the user when these states change (the price is modified).
By using this observation process, boundaries can be divided between the stock and the Stockdisplay class. Assume that the application's requirements change the next day and that you want to use a form-based user interface. To enable this new feature, you only need to construct a new class Stockform as an observer. No matter what happens, the stock class does not need to make any changes. In fact, it does not even know that such changes have occurred. Similarly, if demand changes require that the stock class retrieve price information from another source (possibly from a Web service rather than from a database), the Stockdisplay class does not need to be modified. It just keeps looking at the stock and it's enough.
Physical model
As with most solutions, the problem is the details. Observer mode is no exception. Although the logical model prescribes the observer to observe the subject, it is actually a misuse of the name when implementing this pattern. More precisely, the observer registers with the subject, indicating its willingness to observe the subject. When a state changes, the subject informs the observer of the change. When the observer no longer wishes to observe the subject, the observer revokes the principal. These steps are called observer Registration, notification, and revocation registration, respectively.
Most frameworks implement registration and notification through callbacks. The UML sequence diagrams shown in Figures 2, 3, and 4 simulate the objects and method calls that this method typically uses. For people unfamiliar with sequence diagrams, the top rectangular box represents the object, and the arrows represent method invocations.
Figure 2 describes the registration sequence. The observer invokes the Register method on the principal to pass itself as a parameter. After the principal receives this reference, it must store it so that it notifies the observer at a later time when the state of the event changes. Most observer implementations do not store the observer reference directly in the instance variable, but instead delegate the task to a separate object (usually a container). Using a container to store an observer instance can provide a great benefit, and we'll give a brief introduction to it.
Figure 3 highlights the notification sequence. When the state is changed (Askprice
Changed), the principal retrieves all the observers in the container by calling the Get observer S method. The principal then enumerates the retrieved observers and invokes the Notify method to inform the observer of the state change that occurred.
Figure 4 shows the undo registration sequence. This sequence is performed when the observer no longer needs to observe the body. The observer invokes the unregister method and passes itself as a parameter. The body then calls the Remove method on the container to end the observation process.
Back to our stock application, let's analyze the impact of the registration and notification process. During the application startup process, a Stockdisplay class instance is registered in the stock instance and passed itself as an argument to the Register method. The stock instance (in the container) holds a reference to the Stockdisplay instance. When the price property changes, the stock instance notifies the Stockdisplay by calling the Notify method. When the application shuts down, the Stockdisplay instance uses the following method to unregister the stock instance: Call the Unregister method and terminate the relationship between the two instances.
Note the advantages of storing the observer reference using the container rather than the instance variable. Assuming that in addition to the current user interface Stockdisplay, we also need to draw the stock price in the trading day changes in real-time graphics. To do this, we created a new class called Stockgraph, which draws a graph of the stock price (y-axis) and the day (x axis). At the time the application starts, it registers instances of the Stockdisplay and Stockgraph classes in the stock instance. Because the body stores the observer in the container (as opposed to the instance variable), there is no problem. When the share price changes, the stock instance notifies the two observer instances in its container of the state change that occurred. As we can see, the use of containers provides greater flexibility, that is, each body can support multiple observers. This makes it possible for the subject to notify an infinite number of observers of the state changes that occur, rather than just notifying one observer.
Although not mandatory, many frameworks provide observers and principals with a set of interfaces to implement. As shown in the following C # code example, the IObserver interface exposes a public method notify. This interface is implemented by all classes that are to be used as observers. The IObservable interface (which is implemented by all classes to be used as a principal) exposes two methods, register and unregister. These interfaces typically take the form of abstract virtual classes or real interfaces (if the implementation language supports such constructs). The use of these interfaces helps to reduce the coupling between the observer and the subject. Unlike the tight coupling relationship between the observer and the principal class, the IObserver and IObservable interfaces allow for implementation-independent operations. By analyzing the interface, you will notice that all the methods you type are for the interface type (as opposed to the concrete class). This approach extends the benefits of the interface programming model to the Observer pattern.
IObserver and IObservable interfaces (C #)
Interface The all observer classes should implement
Public interface IObserver {
void Notify (object anobject);
}//iobserver
Interface that all observable classes should implement
Public interface IObservable {
Back to our sample application, we know that the stock class is used as a principal. Therefore, it implements the IObservable interface. Similarly, the Stockdisplay class implements the IObserver interface. Because all operations are defined by the interface (rather than by a specific class), the stock class is not bound to the Stockdisplay class, and vice versa. This allows us to quickly change specific observer or principal implementations without affecting other parts of the application (replacing stockdisplay with different observers or adding additional observer instances).
In addition to these interfaces, the framework often provides a common base class for principals, reducing the work required to support the Observer pattern. The base class implements the IObservable interface to provide the infrastructure needed to support observer instance storage and notification. The following C # code example briefly describes a base class named Observableimpl. Although it is possible for any container to complete this task, the class delegates The Observer store to the hash table instance in the Register and unregister methods (for convenience, we use a hash table as a container in the example, which uses only one method call to unregister a specific observer instance). Also note that the Notifyobservers method is added. This method is used to notify the observers stored in the hash table. When this method is called, the container is enumerated and the Notify method is invoked on the Observer instance.
Observableimpl class (C #)
Helper class that implements observable interface
public class Observableimpl:iobservable {
Container to store the Observer instance (isn't synchronized for
This example)
Protected Hashtable _observercontainer=new Hashtable ();
Add the Observer
public void Register (IObserver anobserver) {
_observercontainer.add (Anobserver,anobserver);
}//register
Remove the Observer
public void Unregister (IObserver anobserver) {
_observercontainer.remove (Anobserver);
}//unregister
Common to notify all the observers
public void Notifyobservers (object anobject) {
Enumeration the observers and invoke their Notify method
foreach (IObserver anobserver in _observercontainer.keys) {
Anobserver.notify (AnObject);
}//foreach
}//notifyobservers
}//observableimpl
Our sample application takes advantage of this base class infrastructure by modifying the stock class to extend the Observableimpl class rather than providing its own specific iobservable interface implementation. Because the Observableimpl class implements the IObservable interface, you do not need to make any changes to the Stockdisplay class. In fact, this method simplifies the implementation of the observer pattern, while maintaining loosely coupled relationships between the classes, allowing multiple principals to reuse the same functionality.
Below the. NET Observer example focuses on the use of the iobservable and IObserver interfaces and Observablebase classes in our stock applications. In addition to the stock and Stockdisplay classes, this example associates the observer with the principal instance using MainClass and modifies the Askprice property of the stock instance. This property is responsible for calling the Notifyobservers method of the base class, which notifies the instance of the associated state change.
Observer sample (C #)
Represents a application
public class Stock:observableimpl {
Instance variable for ask price
Object _askprice;
Property for Ask Price
public Object Askprice {
set {_askprice=value;
Base. Notifyobservers (_askprice);
}//set
}//askprice Property
}//stock
Represents the user interface in the application
public class Stockdisplay:iobserver {
public void Notify (object anobject) {
Console.WriteLine ("The New ask:" + anobject);
}//notify
}//stockdisplay
public class mainclass{
public static void Main () {
Create new display and stock instances
Stockdisplay stockdisplay=new Stockdisplay ();
Stock Stock=new the stock ();
Register the Grid
Stock. Register (Stockdisplay);
Loop times and modify the Ask price
for (int looper=0;looper < 100;looper++) {
Stock. Askprice=looper;
}
Unregister the display
Stock. Unregister (Stockdisplay);
}//main
}//mainclass
. Observer pattern in the NET Framework
Based on our understanding of the observer model, let's turn our attention to this pattern in. NET Framework. Those of you who are very familiar with the types exposed in FCL will notice that there are no iobserver, iobservable, or Observableimpl types in the framework. Although you can indeed be in. NET applications, the introduction of delegates and events provides a new, powerful way to implement observer patterns without having to develop specific types that are specifically designed to support the pattern. In fact, because delegates and events are members of the CLR, the basic constructs of this pattern are added to. The core of the NET Framework. As a result, FCL uses observer patterns extensively in its structure.
There are a lot of articles about how delegates and events work internally, and we don't repeat them here. Suffice it to say that the delegate is the object-oriented (and type-safe) version of the function pointer. A delegate instance holds a reference to an instance or class method, allowing anonymous invocation of the binding method. An event is a special construct declared on a class that publishes state changes to the object of concern at run time. The event represents the form abstraction we used to implement the registration, revocation, and notification methods of the observer pattern (the CLR and many different compilers support it). Delegates are registered to a specific event at run time. When an event is raised, all registered delegates are invoked to enable them to receive notification of the event.
In terms defined in the Observer pattern, the class that declares the event is the principal. Unlike the IObservable interface and the Observableimpl class we used previously, the principal class does not need to implement a given interface or extend the base class. The body only needs to expose one event without any other action. The observer creates a little more work, but the flexibility is much higher (which we'll discuss later). Instead of implementing the IObserver interface and registering itself in the body, the observer must create a specific delegate instance and register the delegate with the principal event. The observer must use a delegate instance of the type specified by the event declaration, or the registration will fail. During the creation of this delegate instance, the Observer passes the method (instance or static) name that the principal notifies the delegate. After you bind a delegate to a method, you can register it with the subject's event. Similarly, you can unregister this delegate from an event. The principal provides notification to the observer by invoking the event.
If you are unfamiliar with delegates and events, implementing the observer pattern seems to require a lot of work, especially compared to the iobserver and IObservable interfaces we used previously. But it's simpler than it sounds, and it's much easier to implement. The following C # and visual Basic. NET code example highlights the class modifications that are required to support delegates and events in our sample application. Note that no stock or Stockdisplay class is used to support any base class or interface of the pattern.
Observers who use delegates and events (C #)
public class Stock {
Declare a delegate for the event
public delegate void Askpricedelegate (object aprice);
Declare the event using the delegate
public event Askpricedelegate Askpricechanged;
Instance variable for ask price
Object _askprice;
Property for Ask Price
public Object Askprice {
set {
Set the instance variable
_askprice=value;
Fire the event
Askpricechanged (_askprice);
}
}//askprice Property
}//stock class
Represents the user interface in the application
public class Stockdisplay {
public void Askpricechanged (object aprice) {
Console.Write ("The new Ask Price is:" + Aprice + "\ r \ n");}
}//stockdispslay class
public class MainClass {
public static void Main () {
Create new display and stock instances
Stockdisplay stockdisplay=new Stockdisplay ();
Stock Stock=new the stock ();
Create a new delegate instance and bind it
To the Observer ' s askpricechanged method
Stock.askpricedelegate adelegate=new
Stock.askpricedelegate (stockdisplay.askpricechanged);
Add the delegate to the event
Stock. Askpricechanged+=adelegate;
Loop times and modify the Ask price
for (int looper=0;looper < 100;looper++) {
Stock. Askprice=looper;
}
Remove the delegate from the event
Stock. Askpricechanged-=adelegate;
}//main
}//mainclass
Once you are familiar with the delegates and events, you will clearly see their great potential. Unlike the IObserver and IObservable interfaces and the Observableimpl classes, using delegates and events can significantly reduce the amount of work required to implement this pattern. The CLR and the compiler provide the basis for observer container management, and provide a common calling convention for registering, Unregistering, and notifying observers. Perhaps the greatest advantage of a delegate is its ability to refer to the inherent characteristics of any method (provided it conforms to the same signature). This allows any class to be used as an observer, regardless of the interface it implements or the class it is dedicated to. Although using the IObserver and iobservable interfaces reduces the coupling between the observer and the principal class, the use of delegates eliminates these coupling relationships completely.
Event mode
Based on events and delegates, FCL can use the Observer pattern very extensively. FCL's designers are fully aware of the great potential of this pattern and apply it throughout the framework to user interfaces and non-UI-specific features. However, the usage is slightly different from the basic observer pattern, which the framework team calls the event pattern.
Typically, this pattern is represented as a formal naming convention for delegates, events, and related methods involved in the event notification process. Although the CLR or standard compiler does not enforce this pattern for all applications and frameworks that exploit events and delegates, Microsoft recommends doing so.
The first of these conventions may also be the most important convention is the name of the event exposed by the principal. For the state change it represents, this name should be self-explanatory. Remember that this convention and all other such conventions are subjective in themselves. The goal is to provide clear instructions for those who take advantage of your events. Other parts of the event pattern are named after the correct event, so this step is critical to the pattern.
Back to our example, let's analyze the impact of this agreement on the stock class. The appropriate way to derive an event name is to use the name of the field modified in the principal class as the root. Because the field name that is modified in the stock class is _askprice, the reasonable event name should be askpricechanged. Obviously, the name of this event is more descriptive than the statechangedinstockclass. Therefore, the Askpricechanged event name conforms to the first convention.
The second convention in the event pattern is to correctly name the delegate and its signature. The delegate name should contain the event name (selected by the first convention) and the additional word handler. This mode requires the delegate to specify two parameters, the first parameter provides a reference to the sender of the event, and the second parameter provides the observer with ambient information. The name of the first parameter is sender. This parameter must be typed as System.Object. This is due to the fact that a delegate might be bound to any potential method on any class in the system. The name of the second parameter (even simpler than the first argument) is E. You must type this parameter as a System.EventArgs or some derived class (sometimes more than this content). Although the return type of the delegate depends on your implementation needs, most delegates that implement this pattern do not return any values at all.
Requires a little attention to the second parameter e of the delegate. This parameter allows the principal object to pass arbitrary ambient information to the observer. If this type of information is not required, it is sufficient to use the System.EventArgs instance because an instance of this class represents no environment data. Otherwise, the class derived from System.EventArgs should be constructed using the corresponding implementation to provide this data. The class must be named according to the event name with the additional word eventargs.
Please refer to our stock class, which requires that the delegate handling the Askpricechanged event be named Askpricechangedhandler. In addition, the second parameter of this delegate should be named Askpricechangedeventargs. Because we need to pass the new stock price to the observer, we need to extend the System.EventArgs class to name the class Askpricechangedeventargs and provide implementations to support the delivery of this data.
The last contract in the event pattern is the name and accessibility of the method on the principal class that is responsible for raising the event. The name of this method should contain the event name and the added on prefix. The accessibility of this method should be set to protection. This Convention applies only to unsealed (not inheritable in VB) classes, because it serves as a derived class to call the known call point of the observer registered in the base class.
You can complete the event pattern by applying this last contract to the stock class. Because the stock class is not sealed, we must add a method to raise the event. In this mode, the name of this method is onaskpricechanged. The following C # code example shows the full view of the event pattern applied to the stock class. Please note the special usage of our System.EventArgs class.
Event Pattern sample (C #)
public class Stock {
Declare a delegate for the event
public delegate void Askpricechangedhandler (object sender,
Askpricechangedeventargs e);
Declare the event using the delegate
public event Askpricechangedhandler Askpricechanged;
Instance variable for ask price
Object _askprice;
Property for Ask Price
public Object Askprice {
set {
Set the instance variable
_askprice=value;
Fire the event
Onaskpricechanged ();
}
}//askprice Property
method to fire event delegate with proper name
protected void onaskpricechanged () {
Specialized event class for the Askpricechanged event
public class Askpricechangedeventargs:eventargs {
Instance variable to store the Ask Price
Private Object _askprice;
constructor that sets Askprice
Public Askpricechangedeventargs (Object askprice) {_askprice=askprice;}
Public to the Ask Price
Public object Askprice {get {return _askprice;}}
}//askpricechangedeventargs
Conclusion
Based on the analysis of the observer pattern here, we can see clearly that this pattern provides a perfect mechanism to delimit clear boundaries between objects in the application. Although implementation via callbacks (using the IObserver and IObservable interfaces) is fairly straightforward, the CLR's delegate and event concepts handle most "heavy work" and lower the coupling level between the subject and the observer. In fact, by using this pattern correctly, you will take a big step forward in ensuring that your application evolves. When your UI and business requirements change over time, the Observer model ensures that you can simplify your work.
Design patterns are a powerful tool for developing flexible applications, if used effectively. This article is written to illustrate the effectiveness of the model approach and to highlight. NET Framework, a pattern used in the Future articles will continue to explore the patterns in FCL and briefly describe some of the patterns used to generate valid Web services.
The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion;
products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the
content of the page makes you feel confusing, please write us an email, we will handle the problem
within 5 days after receiving your email.
If you find any instances of plagiarism from the community, please send an email to:
info-contact@alibabacloud.com
and provide relevant evidence. A staff member will contact you within 5 working days.