June 09, 2004
An example of the proxy pattern
I want to save a list of registered users, but I don't know exactly where to save it. If the user selects a checkbox ( that represents the internet connection ), I'll send the data to a server-side script, in XML format. If the user unchecks the checkbox ( representing that there is not a physical internet connection ), I'll write the data to a Shared Object.
So, this is a mission for the Proxy Pattern!!. Why?. Because I want to isolate the proccess of saving data from the rest of the application, so I just want to say "hey, save this", and don't worry about how ( or where ) it is saved.
That's what this pattern does. The proxy class stores a reference to an object , that will be the real subject of the action. But, the proxy itself creates that object, so it can create an instance of one class or another depending on certain run-time conditions.
So, all the possible classes to be created by the proxy must implement the same interface. And, depending of the kind of proxy, it can be also a good idea to make the proxy class implement also the same interface, so, if needed, it can be replaced.
Some code will help to understand it. First the interface:
interface ISaveable { public function saveUserList( theList: Array ); }
Easy, isn't it?. We want to save a list of users, so we'll have to implement a saveUserList method
Now, the class that handles the "save to server" feature
class myXML extends XML implements ISaveable { private var userArray: Array; function myXML( ) { this.userArray = new Array( ); } function sendData( ) { //this.sendAndLoad( ); } public function saveUserList( theList: Array ) { trace( "The XML is handling the data" ); this.userArray = theList; //create the XML nodes, and send to server } }
Not too complicated. Just a public method to receive an array, and then I'll have to create the XMl nodes, and send the XML to the server.
Well, now the class that saves data to disk
class mySO implements ISaveable { private var theSO: SharedObject; private var userArray: Array; function mySO( ) { this.userArray = new Array( ); } public function saveUserList( theList: Array ) { trace( "The Shared Object handles the data" ); this.userArray = theList; } }
Again, this class implements ISaveable, so after receiving the data, it will try to save it to the contained Shared Object
Now, the tricky part. The value of the checkbox ( the connection status ) is represented here by a variable placed at _root. But, to get this variable value, I'll use a Singleton ( here you can find an implementation by Dave Yang ), That's just a way to have a global accesor to that value, but I prefer it that just using globals. Here is the singleton:
class singleton { private static var inst:singleton; private function singleton() {} public static function get instance():singleton { if (inst == null) inst = new singleton(); return inst; } public function isConnected( ): Boolean { return _root.connected; } }
And finally, the proxy class
class saveUserListProxy implements ISaveable { private var realClass: ISaveable; function saveUserListProxy( ) { var config: singleton = singleton.instance; var conFlag: Boolean = config.isConnected( ); if ( conFlag ) { this.realClass = ISaveable( new myXML( ) ); } else { this.realClass = ISaveable( new mySO( ) ); } } public function saveUserList( theList: Array ) { this.realClass.saveUserList( theList ); } }
So, finally, for this example, in the main timeline, we'll have
var connected: Boolean = false; var theUserList: Array = new Array( ); theUserList.push( { id: 0, name: "Cesar" } ); theUserList.push( { id: 1, name: "Javier" } ); var myProxy: saveUserListProxy = new saveUserListProxy( ); myProxy.saveUserList( theUserList );
And that's all. Test it changing the values of the variable "connected". And sorry for my poor english. It's been particularly poor tonight :(
Posted by Cesar Tardaguila Date: June 9, 2004 01:18 AM | TrackBackThis is exactly what I needed, and in pretty good English, too (far, far better than my Spanish in any case.) Thanks so much!
Posted by: Ron en: June 9, 2004 01:51 PMCesar,
Not to be too picky, but just to avoid the spread of misinformation:
1) This is actually an example of a design pattern called State, which could be considered an extension of Proxy, but isn't a great example of a true proxy.
2) Niggly, but important: your class names should always begin with a capital letter (SaveUserListProxy, not saveUserListProxy). This allows other developers to quickly tell instances and classes apart.
3) Also niggly: Singleton is an awful name for a class. :) ConnectionState, or ConnectionStateSingleton would be better.
Cheers!
Grant.
Thanks for you comments, Grant.
However, I disagree with your comment about the State pattern, because here I have not implemented any transition from one state to another, just because I don't want to change the states at runtime, I just want to pick between two different implmentations of the same process ( saving the user data ).
Maybe I didn't explain it correctly, but the user chooses where to save the data, just before it is saved, and this process happens only once, so there are no changes in the behaviour. That's why I consider this to be a proxy, more than an example of the state pattern.
By the way, I strongly agree with you about the third point. Singleton is an awful name for a class.
Posted by: Cesar Tardaguila en: June 9, 2004 04:09 PMInfo on State: http://www.dofactory.com/Patterns/PatternState.aspx
Posted by: Grant Skinner en: June 9, 2004 04:10 PMAnd Proxy: http://www.dofactory.com/Patterns/PatternProxy.aspx
(sorry for the multiple posts) :)
Posted by: Grant Skinner en: June 9, 2004 04:13 PMThanks for the link, Grant.
There you could see what I meant:
"Allow an object to alter its behavior when its internal state changes. The object will appear to change its class."
That's why I don't consider this example as an example of a state, because it's not the internal behaviour of that class what makes it change its state, I mean, the state doesn't change, it's just picked between two possible implementations.
Just to explain it a bit more clearly.
When the instance of saveUserListProxy is created, the value of _root.connection has been previously setted, and cannot be changed.
I should have explained it better before. :(
Posted by: Cesar Tardaguila en: June 9, 2004 04:26 PMPatterns are often so related in their intent that it doesn't much matter what you call it. What matters is it works, it's extensible, and it's easy to manage. I do agree however, the state pattern is usually better served under different circumstances.
Primarily, the object implementing the collection of states, or the states themselves, should manage the objects _internal_ state. I don't see 'connected' as a logical state of an IO mechanism. Connected would be better implemented as a state of the applications environment. With that, you could consider creating a saveState in the 'proxy'. This still has some problems however. For one, the state is now being managed outside of the object. To solve this you may listen for connection state changes inside the object, but this creates a direct dependency on the environment in the object.
I wouldn't use the state pattern or the proxy in this circumstance. Instead I would use the Strategy Pattern. this.saveStrategy.saveUserList( list ). This is more appropriate as the pattern allows for the strategy to be configured on the outside of and passed in to the context and it takes the responsibility of managing and keeping track of the connection state out of the IO mechanism. It makes management easier down the road by clarifying, in the naming convention, what is happening. For instance, saveState could be construed as the state of the save opperation, success, fail, pending, etc. Where as saveStrategy implies exactly what it is, the way or type of save that is occuring.
If you need an excuse for a Proxy you could write one that serializes the method call and arguments and sends it to a server to do the work, or over a LocalConnection. Or implement a proxy that queues asynchronous methods, ensuring prior calls are finished before additional calls are made.
In the end... if it works, it works. Patterns are not meant to be strict sets of rules that can never be broken. They are guidelines, and it often works best (especially in ActionScript) to implement hybrids of many patterns as one solution.
my .02, anyway.
Thanks for your comment, compiled.
I know this was not a very good example. Anyway, I haven't thought about the strategy pattern. It's a good idea, I think.
Posted by: Cesar Tardaguila en: July 6, 2004 08:38 AM