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
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, Proxy
s 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 Event
s
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 Event
s that
indicate the user’s intent to save or test the current configuration.
It will also be interested in Notification
s (more about them later) from
other parts of the system and handle those Notification
s 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
Command
s 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 Command
s 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 Notification
s. Likewise, a Mediator
may place event listeners on a View
Component, but it will usually translate those Event
s into outbound Notification
s 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 Notification
s? 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
Notification
s 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.
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.
Get ActionScript Developer's Guide to PureMVC now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.