Observers – why subclass?


#1

All of the example code for observers shows subclassing them.

Programming languages vary, and Ruby is one language where inner and outer classes are largely unaware of each other.

Also, assuming most people writing plug-ins create only one observer of each type, why should one need to define a class for that single use?

Hence, I tried the following –

@app_obs = Sketchup::AppObserver.new

def @app_obs.onNewModel(model)
  puts "New  Model"
end

def @app_obs.onOpenModel(model)
  puts "Open Model"
end

Sketchup.add_observer(@app_obs)

And everything worked fine, which it should.

So, any reason why one shouldn’t do this? Secondly, for new plug-in authors, it would be nice if this approach was shown in the help docs.

Thanks,

Greg


#2

Technically there is no strict requirement for a subclass - SketchUp will check if the observer object respond to the event. However - there is an exception - when attaching an DimensionObserver you need to subclass in order for the add_observer to know what type of observer you are attaching.


#3

Thomas,

Thanks for the info.

So SU has several objects in the same category (Observers), and one behaves differently. Most programmers would call that a bug that should be fixed.

Greg


#4

Saying “it should” is an assumption. If I had written the API it would not have worked. I would have added in checks to be sure that subclasses were used instead.

Why? Because Ruby is a dynamic language, (which means classes can dynamically modified at any time, during runtime,) new Ruby coders are often tempted to (and do) modify both Ruby base classes and SketchUp API classes for there OWN unique uses. This is a no-no! It can (and does often) break everyone’s else plugins.

Better that the new coder learn to subclass, and make the modifications to their own unique subclass.

Now, … the best reason however (which has not yet been leveraged) is that all observer subclasses can inherited standard functionality, and pass that down to it’s “offspring”. But usually is not desirable to allow this top most functional definition object to be instantiated. So composition (a Observer mixin module) would be used instead. All the API observer classes would include this module, which makes it appear within all their ancestry chains.
Then all custom subclasses would also get the common functionality. They’d get it by subclassing one of the API observer classes, or by explicitly including the common progenitor mixin observer module.

Ruby is designed foremost for inheritance, (even though it also has composition capabilities,) … so teaching and promoting the subclassing protocol (over other methods) to new coders is best, IMO.

What you did is a bit more advanced, because you actually created singleton callback methods instead of instance callback methods.

It works because the API did not enforce a subclass check when the instance object is attached as an observer. (I would have raised a TypeError, if at the least, the instance did not have one of the expected callback methods.)

* Note that some advanced coders write an “omni-observer” class that might be assigned as a observer subclass (or might not.) Then they put ALL of the plugins callbacks in this one observer class. Instance it, and attach it to any thing, of any type, that they will be watching.
So in this scenario, strict class-checking would break these kind of plugins. Duck-typing for a callback method(s) would be safer within the various add_observer methods.

Your singleton approach would also pass duck-typing.

I would feel safer if you at least promoted:

module SomeAuthor::SomePlugin

  @app_obs = Class::new(Sketchup::AppObserver).new

  def @app_obs.onNewModel(model)
    puts "New  Model"
  end

  def @app_obs.onOpenModel(model)
    puts "Open Model"
  end

  Sketchup.add_observer(@app_obs)

end # module

But the main question is does it read any clearer (especially to newer Ruby coders) or save any more space, than:

module SomeAuthor::SomePlugin

  AppSpy = Class::new(Sketchup::AppObserver) {
    def onNewModel(model)
      puts "New  Model"
    end
    def onOpenModel(model)
      puts "Open Model"
    end
  }
  Sketchup.add_observer(AppSpy.new)

end # module

or:

module SomeAuthor::SomePlugin

  class AppSpy < Sketchup::AppObserver
    def onNewModel(model)
      puts "New  Model"
    end
    def onOpenModel(model)
      puts "Open Model"
    end
  end # class AppSpy
  Sketchup.add_observer(AppSpy.new)

end # module

?

The API documentation. It already needs much correction, especially with the current code examples. Doubling up the examples with both class and singleton examples, might just confuse the newbies.

We have been talking elsewhere about juicing up the observer system to make it easier to code, and debug. I did write a test script the began this for a master common progenitor Sketchup::Observer mixin module. But it was only at 50%. The automation half I had not yet written, and we were just beginning to discuss. Everyone is just busy with other things right now.


#5

Thank you for your opinion. You don’t write the API…

[quote="DanRathbun, post:4, topic:17088"]Why? Because Ruby is a dynamic language, (which means classes can dynamically modified at any time, during runtime,) new Ruby coders are often tempted to (and do) modify both Ruby base classes and SketchUp API classes for there OWN unique uses. This is a no-no! It can (and does often) break everyone's else plugins.

Better that the new coder learn to subclass, and make the modifications to their own unique subclass.[/quote]

[Topic Diversion] please start a new topic titled something similar to

“Dan’s thoughts – never change a SketchUp / Ruby class”

And maybe another topic titled something similar to

“Dan’s thoughts – what Ruby is”

[quote="DanRathbun, post:4, topic:17088"]Now, .. the best reason however (which has not yet been leveraged) is that all observer subclasses can inherited standard functionality, and pass that down to it's "offspring". But usually is not desirable to allow this top most functional definition object to be instantiated. So composition (a Observer mixin module) would be used instead. All the API observer classes would include this module, which makes it appear within all their ancestry chains.Then all custom subclasses would also get the common functionality. They'd get it by subclassing one of the API observer classes, or by explicitly including the common progenitor mixin observer module.[/quote]

[Topic Diversion] please start a new topic titled something similar to

“Dan’s thoughts – proposed additions to the Observer classes”

As to your discussion of a mixin, I suspect some of the code creating the API is not written in Ruby, so let’s leave the implementation to the Trimble SketchUp team.

[quote="DanRathbun, post:4, topic:17088"]What you did is a bit more advanced, because you actually created singleton callback methods instead of instance callback methods.[/quote]

[Edit] I apologize, I was unaware of the term ‘singleton method’. AFAIK, it is a term used only in Ruby, as I can find no other languages where that term is used. As you pointed out, it’s also used in several reflection methods.

If you understand that I was creating methods on the instance object, why mention the concept of modifying SketchUp/Ruby classes?

[quote="DanRathbun, post:4, topic:17088"]Ruby is designed foremost for inheritance, (even though it also has composition capabilities,) ... so teaching and promoting the subclassing protocol (over other methods) to new coders is best, IMO.[/quote]

What if your ‘over other methods’ are commonly used techniques in Ruby, like singleton methods?

[Topic Diversion] please start a new topic (or add to the above) titled something similar to

“Dan’s thoughts – what Ruby is”

My original post was about how to create observer callback methods. Callbacks are a special category of method; for instance, if one subclasses an observer, ‘super’ has no meaning. In other languages (Java, .NET), callbacks would often be implemented as an interface, not as a class. But Ruby doesn’t have implements / interface…

So that leaves two ways to create them, either by subclassing or by using singleton methods.

Greg


Potential methods of combining 2 arrays into a single concatenated array string (TOPIC TITLE EDITED)
#6

Notice that I used the lower case when referring to singleton ?

Your example does exactly this:

Ref: http://ruby-doc.org/core-2.0.0/doc/syntax/methods_rdoc.html

Related methods: