parsley & flex

Post on 09-Jul-2015

1.981 Views

Category:

Technology

1 Downloads

Preview:

Click to see full reader

TRANSCRIPT

Parsley framework for Flex/Flash/AIR

&

Alexander Budjakov Flex Developer prideconan@gmail.com

Parsley is all about decoupling

Parsley is Open Source, licensed under the Apache License 2.0 Written by Jens Halm First version 1.0.RC1 (2007-12-09) Last version 3.0.0 (2012-02-06)

Overview

Parsley is an Application Framework for Flex and Flash Applications built upon an IOC Container and Messaging Framework that can be used to create highly decoupled architectures.

> 4 years

Features

Flexible IOC Container Dependency Injection Decoupled Bindings Messaging Framework Managed Commands Dynamic View Wiring Advanced Modularity Object Lifecycle Localization Extensibility

Parsley Spicelib

Command Framework XML-Mapper Reflection API Logging

Inversion of Control (IoC)

Hollywood Principle: Don't call us, we'll call you.

Program logic runs against abstractions such as callbacks

Inversion of Control (IoC)

IoC Example

package com { public interface IMap { //methods } }

package com { public class GPSNavigator implements INavigator { private var map:IMap; public function GPSNavigator() { this.map = new UkraineMapImpl(); } //other methods } }

Here are a common smells that should lead you to refactor to IoC

Here is the simplest possible IoC component

package com { public class GPSNavigator implements INavigator { private var map:IMap; public function GPSNavigator(map:IMap) { this.map = map; } //other methods } }

It is hard coded Not reusable.

package com { public class GPSNavigator implements INavigator { private static var map:IMap = MapFactory.getMap(); public function GPSNavigator() { } //other methods } }

Some other smells.

IOC Container

We may ask for the object from the container and the container creates the object and its dependencies

Parsley is a classic IOC Container. It provides support for Dependency Injection, Object Lifecycle Management and Messaging

EBookReader <interface>

File

PDFFileImpl TXTFileImpl

IoC Container

inject

creates

Dependency Injection (DI)

Dependency Injection (DI)

How objects obtain references to each other

The core feature of any IOC Container [Inject]

Dependency Injection (DI)

You may have these different tools

Dependency Injection (DI)

OR just one tool with attachments

You may have these different tools

Dependency Injection (DI)

This brings us to the Design Principles - Program to an interface, not an implementation

OR just one tool with attachments

You may have these different tools

Dependency Injection (DI)

Three ways of classic DI

Dependency Injection (DI)

public class Controller { private var model:Model; public function Controller(model:Model) { this.model = model; } }

Constructor Injection

Dependency Injection (DI)

public class Controller { private var model:Model; public function Controller(model:Model) { this.model = model; } }

public class Controller { private var _model:Model; public function set model(value:Model):void { _model = value; } }

Constructor Injection

Setter Injection

Dependency Injection (DI)

public class Controller { private var model:Model; public function Controller(model:Model) { this.model = model; } }

public class Controller { private var _model:Model; public function set model(value:Model):void { _model = value; } }

public interface IController { function injectModel(model:Model):void } public class Controller implements IController { private var model:Model; public function injectModel(model:Model):void { this.model = model; } }

Constructor Injection

Setter Injection

Interface Injection

public class Controller { private var model:Model; public function Controller() { model = new Model(); } } public class Controller { private var model:Model; public function Controller() { model = ModelFactory.create(Model); } } public class Controller { private var model:Model; public function Controller() { model = Model.getInstance(); } }

NOT Dependency Injection

IoC and DI Benefits

• Dependency management • Simplify the reuse of classes or components • Offers configuration flexibility • Simplify unit-testing • The "cleaner" code

Configuration and Initialization

Configuration and Initialization

1

• Telling the IOC Container which classes it should manage

• MXML, XML, AS

<?xml version="1.0" encoding="utf-8"?> <parsley:Objects xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" xmlns:mx="library://ns.adobe.com/flex/mx" xmlns:parsley="http://www.spicefactory.org/parsley"> // *hidden imports <fx:Declarations> <parsley:Object id="userModel" type="{Model}" lazy="true"/> <parsley:DynamicObject type="{Counter}"> <parsley:Property name="maxCount" value="100" /> </parsley:DynamicObject> <parsley:View type="{UsersView}"/> <service:MockUsersService /> <parsley:MapCommand type="{GetUsersCommand}" messageType="{GetUsersMessage}"/> </fx:Declarations> </parsley:Objects>

UserConfig.mxml

Configuration and Initialization

1

• Telling the IOC Container which classes it should manage

• MXML, XML, AS.

2

• Configure DI or Messaging for each individual class

• MXML, XML, Metadata tags

public class GetUsersCommand { [Inject] public var service:IUsersService; [Inject(id="userModel")] public var model:Model; public function execute():AsyncToken { return service.getUsers(); } public function result(users:ArrayCollection):void { model.users = users; } }

public class UsersView extends SkinnableComponent { [Inject] public var model:Model; [Publish(objectId="selectedUser")] [Bindable] public var seletedUser:User; [MessageHandler(selector="user")] public function externalUserSelection(msg:SelectMessage):void { //do something } }

GetUsersCommand.as

UserView.as

Configuration and Initialization

1

• Telling the IOC Container which classes it should manage.

• MXML, XML, AS.

2

• Configure DI or Messaging for each individual class

• MXML, XML, Metadata tags

3 • Initialize IOC Container

<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" xmlns:mx="library://ns.adobe.com/flex/mx" xmlns:parsley="http://www.spicefactory.org/parsley" <fx:Declarations> <parsley:ContextBuilder config="{UserConfig}"/> </fx:Declarations> </s:Application>

<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" xmlns:mx="library://ns.adobe.com/flex/mx" xmlns:parsley="http://www.spicefactory.org/parsley" <fx:Declarations> <parsley:ContextBuilder> <parsley:FlexConfig type="{UserConfig}"/>

<parsley:FlexConfig type="{ServiceConfig}"/>

<parsley:FlexConfig type="{MockServiceConfig}"/>

</parsley:ContextBuilder

</fx:Declarations> </s:Application>

You can use one big configuration file

Or several files with separated configuration

ContextBuilder.newBuilder() .config(FlexConfig.forClass(UserConfig)) .config(FlexConfig.forClass(ServiceConfig)) .config(XmlConfig.forFile("logging.xml")) .build();

Configuration DSL

Decoupled Bindings [Publish] [Subscribe] [PublishSubscribe]

• This feature is much more dynamic than Dependency Injection. • It is really more the decoupled equivalent of the typical usage of Flex Bindings. • The published object does not even have to be a container managed object.

Decoupled Bindings

[Bindable] [Publish(objectId="selectedUser")] public var selectedUser:User; [Subscribe(scope="window")] public var selectedUser:User; [Bindable] [PublishSubscribe(persistent="true", objectId="selectedUserId")] public var selectedUserId:int;

<parsley:View type="{UsersView}"> <parsley:Publish property="selectedUser“ objectId="selectedUser"/> </parsley:View>

MXML Example

ActionScript Examples

uses Local SharedObjects

Messaging [ManagedEvents] [MessageDispatcher] [MessageHandler] [MessageBinding] [MessageError]

• The exchange messages between objects in a fully decoupled manner (sender and the receiver do not have to know each other)

• Messaging Framework is generic, it does not impose a particular usage style

[Event(name="userSelected",type="com.xyz.UserSelectedEvent")] [ManagedEvents("userSelected, somethingSelected")] public class UsersView extends SkinnableComponent { private function handleUserSelect(user:User):void { dispatchEvent(new UserSelectedEvent("userSelected", user)); } }

ActionScript Examples

public class UsersView extends SkinnableComponent { [MessageDispatcher] public var dispatcher:Function; private function handleUserSelect(user:User):void { dispatcher(new UserSeletedMessage(user)); } }

UserSelectedEvent.as Event based class

UserSeletedMessage.as Simple class

[MessageHandler(type="com.xyz.UserSelectedEvent", messageProperties="user, role“)] public function handleUserSelect(user:User, role:String):void { }

[MessageBinding(messageProperty="user", type="com.xyz.UserSelectedEvent")] public var user:User;

Object Lifecycle [Init] [Destroy]

preConfigure preInit postInit preDestroy postDestroy

Lifecycle phases

Object Lifecycle [Init] [Destroy]

[Init] public function init():void { // }

[Destroy] public function destroy():void { // }

The methods marked with [Init] get invoked after the object has been instantiated and all injections have been processed.

The methods marked with [Destroy] get invoked after the Context instance they belong to has been destroyed with Context.destroy() or when the object was removed from the Context.

Lifecycle phases

preConfigure preInit postInit preDestroy postDestroy

Managed Commands

• Dynamically add a command to a Context only for the time it executes • Declarative way to configure Commands • Grouping commands (parallel, sequence, flow) • Map command to message

Managed Commands <parsley:MapCommand type="{GetUserProfileCommand}"/>

public class GetUserProfileCommand { [Inject("userService")] public var service:RemoteObject; public function execute(msg:GetUserMessage):AsyncToken { return service.getUserProfile(msg.userId); } public function result(profile:Profile):void { // } }

[CommandResult] public function profileResult(profile:Profile, message:GetUserMessage):void { [CommandError] public function handleResult(fault:FaultEvent, trigger:GetUserMessage):void {

[CommandResult] [CommandError] [CommandComplete] [CommandStatus]

[Bindable] [CommandStatus(type="com.xyz.GetUserMessage")] public var isGettingProfile:Boolean;

<s:Button label="GetProfile" enabled="{isGettingProfile}" click="..." />

Managed Commands

<parsley:MapCommand messageType="{FindUserMessage}"> <parsley:CommandSequence> <parsley:Command type="{GetUsersCommand}"/> <parsley:Command type="{FindUserCommand}"/> </parsley:CommandSequence> </parsley:MapCommand>

Declaring Groups in MXML

<parsley:MapCommand messageType="{FindUserMessage}"> <parsley:ParallelCommands> <parsley:Command type="{FindUserCommand}"/> <parsley:Command type="{OutputDataComand}"/> </parsley:ParallelCommands> </parsley:MapCommand>

Building MVC Architectures

Custom Scopes

Custom Scopes

[ManagedEvents("add, remove", scope=“global")] [MessageDispatcher(scope="local")] public var dispatcher:Function; [MessageHandler(selector="add", scope="local")] public function save (event:UdpateUser):void { [Subscribe(objectId="selectedUser", scope="window")] public var selectedUser:User;

<parsley:ContextBuilder> <parsley:FlexConfig type="{UserConfig}"/> <parsley:FlexConfig type="{ServiceConfig}"/> <parsley:Scope name="window" inherited="true"/> <parsley:MessageSettings defaultReceiverScope="local"/> </parsley:ContextBuilder>

Default scope is global

Add new “window” scope

Change default scope to local.

Actionscript Examples

Localization

[ResourceBinding(bundle="resources", key="user.profile")] public var message:String;

<s:Label text="{resourceManager.getString('resources', ‘user.profile')}"/>

Usual using with flex binding

It is useful for properties of objects managed by the IOC Container

<parsley:View type="{ProfileView}"> <parsley:ResourceBinding property="header" bundle="resources" key="user.profile" /> </parsley:View>

MXML Example

Configuration Properties project.properties

dev_url = http://www.dev.xyz.com prod_url = http://www.prod.xyx.com version = 1.0.0

Property File compiled into the Application

<fx:Declarations> <fx:String id="properties" source="project.properties" /> </fx:Declarations> <parsley:ContextBuilder <parsley:PropertiesString source="{properties}"/> </parsley:ContextBuilder>

External Property File <parsley:ContextBuilder <parsley:PropertiesFile file="project.properties" /> </parsley:ContextBuilder>

<parsley:Object type="{Config}"> <parsley:Property name="dev" value="{properties.dev_url}" /> <parsley:Property name="prod" value="{properties.prod_url}" /> <parsley:Property name="version" value="{properties.version}" /> </parsley:Object>

Using Properties in MXML Configuration

Conclusion

Questions?

Information

Understanding

top related