O'Reilly logo

ActionScript Developer's Guide to PureMVC by Cliff Hall

Stay ahead with the world's most comprehensive technology and business learning platform.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, tutorials, and more.

Start Free Trial

No credit card required

Chapter 1. Introduction

As a software developer, you are charged not only with the task of creating applications, but also with battling complexity. In almost every case, the programs you write must also be maintainable; requests for new features, bug fixes, and enhancements should be easily accommodated. And in today’s fast-paced business environment, there is a common imperative to release software early and often, so there is little time to fully design a system before the development begins. Rapid shifts in technology are driving the need to support or migrate to new hardware platforms; desktop, web, tablet, and mobile versions of an app—all with different form factors, use cases, and user interfaces—are quickly becoming standard requirements.

With all of these pressures, how are developers and teams supposed to consistently meet deadlines while delivering robust, maintainable code? Design patterns have long been seen as a way of solving specific problems within an application. However, overall application architecture, even when composed of known patterns, can suffer and become unmaintainable if not well planned. This is the problem that the Model-View-Controller (MVC) concept (see Figure 1-1), and specifically PureMVC, is meant to address.

Classic MVC Architecture

MVC Diagram: dashed lines indicate notification, solid lines are direct associations
Figure 1-1. MVC Diagram: dashed lines indicate notification, solid lines are direct associations

This book is no place for a history lesson; suffice it to say that when MVC was first conceived in the 1970s, the world was a far simpler place. What computers could do—and by extension, the software that ran on them—was relatively limited. Yet the war against complexity was already being waged. MVC emerged as a major weapon in the arsenal because it targeted a simple but deadly problem at the heart of nearly all programs that present an interface to the user and let them manipulate data.

To appreciate the elegance of the solution that MVC offers, you must fully grasp the nature of the problem. That requires generalization; stepping back from the myriad details of your application’s moving parts and seeing it in a much simpler form. Despite all that has changed in the 20+ years since MVC was first described, the basic problem it solves remains as prevalent as ever.

For a moment, picture your application as a huge pile of laundry that has accumulated in your closet for weeks and must be washed or you will have to go to work naked. You could just stuff random loads into the washer, toss in some detergent, and be done with it. Of course you might find that the towels have rubbed against your soft bedsheets and roughened them. And the hot water required to make your socks not stand up on their own anymore has caused your favorite t-shirts to shrink and your colors to run, staining your whites. Even the most carefree members of our profession would likely concede that separating your laundry according to some methodology is a worthwhile undertaking. So you sort it into piles according to some unspoken best practices of the laundry room.

The “piles” we are interested in making with MVC are:

  • Code that deals with the data (Model)

  • Code that deals with the user interface (View)

  • Code that deals with business logic (Controller)

The power of the MVC concept is that this extreme generalization applies to any application with a user interface and a domain model. By separating your code according to this simple principle, you can mitigate one of the biggest threats to your project, slay the complexity beast, and be assured that on shipping day you will not have to go to work naked!

Dispensing with the laundry metaphor for now, we will refer to these piles as the “tiers” of our application: distinct levels through which data and control flow passes at runtime. MVC gives us not only the tiers for separating these key interests, but also a few simple rules governing how the actors (classes) in each tier should communicate with those in the other tiers:

  • The Model tier may notify the View or Controller tiers

  • The View tier may update the Model tier and notify the Controller tier

  • The Controller tier may update the View tier or the Model tier

Three simple tiers, three simple rules. Easy to follow, easy to remember.

Note

Strict “Three-tier Design” distinguishes itself from MVC with the requirement that all View interaction with the Model tier must pass through the Controller tier. With MVC, the View can update the Model and the Model can notify the View, skipping the Controller tier altogether. You may choose to impose a strict Three-tier Design limitation on your code if you wish, but it is not required. We will continue to use the word tiers when referring to the Model, View, and Controller regions of the application (as opposed to the actual classes, which will always appear as Model, View, and Controller).

The PureMVC AS3 Reference Implementation

A core goal of PureMVC is to be language-independent. So while most languages have powerful native features that set them apart, PureMVC chooses to use common conventions that can be found in most every language. The chief influences in PureMVC’s original implementation stemmed from client-side concerns in a web-based, client-server environment: specifically Rich Internet Applications (RIAs) based upon Adobe’s Flash Platform. While the author specialized in Flex development, there was a huge community of Flash developers that could also benefit from the framework if it did not rely on any classes specific to the Flex API. And at the time, an ActionScript engine called Tamarin was poised to fuel the next generation of JavaScript in browsers. So an even larger crowd could possibly benefit from the framework if it did not rely on any of the classes specific to the Flash API either.

Taking all that into account, the first decision after choosing the MVC approach was that the framework would rely solely upon elements of the ActionScript language itself; no references to flash.events.Event or Flex data binding. It would not inhibit best practice use of those features, but it would not rely upon them internally. Consequently, PureMVC has its own built-in mechanism for communication between framework actors called Notifications. Since ActionScript is much like any other OOP language in use these days, the framework’s potential portability would be greatly enhanced if it stuck to the simplest language constructs and refrained from leveraging powerful but exotic features such as AS3 Namespaces and XML as a native datatype.

All of this shocks many ActionScript developers at first. Some have gone so far as to say that a framework that eschews these powerful features of Flex, Flash, and ActionScript cannot be optimal. Of course that is easy to say but hard to quantify or defend. Like art, the implementation may please you or it may not. But its effectiveness has been demonstrated repeatedly in fields as diverse as protein modeling, DNA sequencing, high availability video, virtualization hypervisors, and UAV communication systems testing. Chances are it will prove adequate for the amazing things you are doing as well.

And in truth, you do not have to give up all the rich features of the Flash Platform in order to use PureMVC; that is a common misconception. Just because the framework is self-contained does not mean you cannot communicate with it using events or use binding to move data around inside an MXML View Component. And it would be crazy to give up language features like Namespaces and XML as a datatype for the sake of some purist dogma. The key is understanding how and where they are best used in conjunction with the framework.

Many Open Source Software (OSS) projects begin with a few ideas, a little code, and an open ended future where developers throw in feature after feature. Refactoring and deprecation of old features in favor of new ones improve the project but keep dependent developers on a constant treadmill trying to keep up, in turn refactoring their own code in order to take advantage of the latest and greatest goodies.

In contrast (and partly in response), the goal for the AS3 implementation of PureMVC was to be feature-frozen, bug-fixed, and fully mature in as short a time as possible. MVC is fairly straightforward and not difficult to figure out. The scope was determined before coding began, and in the hands of the community, its shortcomings were quickly isolated and addressed. To keep the creepy feature creature at bay, it was decided that extension would happen through add-on utilities, not core framework changes.

Today, the AS3 implementation is referred to as the reference implementation because in addition to being a fully functional framework for the Flash Platform, it provides a class-by-class, method-by-method template for porting to other languages. At the time of this writing, there have been twelve other major ports including AS2, C#, C++, ColdFusion, Haxe, Java, JavaScript, Objective-C, Perl, PHP, Python, and Ruby, so you can easily apply your PureMVC core knowledge across all these languages. Strategies for carrying out the responsibilities of each actor may vary slightly, but their roles and collaboration patterns will remain the same.

The Role of PureMVC in Your Application

MVC is not really a design pattern, as it does not offer specific actors with definite roles, responsibilities, and collaborations. It is a sort of meta-pattern: good advice regarding the general separation of interests within an app. If followed, it can lead to Model tier classes that can be reused in a number of different applications, View tier classes that can be reused or refactored without major impact to the rest of the app, and business logic that can be triggered from multiple places within the app in a decoupled fashion.

We could simply write our applications informed by that advice and be done with it. Many developers do just that and report great success with no framework at all. Using a popular framework like PureMVC should and does offer benefits over just coding according MVC’s advice alone. Some of those are:

  • Reduced need for high-level architecture design, implementation, and documentation

  • No indoctrination on the homegrown approach needed for developers joining your team

  • Clear roles and collaboration patterns for actors, leading to less confusing code

PureMVC is not just a library, it is also a central organizing principle. Again, the advice of MVC is to separate the code into three specific tiers. One way this happens is in the packaging of your application: the folder arrangement you choose to store the class files in. Packaging OOP classes is an art learned with time and practice. Team members with varying skills often have different ideas about how it should be done.

Not only should your classes have clear collaboration patterns, they should also be easy to find if you are new to a project. When creating new classes, it should be obvious where to put them. Returning to the laundry metaphor for a moment, if you had separate bins to use when disrobing each night, then you would not need to waste time sorting your clothes on wash day. You would simply put articles into the proper bin to begin with.

While your packaging will probably vary slightly from the basic recommended structure, you will reap the first two of the above mentioned benefits by adhering to it: you do not need to figure it out yourself, and others working with your code will know where to find and put things if they have experience with PureMVC.

Pay No Attention to the Man Behind the Curtain

If you really want the nitty gritty on everything going on behind the scenes, the documentation on the PureMVC website covers all the actors in depth. The framework was built with the notion of complete, in-the-field replacement of any of its parts just in case there were actors you wanted to implement differently without extension. This is why every class has an associated interface. Also, the use of public or protected over private on everything you can safely override gives you a lot of flexibility to customize without modifying the framework itself.

Since almost all of the time you will use the same few actors in the standard, proscribed ways, we will briefly describe those classes first. You may notice that this approach is essentially the opposite of the online documentation. The intention there is to give a full technical rundown of the framework suitable for architects who may want to evaluate, port, or modify PureMVC at a very deep level. That same documentation has also had to serve developers who just want to use the framework to get on with their jobs. The result has been that the latter (and much larger) crowd tends to see the framework as overly complicated at first, when in fact, it is incredibly simple to use. Much effort has been invested in its design to make proper MVC separation the path of least resistance, hiding as much complexity as possible from the developer.

The focus of this book then, will be to present PureMVC in the most hands-on way possible, skipping or minimizing discussion of the abstractions. By no means does that mean you will be less prepared to enact ninja-like PureMVC skills than someone who knows every class and method inside and out. On the contrary, by focusing intently on what actually matters, you can safely treat the internals as a “black box.”

Meet the Workhorses

The PureMVC framework is composed of actors from well-known design patterns, further increasing the likelihood that developers will already understand or at least be familiar with them. Furthermore, the framework has been written in such a way that of the 11 classes (each with a corresponding interface) of which it is composed, you only need to work directly with about five. But that tiny handful you will use are workhorses that in practice can easily account for all of your application aside from View Components, Value Objects, and constants classes.

Of those five classes you will typically work with, one will be written only once in your app to initialize and manage the system, three will represent actors operating inside each of the three MVC tiers, and one will be used to pass information between the tiers and does not even require instantiation thanks to a convenience method shared by the classes who need it. So in day-to-day development, there are only three classes of real consequence. You will extend these three classes all the time, so you will want to be sure you know their roles, responsibilities, and collaborations well.

They are Proxy, Mediator, and SimpleCommand.

Actors at the Boundaries

The so-called “boundaries” of your application are the places where it interfaces with the outside world: user-facing View Components and external data sources like filesystems or web services. Managing I/O at these boundaries is the responsibility of the Mediator and Proxy respectively.

Use a Proxy as a Data Source for the Application

The unique cloud of relevant terms surrounding your application’s real-world problem domain is generally referred to as the domain model. The domain model of your application will likely be implemented as simple data carrier classes (commonly referred to as Value Objects), that represent data with strong types. There is no framework class for representing data because there are just too many ways to do it; it is not PureMVC’s intent to represent data, but rather to retrieve, persist, and expose it to the user for viewing and manipulation. Retrieving and persisting the data—in whatever form it may take—is the primary role of the Proxy.

Proxy subclasses are long-lived actors that act as data sources within the application. Whether they handle access to the filesystem, remote servers, or hardware like the camera and microphone, Proxys are typically created at startup and available throughout the runtime of the application, though we will explore transient usage patterns as well.

A simple proxy example

In Adobe AIR, there is a nice feature called the Encrypted Local Store (ELS). It allows you to store key / value pairs in an encrypted database on the local disk. In this example, we will see how to use a Proxy subclass to read and write a Value Object to the ELS. For the purposes of demonstration, we will store sensitive information about the location of the user’s mail server and her credentials.

Our approach will be to have the EmailConfigProxy allow the getting and setting of a single Value Object called EmailConfigVO, which has the necessary properties. When the Value Object is set on the EmailConfigProxy, we will take the values off and store them in the ELS as key / value pairs with the key being a private constant defined on the EmailConfigProxy, and the value being the value of the corresponding property on the EmailConfigVO. When getting the Value Object from the EmailConfigProxy, the getter will simply return a new instance of the EmailConfigVO type created from the currently stored values in the ELS. Private setters and getters for the individual ELS key / value pairs are used by the setter and getter for the EmailConfigVO itself.

Note

This example also shows an ActionScript feature used often throughout the book called implicit accessors. As in most any language, you could always have getUser() and setUser() methods for manipulating a private variable called user. But using ActionScript’s implicit accessors, you can have two methods that combine to look like a single property. Although this is a fairly exotic ActionScript feature, when porting your code to a platform without implicit accessors it is straightforward to transform property references into method calls, so portability is not impaired by their use in practice.

EmailConfigProxy

package com.mycompany.myapp.model.proxy
{
    import flash.data.EncryptedLocalStore;
    import flash.utils.ByteArray;

    import com.mycompany.myapp.model.vo.EmailConfigVO;

    import org.puremvc.as3.multicore.patterns.proxy.Proxy;
    
    /**
     * This is an example Proxy for persisting 
     * email configuration items in the AIR 
     * Encrypted Local Store (ELS) for MyApp.
     */
    public class EmailConfigProxy extends Proxy
    {
        public  static const NAME:String        = "EmailConfigProxy";
        private static const EMAIL_HOST:String  = NAME+"/email/config/host";
        private static const EMAIL_PORT:String  = NAME+"/email/config/port";
        private static const EMAIL_USER:String  = NAME+"/email/config/user";
        private static const EMAIL_PASS:String  = NAME+"/email/config/pass";

        // Constructor. Pass the Proxy constructor the 
        // name of this subclass.
        public function EmailConfigProxy( ) 
        {
            super ( NAME ); 
        }
        
        // get the email configuration Value Object
        public function get emailConfigVO():EmailConfigVO
        {
           return new EmailConfigVO ( host, port, user, password );
        }
        
        // set the email configuration Value Object
        public function set emailConfig( config:EmailConfigVO ):void
        {
            host     = config.host;
            port     = config.port;
            user     = String(config.user);
            password = String(config.pass);
        }
        
        private function get host( ):String
        {
            return retrieve( EMAIL_HOST );
        }

        private function set host( host:String ):void
        {
            store( EMAIL_HOST, host );
        }

        private function get port( ):Number
        {
            return Number( retrieve( EMAIL_PORT ) );
        }

        private function set port( port:Number ):void
        {
            store( EMAIL_PORT, port.toString() );
        }

        private function get user( ):String
        {
            return retrieve( EMAIL_USER );
        }

        private function set user( user:String ):void
        {
            store( EMAIL_USER, user );
        }
        
        private function get password( ):String
        {
            return retrieve( EMAIL_PASS );
        }
        
        private function set password( pass:String ):void
        {
            store( EMAIL_PASS, pass );
        }
        
        // store a key /value pair in the ELS
        private function store( key:String, value:String ):void
        {
            if (key && value) {
                var bytes:ByteArray = new ByteArray();
                bytes.writeUTFBytes(value);
                EncryptedLocalStore.setItem(key,bytes);
            }
        } 

        // retrieve a key /value pair from the ELS
        private function retrieve( key:String ):String
        {
            var bytes:ByteArray = EncryptedLocalStore.getItem( key );
            return (bytes)?bytes.readUTFBytes(bytes.length):null;
        }
    }
}

Use a Mediator as a Secretary for a View Component

The user interface of your application will be implemented as a hierarchy of View Components. In Flash, those components could be placed on the stage at design time. In Flex and AIR, the components will mostly be declared in MXML and created at startup, and in either case they may also be dynamically instantiated and inserted into the display list later. Some components will be custom and some out-of-box. Plugging your View Components into the rest of the application is the role of the Mediator.

Mediator subclasses are long-lived actors whose primary function is to isolate the application’s knowledge of a given component in the display list to a single framework subclass. It will provide that component with the data it needs, pass data from the View Component back into the system for processing by business logic, or update the Model tier directly. It should not act as a so-called “code-behind” class that keeps state for the View Component or manages its internals. Though the Mediator may affect the state of a View Component indirectly via interaction with explicitly exposed properties and methods, View Components should encapsulate their own internal implementation. The Mediator should merely pass information between the View Component and the rest of the application with a minimum amount of translation, much as an executive’s personal secretary handles his communication with the outside world, freeing him to focus on his work.

A simple mediator example

Carrying on with the previous example, there must be—somewhere—a View Component for editing the email configuration information that we are saving with the EmailConfigProxy. We will call it EmailConfig and the EmailConfigMediator will tend it.

This EmailConfig View Component has a property called vo where a reference to the EmailConfigVO can be accessed as needed by the EmailConfigMediator. We will also exercise the alternative for the View Component to pass the data to the EmailConfigMediator in a property of a custom Event. The properties of that Value Object will be used by the View Component to populate its form. When the user has entered or edited the data, he will be able to test the configuration and if the test passes, he will then be able to save the configuration. We will leave it to the View Component to encapsulate all of the necessary supporting behavior such as not enabling the “Save” button until a test has been done, and not enabling the “Test” button until all the information has been entered, and so on. All that we need to know to build the EmailConfigMediator is the relevant properties, methods, and Events that make up the API of the View Component. Treating the View Component as a “black box” can help companies outsource UI development or leverage strong internal UI developers who may not know PureMVC.

The EmailConfigMediator will listen to the EmailConfig View Component for certain Events that indicate the user’s intent to save or test the current configuration. It will also be interested in Notifications (more about them later) from other parts of the system and handle those Notifications by interacting with the View Component. Finally, the EmailConfigMediator will also collaborate directly with the EmailConfigProxy to retrieve or persist the EmailConfigVO.

EmailConfigMediator

package com.mycompany.myapp.view.mediator
{
    import com.mycompany.myapp.controller.constant.MyAppConstants;
    import com.mycompany.myapp.model.proxy.EmailConfigProxy;
    import com.mycompany.myapp.model.proxy.EmailProxy;
    import com.mycompany.myapp.model.vo.EmailConfigVO;
    import com.mycompany.myapp.view.components.EmailConfig;
    import com.mycompany.myapp.view.event.MyAppEvent;

    import org.puremvc.as3.interfaces.INotification;
    import org.puremvc.as3.patterns.mediator.Mediator;
    
    public class EmailConfigMediator extends Mediator
    {
        public static const NAME:String = "EmailConfigMediator";
        
        private var emailConfigProxy:EmailConfigProxy;

        // Pass Mediator constructor this mediator's name and component
        public function EmailConfigMediator( viewComponent:EmailConfig ) 
        {
            super( NAME, viewComponent ); 
        }

        // get the View Component cast to the appropriate type
        private function get emailConfig():EmailConfig
        {
            return viewComponent as EmailConfig;
        }
        
        // Called at registration time. Form direct collaborations.
        override public function onRegister():void
        {
            // set listeners on View Components
            emailConfig.addEventListener( MyAppEvent.SAVE_EMAIL_CONFIG, 
                                          saveEmailConfig );
            emailConfig.addEventListener( MyAppEvent.TEST_EMAIL_CONFIG, 
                                          testEmailConfig );
            
            // retrieve needed proxies
            emailConfigProxy = facade.retrieveProxy(EmailConfigProxy.NAME)
                                                      as EmailConfigProxy;
        }
        
        // We can get the config from the event if the component sends it this way.
        // Here we save it immediately to the ELS via the EmailConfigProxy
        private function saveEmailConfig( event:MyAppEvent ):void
        {
            emailConfigProxy.emailConfigVO = event.data as EmailConfigVO;
        }
        
        // We can also get the data from the View Component.
        // Here, we'll send it off in a notification to be processed by a Command
        private function testEmailConfig( event:MyAppEvent ):void
        {
            sendNotification( MyAppConstants.PERFORM_EMAIL_TEST, emailConfig.vo );
        }
        
        // Called at regisistration time, we should list the 
        // notifications we want to hear about
        override public function listNotificationInterests():Array 
        {
            return [ MyAppConstants.SHOW_CONFIG,
                     EmailProxy.TEST_RESULT
                   ];
        }
        
        // Respond to notifications this mediator is interested in
        override public function handleNotification( note:INotification ):void 
        {
            switch ( note.getName() ) 
            {
                // set the email configuration on the View Component
                case MyAppConstants.SHOW_CONFIG:
                    emailConfig.vo = emailConfigProxy.emailConfigVO;
                    break;

                // set the result of the email test on the View Component
                case MyAppConstants.TEST_RESULT:
                    emailConfig.testResult = note.getBody as Boolean;
                    break;
            }
        }
    }
}

Actors Between the Boundaries

Often in an application that does not follow strong OOP design principles, a class that communicates with the server will also perform lots of calculations on the data being received or transmitted, or even create and interact with the View Components that display it. Or a class that is used to display data will also load it, perform lots of calculations on it, and store it. By separating the responsibilities of the classes at those boundaries, you have already taken a giant step toward keeping your code from becoming a messy plate of spaghetti. Still, the boundary actors could use a little help to keep their responsibilities light and focused.

Isolated from the hustle and bustle of I/O going on at the external boundaries is where the thinking takes place in your application. Whether unique to a given application or shared amongst a suite of apps, business logic is the responsibility of the SimpleCommand and MacroCommand (collectively referred to as simply Commands throughout the book).

Let SimpleCommands Do Most of the Thinking

Besides handling I/O, your application will likely have a number of purely logical operations to perform throughout runtime. Activities like preparing the application for use at startup or performing a search-and-replace on a chunk of text fall outside the logical realm of responsibility for either the View tier’s Mediator or the Model tier’s Proxy.

SimpleCommand subclasses are short-lived actors that are created when needed, execute their function, and exit to be garbage-collected thereafter. The benefit of using a SimpleCommand for this sort of thing, as opposed to a class with static methods, is that the latter approach promotes the kind of spaghetti code that MVC is intended to avoid. It couples the calling actors to it, making them difficult to reuse separately. Such classes tend to become so-called “god objects,” growing larger and taking on a more ill-defined role over time since they seem to be a handy place to put various odds and ends. By placing logic in discrete classes and triggering them in a decoupled fashion, you get code that is easier to understand, refactor, and reuse.

In our previous example of the EmailConfigMediator, we had a testEmailConfig() method that was called when the user pressed a “Test” button in the EmailConfig View Component. That component dispatched a custom Event carrying the EmailConfigVO to be tested. The EmailConfigMediator sent the EmailConfigVO off in a Notification named MyAppConstants.PERFORM_EMAIL_TEST. Here we will assume that a SimpleCommand called PerformEmailTestCommand has been registered to handle this Notification (more on how that happens later). Though we will see much more involved logic happening in Commands later on in the book, here is an example of what that SimpleCommand might look like:

PerformEmailTestCommand

package com.mycompany.myapp.controller.command
{
    import com.mycompany.myapp.model.proxy.EmailProxy;
    import com.mycompany.myapp.model.vo.EmailConfigVO;
    
    import org.puremvc.as3.interfaces.INotification;
    import org.puremvc.as3.patterns.command.SimpleCommand;
    
    public class PerformEmailTestCommand extends SimpleCommand
    {
        override public function execute( note:INotification ):void
        {
            // Get the email configuration from the notification body
            var config:EmailConfigVO = EmailConfigVO( note.getBody() );
            
            // Get the EmailProxy
            var emailProxy:EmailProxy;
            emailProxy = EmailProxy( facade.retrieveProxy(EmailProxy.NAME) );
            
            // Invoke the email configuration test. 
            // The EmailProxy will send the result as 
            // a Boolean in the body of an EmailProxy.TEST_RESULT note,
            // which will be handled by the EmailConfigMediator
            emailProxy.testConfiguration( config ); 
        }
    }
}

Use a MacroCommand to Execute Several SimpleCommands

For more complex operations, there is the MacroCommand, which simply groups any number of SimpleCommands (referred to as its subcommands), and executes them in First In First Out (FIFO) order. It is rarely sighted in the wild, but we will explore a reasonable usage later in this book.

The Rest of the Gang

You have been briefly introduced to the workhorse classes you will be writing every day: Mediator, Proxy, and SimpleCommand. Learning what goes into those classes (and just as importantly, what does not) and how to set up the collaborations between them that eventually bring your application to life will be the major focus of this book.

As promised, esoteric discussion of the PureMVC internals will be kept to a minimum. Nevertheless, there are a few other classes you will come into contact with or should at least know about, so we will cover those briefly here and more deeply as the need arises in the following chapters.

Notifications

Notifications provide the mechanism by which those workhorse classes communicate with each other. While a Proxy may listen to a service component for Events, it will broadcast its own status to the rest of the PureMVC actors using Notifications. Likewise, a Mediator may place event listeners on a View Component, but it will usually translate those Events into outbound Notifications in order to communicate user intent to other parts of the application.

A Notification has a name, an optional body (which can be any Object), and an optional type (a String). Mediator, Proxy, and SimpleCommand are all Notifier subclasses, meaning they all inherit a convenience method called sendNotification(), which takes the three properties just mentioned as arguments, constructs a new Notification, and broadcasts it.

Who will receive these Notifications? Take another look back at Figure 1-1, the MVC diagram.

Notice that the View and Controller tiers are potential recipients of notification, but the Model tier is not. The reason for that is to keep the Model tier portable. It is the combined duty of the View and Controller tiers to ensure that the user is able to interact with the data. Those tiers are both able to directly update the Model tier. The Model tier is only responsible for making the data available. For an actor of the Model tier to become interested in the business of presentation would be overstepping its role and potentially coupling it to one particular application implementation.

What this means is that Proxy, Mediator, and SimpleCommand can each send a Notification, but only Mediator and SimpleCommand may receive them.

Note

Notifications are commonly referred to as “notes” for short.

The Core Actors

Representing each of the three MVC tiers (and referred to as the Core actors) are three Singletons called Model, View, and Controller. They take care of registering and making available your Proxy, Mediator, SimpleCommand, and MacroCommand subclasses.

The Model and View act as registries of Proxy and Mediator instances (remember they are long-lived actors), respectively. The View also manages notifying all of the interested actors when a Notification is sent.

The Controller handles the mapping between SimpleCommand or MacroCommand class names and Notification names. It also creates and executes the appropriate class instances when a mapped Notification is broadcast by any actor.

Although these classes are the engine of PureMVC, you will never have to work with them directly or even care what they do. Why?

The Facade

This design pattern is very handy for providing an interface to an arbitrarily complex system so that clients of the system do not ever have to interact directly with the system’s actors.

In the case of PureMVC, the Facade gives the developer the impression that we simply rolled the Model, View, and Controller classes up into one Singleton. All the necessary methods from each of the three Core actors are present on it. Since they are separate classes, you can replace or modify any of them individually if need be (which is almost never).

Imagine the Facade as the receptionist at a hotel. When a guest comes in, he does not just get on the elevator and go find a room. Instead, the receptionist gets him checked in, gets him his keys, and tells him where his room is. When he comes back an hour later, she also tells him where the ice machine is and gets him a bucket, or fetches a concierge if he needs to find a place to eat, or the maintenance guy if the lights in his room are not working.

Packaging Your Classes

Now that you are acquainted with the classes you will be writing, let us consider how they are packaged in a typical project.

First, a quick review of the most common actors:

Ordinary Classes

View Components

The building blocks of your user interface

Events

Dispatched by your View Components

Value Objects

Data entities in your domain model

Enums

Enumerations of valid values for multiple choice fields

Constants

Classes with static constant values

Framework Classes

  • Facade

  • Mediator

  • SimpleCommand / MacroCommand

Typical Package Structure

As mentioned earlier, there is a suggested package structure associated with PureMVC projects. Figure 1-2 shows those common classes packaged within a structure whose first division ensures everything in the system is conceptually sorted according to MVC.

Despite being simple enough to build quickly by hand, this structure is extremely extensible. For instance, if your application has a lot of View Components, you can subdivide the view.components package to any depth you like to cordon off the user interface elements of your various subsystems. If you have a very large domain model, you may enforce the same discipline with the model.vo package. Maintaining the MVC separation as your central organizing principle also helps you to separate your code into libraries (or combine multiple libraries) with confidence that your packaging schemes will mesh. You may find that you want to extract your entire Model tier into a library for reuse across applications. You might also want to build your Model tier in a separate library even if you do not have multiple apps planned. If that library is not allowed to reference the View tier and Controller tier code in the app, then you will have ensured that the most important separation of MVC has been maintained—isolation of the Model tier.

Of course, your codebase can be structured any way you please; the framework itself is not sensitive to your package structure. But the point of MVC is separation of interests, so that as the application’s complexity increases, maintainability remains constant. Packaging is a powerful tool for that very purpose. If you are using PureMVC, you’ll do current and future team members a favor by adhering to some common standards for packaging. Consider how implementation of the Dewey Decimal System helps a reader familiar with it go into any library, locate the card catalog, and find the book they need. Of course a local library could create their own internally self-consistent system for locating books, that is a given. But for anyone who is familiar with how most libraries organize their books, the seeker of knowledge in this home-brew library would encounter unnecessary mental friction until they had learned the local filing system. Remember the “Code at the Speed of Thought” goal stated in the Preface? Great strides toward that goal can be made by eliminating points of mental friction wherever possible.

Typical package structure for a PureMVC-based application
Figure 1-2. Typical package structure for a PureMVC-based application

Standard and MultiCore Versions

Shortly after PureMVC’s release, the largest requested change that had not been originally scoped was implemented: the MultiCore version.

Since there can be only one instance of each of the Facade and Core actors, a powerful feature of the Flash Platform was difficult to work with: Flex Modules or loaded Flash SWF files that were themselves using PureMVC.

Imagine a truly modular design decoupled enough that it could accommodate third party PureMVC-based modules running together in a PureMVC-based host application. Unless the module writer has access to the host code, Notification naming collisions could occur, thereby causing unstable behavior.

The solution to the problem was to allow for separate sets of the Facade and Core MVC Singletons for the host and each module. They become Multitons now: registries of named instances rather than holders of a single instance. Each core could then run as a whole separate program.

How do you communicate between cores? Be it host-to-module, module-to-module, or module-to-host, you can always use interfaces and direct references to expose a communication API for your cores. Alternatively, you could use a utility called Pipes that was released along with the MultiCore version. Pipes was inspired by the Unix command line interface where you can chain programs together in pipelines and pass data through them for processing. You can plumb your inter-core communication paths by combining “pipes” and “pipe-fittings” such as splitting and joining tees, filters, etc., then send typed messages between cores, optionally carrying data or View Component references.

Some applications are just naturally good candidates for modular system design. Music production programs that allow virtual instruments to be combined with sequencing and mixing are a perfect example. Each virtual instrument could be implemented as a module, the sequencer as a module, and the mixing suite as a module. One nice benefit of a modular architecture like this is that it opens the door to third party plugin development. In a notional music production app, third parties could develop effects and instruments that could be plugged into your app to make it even more powerful. You could simply release an API library containing the interfaces, Value Objects, and message classes that are needed by all modules, and then developers could begin adding value to your app. If the app were not modular, then third parties would not be able to enhance it without access to the source code of the entire application, which you may not wish to give away.

MultiCore is a whole level of separation beyond MVC, and while it is extremely powerful, not everybody needs it. There is a small but unavoidable overhead in complexity for the developer that was deemed best implemented in an alternate version of the framework. MultiCore was created, and the existing version was dubbed Standard.

In this book, we are using the Standard version. The differences are not that great, so do not be worried when you find yourself needing a modular solution. See Chapter 10 for some resources to get you started with MultiCore.

Caution

If your team is blessed with the time for unit testing, then you should choose the MultiCore version instead of Standard, even if you are not writing a modular application. The reason is that MultiCore allows you to have an isolated core for each test method in a test class and/or each test class in a test suite. Otherwise, in Standard, each test is happening inside the same core for the duration of the test run, allowing the outcome of previous tests to conceivably affect the current test unless elaborate setup and teardown is performed. It is not necessary to use Flex Modules in order to use MultiCore, you can write your application the same way, the only real difference you will see is that your ApplicationFacade.getInstance() method will need to take a String argument for a key.

Writing Portable Code

A few simple decisions made before PureMVC was implemented led to its high portability. Limited scope and use of only the most common OOP constructs ensured it would be easy to recreate in other languages. Since the framework is available in many popular programming languages, your application has numerous potential porting targets to choose from, should the need arise.

If you only write applications exclusively for the Flash Platform, PureMVC’s portability may not seem that important to you. Fortunately, Adobe AIR is available on many hardware platforms today, increasing the Flash Platform developer’s cozy feeling of safety. Just like rock and roll, AIR will never die—insert blazing air guitar solo here (pun only slightly intended). As I happen to love it and make a living using it, I certainly hope that will be the case.

Fortunes can change, however. The platform that you build and improve your applications on could easily go the way of the dinosaurs, and not because it was technically inferior.

Consider how Apple destroyed Flash’s previous ubiquity by not allowing it on their popular iPhone and iPad products. For a time, they refused to let AIR apps onto those products as well. The only option for access to that huge market was to learn Objective C or to write your app in HTML, CSS, and JavaScript.

Less than a week before the delivery of this book’s manuscript, Adobe announced its intention to donate Flex to the Apache Software Foundation, and the future of Flex now hangs in the balance. Will enterprise customers walk away from it now that it is no longer supported directly by Adobe, or will the community keep it alive and relevant? Only time will tell, but it is very likely that a large number of apps written in Flex will now be migrated to some other platform. Will the overall architecture of those applications be portable, helping to make the unavoidably tough migration straightforward, or will they have to be redesigned from scratch at great expense?

The point here is that the rule of “Survival of the Fittest” does not fully determine the fate of a major software platform. Industry hardware and software giants do. If your code is to survive long-term, it needs to be portable.

Not only might your chosen platform disappear, but plenty of agencies and independent software vendors develop for multiple platforms because no one language and toolset covers all the markets they want to reach. Can you imagine the hassle of having to maintain multiple versions of your application in different languages with completely different architectures?

Wait, you say, Objective C is totally different from ActionScript! Of course implementations of the same application are going to be different on each platform, right?

Right. The question is: what will be different because it must, and what will be different because you simply chose a different approach? That is what needs to be sorted out when considering the portability of your code. There will certainly be platform-specific differences. However, by consciously choosing PureMVC for each platform, you and your team can build your experiences on a single architecture and communicate more effectively on each project with less confusion.

One of the benefits of the PureMVC approach is that you can port what really matters: the architecture. The packaging, as well as the actors and their roles, responsibilities, and collaborations are all portable.

Other than the syntactic differences in the languages, the things that are likely to be different because they must are View Components, services, and the way you communicate with them; in other words, the boundaries of the app, which, conveniently, is the stuff that Mediator and Proxy isolate each other and the business logic from.

While you would certainly have to write the app again in its entirety on the target platform, you would not have to completely redesign the architecture.

You would write mostly the same classes with the same methods and properties, just accounting for the differences at the boundaries and preserving the logic and notification patterns. The business of adding new use cases to both applications would then largely amount to implementing the same actors in both places. The refactoring of existing use cases would not be complicated by differences in architecture.

It may sound odd, but the time to think about the portability of your code is before and while you write it, not when you are eventually forced to port it and have not yet mastered the new platform.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, interactive tutorials, and more.

Start Free Trial

No credit card required