Issott_Common Design Patterns for Symbian OS-The Foundations of Smartphone Software_0470516356 (779879), страница 42
Текст из файла (страница 42)
For instance, it could take some corrective actionand re-try the same state change or perhaps it could signal a change backto some base state. The exact choice depends very much on what eachof the state changes mean.However, one complication to be aware of is that the respondersmay not be in a consistent state. Figure 6.16 shows ResponderA failing,however, if it had been ResponderB that had failed then ResponderAwould be in a different state to ResponderB.ImplementationSingle-Threaded CoordinatorThe implementation given here is for the structure described in Figure 6.13.You first need to decide how to express the state coordination model.The most straightforward way is to use one enumeration for the possiblestates25 and another for the list of layers. For this example, we chooseto model the layering of the responders on the layering of the system asshown below:enum TState{EState1,EState2,...};enum TResponderLayer{EKernel,EBaseServices,EAppEngines,EAppFramework,EAppUI,EApplications,};Once the model has been decided upon, the three main classes canbe implemented using the types that describe the coordination model.First of all the controller, whose main job is to start the state changes,is shown here.
This is satisfied within SignalStateChange() whichsimply makes calls to iCoordinator as appropriate. As a result of this25 If needed, more complex types can be used to represent each state, so long as they allderive from a single base class.220PROVIDING SERVICESthe controller, is called back via the MStateChanger interface whichit should implement to handle both the success and failure of the statechange:// Event Mixin used by the coordinator to signal completion of a state// change requestclass MStateChanger{public:void MscStateChangeComplete(TInt aError) = 0;};class CController : public MStateChanger{public:void SignalStateChange();...public: // From MStateChangervoid MscStateChangeComplete(TInt aError);private:CCoordinator* iCoordinator;};Each of the responders derives from the following interface designedusing Event Mixin (see page 93).
Each method takes a reference to anMCoordinator object which they later use to send their acknowledgements of the state change. A cancel method is provided since the statechange can be asynchronous:class MResponder{public:// When the state change has been handled, whether or not it was// successful, ChangeComplete() must be called on aCoordinator. This// can be done synchronously within this function or asynchronously.virtual void MrHandleStateChange(MCoordinator& aCoordinator,TState aNewState) = 0;// This will only ever be called when the responder is in the// process of handling a state change. It must synchronously call// ChangeComplete() on aCoordinator with KErrNone if the state change// couldn’t be stopped or KErrCancel if it was.virtual void MrHandleStateChange(MCoordinator& aCoordinator) = 0;};A coordinator derives from the following interface designed usingEvent Mixin (see page 93).
It allows responders to register and de-registerfor state changes as well as to signal when their state changes havecompleted. Note that MscStateChangeComplete() is not a leavingfunction because that would escalate errors from the coordinator to aresponder whereas we want most errors in the coordinator to go to theCOORDINATOR221controller. McRegisterResponderL() is an exception to this; if thatfunction cannot be completed for whatever reason, then this is an issuejust for that individual responder rather than the whole system.Note that the McStateChangeComplete() function does not havea trailing ‘L’, which indicates it is not a Leaving function.
This is becausethe function is called by a responder and any Leave would need tobe handled by one of them. Instead any error that occurs during theexecution of this function needs to be resolved either by the coordinatoritself or passed onto the controller.class MCoordinator{public:void McRegisterResponderL(MResponder& aResponder,TResponderLayer aLayer) = 0;void McDeregisterResponder(MResponder& aResponder,TResponderLayer aLayer) = 0;void McStateChangeComplete(MResponder& aResponder, TInt aError) = 0;};The CCoordinator class derives from MCoordinator so that itcan work with the responders to change their state. However, its mainadditional responsibility is to provide an interface for the controller tostart (and potentially cancel) a state change.
This is done by providing theSignalStateChange() method with the MStateChanger interfacethat is used to asynchronously signal the completion of the task.You might also consider using Singleton (see page 346) to ensure thatthere is only one instance of this class. However, the lifetime of thecoordinator would still need to be managed somewhere and since onlythe controller should directly use the CCoordinator class, Singleton(see page 346) doesn’t benefit us much.class CCoordinator : public CBase, public MCoordinator{public:static CCoordinator* NewL();virtual ∼CCoordinator();void SignalStateChange(MStateChanger& aController, TState aNewState);void CancelStateChange();public: // From MCoordinatorvoid McRegisterResponderL(MResponder& aResponder,TResponderLayer aLayer) = 0;void McDeregisterResponder(MResponder& aResponder,TResponderLayer aLayer) = 0;void McStateChangeComplete(MResponder& aResponder, TInt aError) = 0;private:222PROVIDING SERVICESCCoordinator();void ConstructL();void NotifyCurrentLayer();void NotifyNextLayer();void HandleStateChangeError(TInt aError);private:TState iNewState;MStateChanger* iController; // UnownedTResponderLayer iCurrentLayer;RArray< RArray< MResponder* > > iResponders; // UnownedRArray< MResponder* > iWaitingResponders; // Unowned};The member variables of this class require some explanation:• iNewState stores the state to which responders must change.• iController is an event mixin that allows us to signal when thecollective state change for all responders has been completed eithersuccessfully or with an error.
This is not NULL only when a statechange is ongoing.• iCurrentLayer keeps track, while a state change is ongoing, of thelayer for which notifications have been sent out and for which we areawaiting acknowledgement.• iResponders is a two-dimensional array of MResponder pointers.
This array is used to hold the list of registered responders,with the first dimension indicating the TResponderLayer in whicheach of the responders in the corresponding second dimension areto be notified. For instance, iResponders[EBaseServices][0]->MrHandleStateChange(EState2) would notify the firstresponder at the EBaseServices level of change 2.• iWaitingResponder is used while a state change is ongoing tokeep track of which responders are yet to respond.The implementation of this pattern is best illustrated by examining theimplementation of the public interface of CCoordinator.SignalStateChange() is responsible for initializing the notificationof a change, starting first at the lowest layer which, in this case, isEKernel.void CCoordinator::SignalStateChange(MStateChanger& aController,TState aNewState){COORDINATOR223if(iController != NULL){// There’s an ongoing transition so complete the request with erroraController.MscStateChangeComplete(KErrInUse);return;}// Else start a new state change transistioniController = &aController;// Start from the bottom of the dependency layersiCurrentLayer = EKernel;iNewState = aNewState;NotifyCurrentLayer();}NotifyCurrentLayer() iterates over all the registered respondersat the current layer, notifying them all at once and transferring thenotified responders to iWaitingResponders to track which respondershave acknowledged the state change at the current layer.
Note thatMResponder::MrHandleStateChange() could be implemented tosynchronously acknowledge the state change. Hence we need to checkat the end if there are any responders we’re waiting for. If not then weimmediately advance to the next layer.void CCoordinator::NotifyCurrentLayer(){// Set up the waiting responder arrayiWaitingResponders.Close(); // Free the memory previously usediWaitingResponders = iResponders[iCurrentLayer];// Force the responders to re-registeriResponders[iCurrentLayer] = RArray();// Notify all waiting responders. This needs to be done in reverse so// that any responders which respond synchronously don’t interfere// with the part of the array being iterated over when they’re removed// from iWaitingResponders.const TInt count = iWaitingResponders.Count();for (TInt i = count; i >= 0; --i){iWaitingResponders[i]->MrHandleStateChange(&this, iNewState);}if(iWaitingResponders.Count() == 0){// Not waiting for any responses so go to next layerNotifyNextLayer();}}NotifyNextLayer() is responsible for notifying the next layer upuntil the final layer has been done.224PROVIDING SERVICESvoid CCoordinator::NotifyNextLayer(){++iCurrentLayer;if(iCurrentLayer >= KLastResponderLayer){// Finished all notifications so notify the controlleriController->MscStateChangeComplete(KErrNone);iController = NULL;return;}// Else notify the current layerNotifyCurrentLayer();}The next two public CCoordinator methods are simple registrationand de-registration methods which update the array of responders at theappropriate layer with the change.
Note that if a responder wishes tore-register for the next state change it should call this function before itcalls MCoordinator::McStateChangeComplete() for the previousstate change. This ensures that there is no opportunity for a notificationto be missed by the responder.void CCoordinator::McRegisterResponderL(MResponder& aResponder,TResponderLayer aLayer){iResponders[aLayer].AppendL(&aResponder);}// Attempting to de-register a responder that isn’t registered is a// programmatical fault so Fail Fast (see page 17)void CCoordinator::McDeregisterResponder(MResponder& aResponder,TResponderLayer aLayer){TInt index = iResponders[aLayer].Find(&aResponder);ASSERT(index != KErrNotFound);iResponders[aLayer].Remove(index);}McStateChangeComplete() is called by each responder toacknowledge that they’ve transitioned to the new state they were notifiedof in MrHandleStateChange().