9 Replies Latest reply on Aug 29, 2007 10:55 AM by Daniel_Summers

    Singleton Pattern Problem

    Daniel_Summers
      I'm trying to be a well-behaved OO ActionScripter by using the Singleton Pattern to have System settings for an application.
      Below is the code and it works well:


      package com.gh {

      [Bindable]
      public class SystemVars {

      private static var _instance:SystemVars;
      private var _serverIP:String = "10.0.0.228";
      private var _serverPort:int = 7700;

      public function SystemVars( se:SingletonEnforcer ):void {
      }

      public static function getInstance():SystemVars {
      if( !SystemVars._instance ) {
      SystemVars._instance = new SystemVars(new SingletonEnforcer());
      }
      return SystemVars._instance;
      }

      public function get serverIP():String {
      return _serverIP;
      }

      public function set serverIP( serverIP:String ):void {
      _serverIP = serverIP;
      }

      public function get serverPort():int {
      return _serverPort;
      }

      public function set serverPort( serverPort:int ):void {
      _serverPort = serverPort;
      }
      }
      }

      class SingletonEnforcer {
      public function SingletonEnforcer():void {
      return;
      }
      }


      The problem comes when I want to not only use the Singleton Pattern, but have the variables set from a database using an HTTPService:


      package com.gh {
      import mx.rpc.http.HTTPService;
      import mx.rpc.events.ResultEvent;


      [Bindable]
      public class SystemVars {

      private static var _instance:SystemVars;
      private var _serverIP:String;
      private var _serverPort:int;
      private var _httpService:HTTPService;

      public function SystemVars( se:SingletonEnforcer ):void {
      }

      public static function getInstance():SystemVars {
      if( !SystemVars._instance ) {
      SystemVars._instance = new SystemVars(new SingletonEnforcer());
      SystemVars._instance.setVars();
      }
      return SystemVars._instance;
      }

      public function get serverIP():String {
      return _serverIP;
      }

      public function set serverIP( serverIP:String ):void {
      _serverIP = serverIP;
      }

      public function get serverPort():int {
      return _serverPort;
      }

      public function set serverPort( serverPort:int ):void {
      _serverPort = serverPort;
      }

      private function setVars():void {
      _httpService = new HTTPService();
      _httpService.url = " http://10.0.0.228/data/system_vars.php";
      _httpService.resultFormat = HTTPService.RESULT_FORMAT_E4X;
      _httpService.addEventListener(ResultEvent.RESULT, httpServiceResultHandler);
      _httpService.send();
      }

      private function httpServiceResultHandler( event:ResultEvent ):void {
      _serverIP = String( event.result.serverIP );
      _serverPort = int( event.result.serverPort );
      }

      }
      }

      class SingletonEnforcer {
      public function SingletonEnforcer():void {
      return;
      }
      }


      This works just fine, except for values are null until the HTTPService returns a result.
      What I'd like to do is not return the instance until there's a result, but is that even possible?

      Or should I use a different approach?
        • 1. Re: Singleton Pattern Problem
          ntsiii Level 3
          I do not see how. The http service call is asynchronous, but your property access is synchronous. But I'm only moderately good at oop.

          Well, you could get the values from an event handler. Have this class dispatch an event in the result handler function, listen for it in the parent, and in the listener, get the values using event.target.setverIP;

          Tracy
          • 2. Re: Singleton Pattern Problem
            flexPro
            Tracy's analysis of the situation above is accurate.

            Although not ideal, one way to handle this would be to use a callback. Basically, instead of returning a reference to your singleton directly, you would require a callback that expects a single argument, which will be a reference to your singleton. If the service has already returned with results, you simply invoke this callback immediately. Otherwise, you add it to an array (since getInstance may be called multiple times before the service has returned) and then you invoke those callbacks in your service's result handler. Below are the changes you would make to your code:

            private var _callbacks:Array = [];

            public static function getInstance(onInit:Function):void {
            if( !SystemVars._instance ) {
            SystemVars._instance = new SystemVars(new SingletonEnforcer());
            SystemVars._instance.setVars();
            }
            else if(!_serverIP) // we're still waiting on a result
            _callbacks.push(onInit);
            else
            onInit(SystemVars._instance);
            }

            private function httpServiceResultHandler( event:ResultEvent ):void {
            _serverIP = String( event.result.serverIP );
            _serverPort = int( event.result.serverPort );

            for(var i:int = 0; i < _callbacks.length; i++) {
            var fn:Function = _callbacks ;
            fn(this);
            }
            _callbacks = null; // we're done with it, so free the memory
            }
            • 3. Re: Singleton Pattern Problem
              flexPro Level 1
              Oops... just noticed I forgot the following line in the if( !SystemVars._instance ) clause:

              _callbacks.push(onInit);
              • 4. Re: Singleton Pattern Problem
                Daniel_Summers Level 1
                Well put. Would definitely work as you've suggested.
                We've moved changed it around a bit to also catch for other situations, but kudos for the solution!
                • 5. Re: Singleton Pattern Problem
                  _Cal
                  I found this thread interesting but I don't understand how you would instantiate the SystemVars class. What would be the mxml side of things?
                  • 6. Re: Singleton Pattern Problem
                    Daniel_Summers Level 1
                    The instantiation would be static. So you could access it by saying:

                    SystemVars.getInstance().serverIP

                    Being static you don't have to create a "new" instance of it before using it. Best used when multiple classes are sharing the same data and most specifically when you don't want more that one instance created.
                    • 7. Re: Singleton Pattern Problem
                      _Cal Level 1
                      When I use SystemVars.getInstance().serverIP I get the error

                      "Access of possibly undefined property serverIP through a reference with static type void."

                      I copied the code from this thread along with the callback revisions. I can get everything working without the singleton class but when I include it I get the above error. I'm sure I'm missing something but can't see it. Any ideas?
                      • 8. Re: Singleton Pattern Problem
                        _Cal Level 1
                        Thanks for your help Daniel. I managed to get everything working and the class is instantiated once only.

                        I did have to make the following changes to make the compiler happy.

                        private static var _callbacks:Array = [];

                        public static function getInstance(onInit:Function):Object {
                        return _instance; // for the getInstance routine

                        I'm just getting my legs on this stuff so I'm not quite sure why there is a difference in the code samples. But thanks again.
                        • 9. Re: Singleton Pattern Problem
                          Daniel_Summers Level 1
                          I'm glad - it's a unique concept to grasp when learning it for the first time, but it's definitely one you'll use frequently once you see the power behind it.