Tải bản đầy đủ - 0 (trang)
13-11. Implement the Observer Pattern

13-11. Implement the Observer Pattern

Tải bản đầy đủ - 0trang

CHAPTER 13 ■ COMMONLY USED INTERFACES AND PATTERNS







TemperatureEventHandler delegate, which defines the signature of the method that

all observers of a Thermostat object must implement and that a Thermostat object

will call in the event of temperature changes







TemperatureChangeObserver and TemperatureAverageObserver classes, which are

observers of the Thermostat class



The TemperatureChangeEventArgs class (in the following listing) derives from the class

System.EventArgs. The custom event argument class should contain all of the data that the subject needs

to pass to its observers when it notifies them of an event. If you do not need to pass data with your event

notifications, you do not need to define a new argument class; simply pass EventArgs.Empty or null as

the argument when you raise the event. (See recipe 13-9 for details on implementing custom event

argument classes.)

namespace Apress.VisualCSharpRecipes.Chapter13

{

// An event argument class that contains information about a temperature

// change event. An instance of this class is passed with every event.

public class TemperatureChangedEventArgs : EventArgs

{

// Private data members contain the old and new temperature readings.

private readonly int oldTemperature, newTemperature;

// Constructor that takes the old and new temperature values.

public TemperatureChangedEventArgs(int oldTemp, int newTemp)

{

oldTemperature = oldTemp;

newTemperature = newTemp;

}

// Read-only properties provide access to the temperature values.

public int OldTemperature { get { return oldTemperature; } }

public int NewTemperature { get { return newTemperature; } }

}

}

The following code shows the declaration of the TemperatureEventHandler delegate. Based on this

declaration, all observers must implement a method (the name is unimportant) that returns void and

takes two arguments: an Object instance as the first argument and a TemperatureChangeEventArgs object

as the second. During notification, the Object argument is a reference to the Thermostat object that

raises the event, and the TemperatureChangeEventArgs argument contains data about the old and new

temperature values.

namespace Apress.VisualCSharpRecipes.Chapter13

{

// A delegate that specifies the signature that all temperature event

// handler methods must implement.

public delegate void TemperatureChangedEventHandler(Object sender,

TemperatureChangedEventArgs args);

}



664



www.it-ebooks.info



CHAPTER 13 ■ COMMONLY USED INTERFACES AND PATTERNS



For the purpose of demonstrating the Observer pattern, the example contains two different observer

types: TemperatureAverageObserver and TemperatureChangeObserver. Both classes have the same basic

implementation. TemperatureAverageObserver keeps a count of the number of temperature change

events and the sum of the temperature values, and displays an average temperature when each event

occurs. TemperatureChangeObserver displays information about the change in temperature each time a

temperature change event occurs.

The following listing shows the TemperatureChangeObserver and TemperatureAverageObserver

classes. Notice that the constructors take references to the Thermostat object that the

TemperatureChangeObserver or TemperatureAverageObserver object should observe. When you instantiate

an observer, pass it a reference to the subject. The observer must create a delegate instance containing a

reference to the observer’s event-handler method. To register as an observer, the observer object must

then add its delegate instance to the subject using the subject’s public event member. This is made even

easier with the simplified delegate syntax provided by C#, where it is no longer required to explicitly

instantiate a delegate to wrap the listening method.

Once the TemperatureChangeObserver or TemperatureAverageObserver object has registered its

delegate instance with the Thermostat object, you need to maintain a reference to this Thermostat object

only if you want to stop observing it later on. In addition, you do not need to maintain a reference to the

subject, because a reference to the event source is included as the first argument each time the

Thermostat object raises an event through the TemperatureChange method.

namespace Apress.VisualCSharpRecipes.Chapter13

{

// A Thermostat observer that displays information about the change in

// temperature when a temperature change event occurs.

public class TemperatureChangeObserver

{

// A constructor that takes a reference to the Thermostat object that

// the TemperatureChangeObserver object should observe.

public TemperatureChangeObserver(Thermostat t)

{

// Create a new TemperatureChangedEventHandler delegate instance and

// register it with the specified Thermostat.

t.TemperatureChanged += this.TemperatureChange;

}

// The method to handle temperature change events.

public void TemperatureChange(Object sender,

TemperatureChangedEventArgs temp)

{

Console.WriteLine ("ChangeObserver: Old={0}, New={1}, Change={2}",

temp.OldTemperature, temp.NewTemperature,

temp.NewTemperature - temp.OldTemperature);

}

}

// A Thermostat observer that displays information about the average

// temperature when a temperature change event occurs.

public class TemperatureAverageObserver

{



665



www.it-ebooks.info



CHAPTER 13 ■ COMMONLY USED INTERFACES AND PATTERNS



// Sum contains the running total of temperature readings.

// Count contains the number of temperature events received.

private int sum = 0, count = 0;

// A constructor that takes a reference to the Thermostat object that

// the TemperatureAverageObserver object should observe.

public TemperatureAverageObserver(Thermostat t)

{

// Create a new TemperatureChangedEventHandler delegate instance and

// register it with the specified Thermostat.

t.TemperatureChanged += this.TemperatureChange;

}

// The method to handle temperature change events.

public void TemperatureChange(Object sender,

TemperatureChangedEventArgs temp)

{

count++;

sum += temp.NewTemperature;

Console.WriteLine

("AverageObserver: Average={0:F}", (double)sum / (double)count);

}

}

}

Finally, the Thermostat class is the observed object in this Observer (Event) pattern. In theory, a

monitoring device sets the current temperature by calling the Temperature property on a Thermostat

object. This causes the Thermostat object to raise its TemperatureChange event and send a

TemperatureChangeEventArgs object to each observer.

The example contains a Recipe13_11 class that defines a Main method to drive the example. After

creating a Thermostat object and two different observer objects, the Main method repeatedly prompts

you to enter a temperature. Each time you enter a new temperature, the Thermostat object notifies the

listeners, which display information to the console. The following is the code for the Thermostat class:

namespace Apress.VisualCSharpRecipes.Chapter13

{

// A class that represents a Thermostat, which is the source of temperature

// change events. In the Observer pattern, a Thermostat object is the

// subject that Observers listen to for change notifications.

public class Thermostat

{

// Private field to hold current temperature.

private int temperature = 0;

// The event used to maintain a list of observer delegates and raise

// a temperature change event when a temperature change occurs.

public event TemperatureChangedEventHandler TemperatureChanged;

// A protected method used to raise the TemperatureChanged event.

// Because events can be triggered only from within the containing



666



www.it-ebooks.info



CHAPTER 13 ■ COMMONLY USED INTERFACES AND PATTERNS



// type, using a protected method to raise the event allows derived

// classes to provide customized behavior and still be able to raise

// the base class event.

virtual protected void OnTemperatureChanged

(TemperatureChangedEventArgs args)

{

// Notify all observers. A test for null indicates whether any

// observers are registered.

if (TemperatureChanged != null)

{

TemperatureChanged(this, args);

}

}

// Public property to get and set the current temperature. The "set"

// side of the property is responsible for raising the temperature

// change event to notify all observers of a change in temperature.

public int Temperature

{

get { return temperature; }

set

{

// Create a new event argument object containing the old and

// new temperatures.

TemperatureChangedEventArgs args =

new TemperatureChangedEventArgs(temperature, value);

// Update the current temperature.

temperature = value;

// Raise the temperature change event.

OnTemperatureChanged(args);

}

}

}

// A class to demonstrate the use of the Observer pattern.

public class Recipe13_11

{

public static void Main()

{

// Create a Thermostat instance.

Thermostat t = new Thermostat();

// Create the Thermostat observers.

new TemperatureChangeObserver(t);

new TemperatureAverageObserver(t);



667



www.it-ebooks.info



CHAPTER 13 ■ COMMONLY USED INTERFACES AND PATTERNS



// Loop, getting temperature readings from the user.

// Any noninteger value will terminate the loop.

do

{

Console.WriteLine(Environment.NewLine);

Console.Write("Enter current temperature: ");

try

{

// Convert the user's input to an integer and use it to set

// the current temperature of the Thermostat.

t.Temperature = Int32.Parse(Console.ReadLine());

}

catch (Exception)

{

// Use the exception condition to trigger termination.

Console.WriteLine("Terminating Observer Pattern Example.");

// Wait to continue.

Console.WriteLine(Environment.NewLine);

Console.WriteLine("Main method complete. Press Enter");

Console.ReadLine();

return;

}

} while (true);

}

}

}



Usage

The following listing shows the kind of output you should expect if you build and run the previous

example. The bold values show your input.

Enter current temperature: 50

ChangeObserver: Old=0, New=50, Change=50

AverageObserver: Average=50.00



Enter current temperature: 20

ChangeObserver: Old=50, New=20, Change=-30

AverageObserver: Average=35.00



668



www.it-ebooks.info



CHAPTER 13 ■ COMMONLY USED INTERFACES AND PATTERNS



Enter current temperature: 40

ChangeObserver: Old=20, New=40, Change=20

AverageObserver: Average=36.67



13-12. Implement a Parallel Producer-Consumer Pattern

Problem

You need to coordinate several threads using a collection, such that one or more producer threads

places items into the collection as one or more consumer threads removes items from it.



Solution

Use the System.Collections.Concurrent.BlockingCollection class.



How It Works

The BlockingCollection is a wrapper class that provides the foundation for the parallel producerconsumer pattern. Consumer threads are blocked when trying to take data from the collection until

there are data items available. Optionally, producer threads are blocked when trying to add data to the

collection if there are too many items already in the collection.

BlockingCollection wraps around collections classes that implement the System.Collections.

Concurrent.IProducerConsumerCollection interface—this includes the ConcurrentQueue,

ConcurrentStack, and ConcurrentBag collections in the System.Collections.Concurrent namespace.

To create a new instance of BlockingCollection, pass in an instance of the collection that you want

to wrap and, if required, the maximum number of items you wish to be in the collection before

producers will block when adding. For example, the following statement creates a new instance wrapped

around a ConcurrentQueue with a size limit of three pending items:

new BlockingCollection(new ConcurrentStack(), 3);

The default constructor for BlockingCollection (which takes no arguments) uses the

ConcurrentQueue class as the underlying collection, and uses no size limit—meaning that items will be

taken out of the collection in the same order in which they were added, and also that producer threads

will not block when adding items, irrespective of how many items are in the collection.

There are two ways for consumers to take items out of the collection. If you have one consumer

thread, then the simplest way is to call the GetConsumingEnumerable method and use the resulting

IEnumerable in a foreach loop. The loop will block when there are no items in the collection to be

consumed. If you have multiple consumers, then they should call the Take method, which will return an

item if one is available in the collection or block until such time as one becomes available.

If you don’t want your producers and consumers to block, you can use the

BlockingCollection.TryAdd and BlockingCollection.TryTake methods. These methods won’t block,

regardless of the state of the collection, and they return a bool to indicate whether the add or take

operations succeeded.



669



www.it-ebooks.info



CHAPTER 13 ■ COMMONLY USED INTERFACES AND PATTERNS



When using this pattern, there often comes a point when your producers have added all of the items

that you require and their tasks or threads have completed. However, your consumers will still be

blocking because they continue to wait for new items to arrive in the collection. To avoid this situation,

you should call the BlockingCollection.CompleteAdding instance method, which stops the methods the

consumers are using from blocking—see the following code example for an illustration of this.



The Code

The following example creates a BlockingCollection using a ConcurrentQueue as the underlying

collection. Using the .NET parallel programming features (see Chapter 15 for further information about

parallel programming), a single consumer reads items from the collection while four producers add

items. The main application thread waits for the producers to add their items and finish, before calling

CompleteAdding on the collection. This causes the consumer’s foreach method to stop blocking when all

of the items are read from the collection.

using

using

using

using

using

using



System;

System.Collections.Generic;

System.Linq;

System.Text;

System.Collections.Concurrent;

System.Threading.Tasks;



namespace Apress.VisualCSharpRecipes.Chapter13

{

class Recipe13_12

{

static void Main(string[] args)

{

// Create the collection.

BlockingCollection bCollection

= new BlockingCollection(new ConcurrentQueue(), 3);

// Start the consumer.

Task consumerTask = Task.Factory.StartNew(

() => performConsumerTasks(bCollection));

// Start the producers.

Task producer0 = Task.Factory.StartNew(

() => performProducerTasks(bCollection, 0));

Task producer1 = Task.Factory.StartNew(

() => performProducerTasks(bCollection, 1));

Task producer2 = Task.Factory.StartNew(

() => performProducerTasks(bCollection, 2));

Task producer3 = Task.Factory.StartNew(

() => performProducerTasks(bCollection, 3));

// Wait for the producers to complete.

Task.WaitAll(producer0, producer1, producer2, producer3);

Console.WriteLine("Producer tasks complete.");



670



www.it-ebooks.info



Tài liệu bạn tìm kiếm đã sẵn sàng tải về

13-11. Implement the Observer Pattern

Tải bản đầy đủ ngay(0 tr)

×