June 17, 2005
DRY, and by the way, encapsulate what will change
Don't repeat yourself. When you write the same code more than once, you are starting to paint yourself into a corner.
See, for instance, this example.
I'm working on an application where I'm going to use the UI components. It's a MVC application, so the view is completely abstracted from the application's logic.
There's more than one view ( there are many different screens where I have to show the model data in different ways ), so I've thought that it will be better to build a different class for each view ( each screen ).
So, each view will attach a movieclip that contains the screen's graphics and interactive elements ( buttons, datagrids, whatever ). All those view inherit from a class that implements event dispatching, and some more common code. That's one reason to not inherit from movieclip. There are other ones, but I should not want to discuss them now. Anyway...
Also, I still don't have the definitve graphic assests. Even more, if the budget is big enough, I'll have to develop my own set of components, and use it.
So, imagine that you run the application. The first screen you'll find will be the login screen.
As you see in the screenshot, that screen contains a component. Here's the LoginView code:
import mx.utils.Delegate; class LoginView { private var timeline : MovieClip; private var loginScreen: MovieClip; function LoginView( tl: MovieClip ) { this.timeline = tl; } public function init( ) { this.loginScreen = this.timeline.attachMovie( "LoginScreen", "LoginScreen", this.timeline.getNextHighestDepth( ), { _x: 0, _y: 0 } ); this.loginScreen.loginBtn.label= "Go!"; this.loginScreen.loginBtn.addEventListener( "click", Delegate.create( this, click ) ); } private function click( ) { trace( "click" ); } }
So, when I want to show that screen, I'll do something like:
var loginView: LoginView = new LoginView( this ); loginView.init( );
And the button doesn't work. It doesn't even shows the right message. Some minutes of googling, and a bit of common sense and knowledge of the platform, told me that maybe, the button is not initialized when the movieclip is attached, so maybe I should wait a frame to assign its text.
import mx.utils.Delegate; class View { private var timeline : MovieClip; private var loginScreen: MovieClip; function View( tl: MovieClip ) { this.timeline = tl; } public function init( ) { this.loginScreen = this.timeline.attachMovie( "LoginScreen", "LoginScreen", this.timeline.getNextHighestDepth( ), { _x: 0, _y: 0 } ); var theView: View = this; this.loginScreen.onEnterFrame = function( ) { theView.initButton( ); delete this.onEnterFrame; } } private function initButton( ) { this.loginScreen.loginBtn.label= "Go!"; this.loginScreen.loginBtn.addEventListener( "click", Delegate.create( this, click ) ); } private function click( ) { trace( "click" ); } }
It's solved. But this solution smells ( not only because of the scope trick ). I'll have various screens that contain components ( maybe 10-12 ), so I'll have to write that code in 10 classes. Sure, I could write a base class and move that code to that base class, but that's not a solution, because not all my views will have the same amount of buttons, so there's not sense in subclassing just to override later. And my views are subclasses of another base class yet.
Even worse, probably, I'll have to develop my own components, so maybe, that code will have to be changed.
So, I'm repeating code, and that code will be probably changed in a near future. What should I do?. Encapsulate it into its own class.
To do so, I'll use my old Callback class:
class Callback { private var callbackObjVal: Object; private var callbackMethodVal: String; public function Callback( objParam: Object, methodParam: String ) { this.callbackObjVal = objParam; this.callbackMethodVal = methodParam; } public function fire( parameter: Object ): Object { return this.callbackObjVal[ this.callbackMethodVal ]( parameter ); } }
So, I'll write a ButtonHandler class:
class ButtonHandler { public static function initButton( mc: MovieClip, callback: Callback ) { var cb: Callback = callback; mc.onEnterFrame = function( ) { cb.fire( ); delete this.onEnterFrame; } } }
And I'll use it this way:
import mx.utils.Delegate; class View { private var timeline : MovieClip; private var loginScreen: MovieClip; function View( tl: MovieClip ) { this.timeline = tl; } public function init( ) { this.loginScreen = this.timeline.attachMovie( "LoginScreen", "LoginScreen", this.timeline.getNextHighestDepth( ), { _x: 0, _y: 0 } ); ButtonHandler.initButton( this.loginScreen, new Callback( this, "initButton" ) ); } private function initButton( ) { this.loginScreen.loginBtn.label= "Go!"; this.loginScreen.loginBtn.addEventListener( "click", Delegate.create( this, click ) ); } private function click( ) { trace( "click" ); } }
Overkill?. Well, the solution is a bit complex, but it's the one that most satisfies me, because it's the one that brings more flexibility. If I have to change the way I manage the buttons, I just have to change the implementation of one method in one class, and I don’t have to change any interface. I'm coding for the interface, not for the implementation.
Posted by Cesar Tardaguila Date: June 17, 2005 11:49 AM | TrackBackAs always a treat to read :)
thanks
Darren
hi, what plugin you use for as2 syntax highlighting ?
it's pretty neat :-)