sketchucation logo sketchucation
    • Login
    ℹ️ Licensed Extensions | FredoBatch, ElevationProfile, FredoSketch, LayOps, MatSim and Pic2Shape will require license from Sept 1st More Info

    Observer interface (best practices?)

    Scheduled Pinned Locked Moved Developers' Forum
    9 Posts 3 Posters 1.0k Views 3 Watching
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • A Offline
      Aerilius
      last edited by Aerilius

      So far always when I used an observer, I followed the API example and created a subclass of one of SketchUp's observer classes. This poses some challenges for delegating an observer event to a private method in an instance of my main class.
      Very simple example:

      <span class="syntaxdefault"><br />class MyTool<br />  def initialize<br />    </span><span class="syntaxkeyword">@</span><span class="syntaxdefault">webdialog </span><span class="syntaxkeyword">=</span><span class="syntaxdefault"> UI</span><span class="syntaxkeyword">;;</span><span class="syntaxdefault">WebDialog</span><span class="syntaxkeyword">.new()<br /></span><span class="syntaxdefault">    </span><span class="syntaxcomment"># …<br /></span><span class="syntaxdefault">    Sketchup</span><span class="syntaxkeyword">.</span><span class="syntaxdefault">active_model</span><span class="syntaxkeyword">.</span><span class="syntaxdefault">selection</span><span class="syntaxkeyword">.</span><span class="syntaxdefault">add_observer</span><span class="syntaxkeyword">(</span><span class="syntaxdefault">MySelectionObserver</span><span class="syntaxkeyword">.new)<br /></span><span class="syntaxdefault">  end<br /><br />  def updateSelectionInWebDialog</span><span class="syntaxkeyword">(</span><span class="syntaxdefault">entities</span><span class="syntaxkeyword">)<br /></span><span class="syntaxdefault">    </span><span class="syntaxcomment"># passes entities to webdialog…<br /></span><span class="syntaxdefault">  end<br />end<br /><br />class MySelectionObserver </span><span class="syntaxkeyword"><</span><span class="syntaxdefault"> Sketchup</span><span class="syntaxkeyword">;;</span><span class="syntaxdefault">SelectionObserver<br />  def onSelectionBulkChange</span><span class="syntaxkeyword">(</span><span class="syntaxdefault">selection</span><span class="syntaxkeyword">)<br /></span><span class="syntaxdefault">    </span><span class="syntaxcomment"># needs to call instance method of MyTool instance<br /></span><span class="syntaxdefault">    </span><span class="syntaxcomment"># * either I keep a public globally accessible variable/constant/method to get the current instance of MyTool<br /></span><span class="syntaxdefault">    </span><span class="syntaxcomment"># * or I pass the MyTool instance (as self) to the initialize method of this observer instance<br /></span><span class="syntaxdefault">  end<br />end<br /></span>
      

      Now seeing that an observer does not need to be its own class, but rather an arbitrary class (like MyTool) that can behave like the specific observer should = "implements the interface" (or in Ruby: "responds to" onSelectionBulkChange), I could simplify it like this:

      <span class="syntaxdefault"><br /></span><span class="syntaxcomment"># implements onSelectionBulkChange from Sketchup;;SelectionObserver<br /></span><span class="syntaxdefault">class MyTool<br />  def initialize<br />    </span><span class="syntaxkeyword">@</span><span class="syntaxdefault">webdialog </span><span class="syntaxkeyword">=</span><span class="syntaxdefault"> UI</span><span class="syntaxkeyword">;;</span><span class="syntaxdefault">WebDialog</span><span class="syntaxkeyword">.new()<br /></span><span class="syntaxdefault">    Sketchup</span><span class="syntaxkeyword">.</span><span class="syntaxdefault">active_model</span><span class="syntaxkeyword">.</span><span class="syntaxdefault">selection</span><span class="syntaxkeyword">.</span><span class="syntaxdefault">add_observer</span><span class="syntaxkeyword">(</span><span class="syntaxdefault">self</span><span class="syntaxkeyword">)<br /></span><span class="syntaxdefault">  end<br /><br />  def updateSelectionInWebDialog</span><span class="syntaxkeyword">(</span><span class="syntaxdefault">entities</span><span class="syntaxkeyword">)<br /></span><span class="syntaxdefault">    </span><span class="syntaxcomment"># passes entities to webdialog…<br /></span><span class="syntaxdefault">  end<br /><br />  def onSelectionBulkChange</span><span class="syntaxkeyword">(</span><span class="syntaxdefault">selection</span><span class="syntaxkeyword">)<br /></span><span class="syntaxdefault">    updateSelectionInWebDialog</span><span class="syntaxkeyword">(</span><span class="syntaxdefault">selection</span><span class="syntaxkeyword">.</span><span class="syntaxdefault">entities</span><span class="syntaxkeyword">)<br /></span><span class="syntaxdefault">  end<br />end<br /></span>
      

      This does not follow the documentation. There are also some differences:

      • I could use one class (MyTool) to act as observer for different SketchUp entities ( onSelectionBulkChange, onViewChanged, onLayerAdded)
      • SketchUp's observer classes define all callback methods with empty implementation. Thus a subclass inherits them (and you can call them but nothing happens) where as my not-subclass does not respond to all callbacks.
        SketchUp currently does not raise a NotImplementedError. But theoretically it could do so (and it's not specified by the documentation).
      • Once more it becomes clear that many object oriented concepts (interfaces, abstract classes/methods) don't directly apply to Ruby (which has no more than plain classes/modules). Yet Ruby programming can be very object-oriented (we would include mixin modules or extend objects with mixin modules).

      How do you use observers, as subclass of a Sketchup observer class or as your own class that implements that interface?

      Would it be advisable what I showed above? Is it guaranteed that SketchUp will not raise errors in future if some methods are not implemented?

      1 Reply Last reply Reply Quote 0
      • Dan RathbunD Offline
        Dan Rathbun
        last edited by

        @aerilius said:

        Would it be advisable what I showed above?

        NO. It is poor practice.

        Observer classes should at least be a subclass of one of the API superobservers.
        I WOULD like a master Sketchup::Observer superclass (which would actually be implemented as a mixin module so it could not be instantiated,) for ALL observer superclassses.

        The idea of a MetaObserver is not new. Jim Foltz has been using them for years.
        IF there was a master mixin superclass module, one could define a metaobserver like:

        module Author
          module Plugin
        
            class MetaObserver
              include(Sketchup;;Observer)
              
              # callback overrides, etc.
        
            end
        
          end
        end
        
        

        In this way the author's class inherits the common functionality, can be listed along with ALL other observer classes as a descendant of Sketchup::Observer (because it is now the direct ancestor via inclusion, and appears as the first parent in the ancestor list.)

        @aerilius said:

        Is it guaranteed that SketchUp will not raise errors in future if some methods are not implemented?

        NO

        I have long been pushing the idea that tool classes need a proper superclass (either Sketchup::Tool or UI::Tool,) from which standard behavior and instance methods would be inherited. (There is an example I posted in the SKX Extension forum.)

        Ya'll may be forgetting that each model (of which multiple might exist on the Mac,) has it's OWN selection collection instance object, it's OWN tools collection instance object (which holds a set of references to model specific tool instance objects.)

        (These facts negate Anton's whimsical idea that tools objects may not have to be instances. Even though technically, a module is actually an instance of class Module.)

        I'm not here much anymore.

        1 Reply Last reply Reply Quote 0
        • Dan RathbunD Offline
          Dan Rathbun
          last edited by

          @anton_s said:

          This is how I think Sketchup Observers work.

          They cannot. The list (array) of observer instances would be wiped out each time a new instance was initialized, in your example.

          I'm not here much anymore.

          1 Reply Last reply Reply Quote 0
          • Dan RathbunD Offline
            Dan Rathbun
            last edited by

            @Aerilius : a few points.

            Selection observers are not added via a method call upon the model, ... there is an add method upon the model's selection collection instance.

            It was allowable for classes to define nested classes. Did this change in Ruby 2.0 ?

            I'm not here much anymore.

            1 Reply Last reply Reply Quote 0
            • A Offline
              Aerilius
              last edited by

              @anton_s said:

              This is how I think Sketchup Observers work.

              Then it's probably how the Sketchup::Selection class works, to which selection observers are added.

              1 Reply Last reply Reply Quote 0
              • A Offline
                Aerilius
                last edited by

                @dan rathbun said:

                … there is an add method upon the model's selection collection instance.

                I fixed the mistake. Initially I had changed the example (model → selection) of how I wanted to illustrate the question, but it changes nothing on the core topic in question.

                1 Reply Last reply Reply Quote 0
                • Dan RathbunD Offline
                  Dan Rathbun
                  last edited by

                  @aerilius said:

                  How do you use observers, as subclass of a Sketchup observer class or as your own class that implements that interface?

                  I do what the API docs suggest.

                  ONE application observer that watches the application, and attaches a model observer subclass instance to models when they are opened or created.

                  INSIDE the model observer instance, are instance variables that hold references to any other model specific observer instances. IE, the model observer instantiates and attaches any subordinate observers needed. The "handle" to the model and / or the model observer can be passed down into any or all of the subordinate observers.

                  Lastly.. some problems with private methods can be overcome by using protected methods instead.

                  I'm not here much anymore.

                  1 Reply Last reply Reply Quote 0
                  • A Offline
                    Aerilius
                    last edited by

                    @dan rathbun said:

                    @aerilius said:

                    Would it be advisable what I showed above?

                    NO. It is poor practice.
                    Observer classes should at least be a subclass of one of the API superobservers.

                    This is likely to have proper types ( is_a?(Sketchup::Observer)) and avoid unimplemented methods.

                    But the current solution in the first example is overcomplicated and distracts from the idea of what an observer is used for. An observer is an object that is notified about a certain events, and in the above case the "tool" class needs to be notified somehow. I agree that this observer should implement the interface of the specific observer (in order to have the behavior of a SelectionObserver, it must implement all its methods etc.).

                    But (in other ooo programming languages) it is legitimate to implement multiple interfaces, and my class would be able to observe multiple API objects. In Ruby, subclass inheritance does not allow this:
                    class MyTool < Sketchup::SelectionObserver, Sketchup::MaterialsObserver
                    In Ruby something like multiple inheritance can rather be achieved by mixin modules, and interfaces probably should be done as mixin modules(?).
                    class MyTool include(Sketchup::SelectionObserver, Sketchup::MaterialsObserver)

                    So I agree totally that the API should provide the observers as modules.

                    @aerilius said:

                    Is it guaranteed that SketchUp will not raise errors in future if some methods are not implemented?

                    We can so far only adhere to the documentation (and everything not mentioned will break in future), but considering the limited examples, I don't see that as a source to develop further skills. So this was rather supposed to get more people involved, especially some experienced with the internals or design patterns of the API.

                    1 Reply Last reply Reply Quote 0
                    • A Offline
                      Anton_S
                      last edited by

                      Hmm, thats a good example. Now, I won't have to use variables to tranfer data between two different class instances. Simply having 'em all in one class is good implementation. 👍

                      This is how I think Sketchup Observers work.

                      <span class="syntaxdefault"><br />class Sketchup</span><span class="syntaxkeyword">;;</span><span class="syntaxdefault">Selection<br /><br />  def initialize<br />    </span><span class="syntaxkeyword">@</span><span class="syntaxdefault">observers </span><span class="syntaxkeyword">=</span><span class="syntaxdefault"> </span><span class="syntaxkeyword">[]<br /></span><span class="syntaxdefault">  end<br /><br />  def add_observer</span><span class="syntaxkeyword">(</span><span class="syntaxdefault">obj</span><span class="syntaxkeyword">)<br /></span><span class="syntaxdefault">    </span><span class="syntaxkeyword">@</span><span class="syntaxdefault">observers </span><span class="syntaxkeyword"><<</span><span class="syntaxdefault"> obj<br />    true<br />  end<br /><br />  def remove_observer</span><span class="syntaxkeyword">(</span><span class="syntaxdefault">obj</span><span class="syntaxkeyword">)<br /></span><span class="syntaxdefault">    </span><span class="syntaxkeyword">@</span><span class="syntaxdefault">observers</span><span class="syntaxkeyword">.</span><span class="syntaxdefault">delete</span><span class="syntaxkeyword">(</span><span class="syntaxdefault">obj</span><span class="syntaxkeyword">)</span><span class="syntaxdefault"> </span><span class="syntaxkeyword">?</span><span class="syntaxdefault"> true </span><span class="syntaxkeyword">;</span><span class="syntaxdefault"> false<br />  end<br /><br />  </span><span class="syntaxcomment"># Some method triggered by SketchUp private API.<br /></span><span class="syntaxdefault">  def _trigger_selection_added</span><span class="syntaxkeyword">(</span><span class="syntaxdefault">sel</span><span class="syntaxkeyword">,</span><span class="syntaxdefault"> element</span><span class="syntaxkeyword">)<br /></span><span class="syntaxdefault">    </span><span class="syntaxkeyword">@</span><span class="syntaxdefault">observers</span><span class="syntaxkeyword">.</span><span class="syntaxdefault">each </span><span class="syntaxkeyword">{</span><span class="syntaxdefault"> </span><span class="syntaxkeyword">|</span><span class="syntaxdefault">obj</span><span class="syntaxkeyword">|<br /></span><span class="syntaxdefault">      next unless obj</span><span class="syntaxkeyword">.</span><span class="syntaxdefault">respond_to</span><span class="syntaxkeyword">?(;</span><span class="syntaxdefault">onSelectionAdded</span><span class="syntaxkeyword">)<br /></span><span class="syntaxdefault">      begin<br />        </span><span class="syntaxkeyword">@</span><span class="syntaxdefault">obj</span><span class="syntaxkeyword">.</span><span class="syntaxdefault">onSelectionAdded</span><span class="syntaxkeyword">(</span><span class="syntaxdefault">sel</span><span class="syntaxkeyword">,</span><span class="syntaxdefault"> element</span><span class="syntaxkeyword">)<br /></span><span class="syntaxdefault">      rescue Exception </span><span class="syntaxkeyword">=></span><span class="syntaxdefault"> e<br />        puts </span><span class="syntaxstring">"#{e}\n#{e.backtrace.first}"<br /></span><span class="syntaxdefault">      end<br />    </span><span class="syntaxkeyword">}<br /></span><span class="syntaxdefault">  end<br /><br />  </span><span class="syntaxcomment"># ...<br /></span><span class="syntaxdefault">end<br /></span>
                      

                      I think the event is called only if the tool contains that method.
                      This might tell us that you tool isn't event supposed to be an instance. It may be a module as well. As long as the methods are accessible ( self.onSelectionAdded) your good to go.

                      1 Reply Last reply Reply Quote 0
                      • 1 / 1
                      • First post
                        Last post
                      Buy SketchPlus
                      Buy SUbD
                      Buy WrapR
                      Buy eBook
                      Buy Modelur
                      Buy Vertex Tools
                      Buy SketchCuisine
                      Buy FormFonts

                      Advertisement