Good class modelling in Ruby?

So I know there are some Ruby-ites who have read this blog in the past, maybe you could give me some advice? Sorry this entry is so long, man I write too much.

I’m having trouble switching my brain from understanding good class hieararchy design in a statically typed language (like Java), to Ruby’s world of ‘duck typing’ which I’m still not entirely comfortable with.

Problem Domain

I need to work with ‘holdings’ statements in my ILS, or really as expressed by my OPAC, HIP3. There are basically three classes involved:

#1: ‘Copies’: A bad name, but the one HIP uses. For bibs under serial control, there is a ‘copy’ level which basically (over-simplified) aggregates items in a particular run. For instance, a copy might represent v.-v.20 of a particular title in a particular library location, all on the shelf together.

#2: Serial Items: Underneath a copy, there is a serial item, representing a particular object on the shelf (could be a bound volume or a loose volume or even some other media).

#3: Monographic Items: Non-serial-controlled items don’t have the copy level, there are just plain Items.

All of these three classes can be abstracted as what I’ll call a ‘holding’. They all have some attributes and perhaps functions in common. For instance, they all have a call #, a location code, and a collection code. They also of course have some unique things (like the parent-child relationship between Copy and Serial Item).

There isn’t really any super/sub-class relationship between these three classes. You can’t say any one of them is any of the others–you can just say that all three are Hip3Holdings.

They also don’t neccesarily need to share any code. The way they perform their common API might be entirely different.

Statically Typed

In Java, I might define an Interface “Hip3Holding”. Java interfaces define API that implementors must implement, but provide no code (implementation) of their own. So in this case all three classes would implement Hip3Holding, but there wouldn’t be any other class hiearchy between them. This leaves any of them free to have any super-class they might want at another time. Client code can still ask if an object is an “instanceof Hip3Holding” and all three will say ‘yes’. Which means they commit to providing the API defined there, you can treat them all like an abstract Hip3Holding.

Alternately, if they did need to share some code in common, I might make Hip3Holding a class, not just an interface, that all three sub-classed from. Hip3Holding would probably be an abstract class, you can’t instantiate any Hip3Holdings, you need to instantiate the specific concrete sub-classes. Now they can inherit common code, and of course they are all still “instanceof Hip3Holding”.

We could make this more complicated. All three are abstractly Hip3Holdings, but #2 and #3 are also abstractly Hip3ItemHoldings, which might be a bit more specific but still common to both of them.

In Java you could handle still this with either interfaces or abstract classes. Interfaces can extend each other, so we could have another interface Hip3ItemHolding extending Hip3Holding which #2 and #3 implemented. Or we could have an intervening abstract class Hip3ItemHolding which they subclassed. Then we could ask ‘instanceof Hip3ItemHolding’, and have an object tell us that it’s committed to providing that more specific API too.

All of these designs are possible, and they all to me are potentially good designs, depending on the specifics of the situation.

In Ruby?

But in ruby ‘duck typing’, my instincts for good design fail me. We don’t have interfaces, although we do have modules. I don’t think we have abstract classes? (But I could be wrong). My instincts are built around static typing in the first place, which we don’t have in Ruby either. So what can we do in Ruby?

We could do nothing, just provide all three classes with no relation between them at all. They all happen to implement common methods, which a client can just call—but there’s no way to ask “kind_of? Hip3Holding” in general. There’s no real tracking of what API is common to Hip3Holding, which still seems dangerous to me–if we add a method to our abstract idea of Hip3Holding, we’ve got to remember to add it to all these specific classes (which aren’t marked as being Hip3Holdings even), or else we’ll get an error–but not until run-time at an unpredictable point! So this is something we couldn’t even do this way in a statically typed language–but it still doesn’t seem like a good idea to me!

Okay, so we can provide a Hip3Holding Module instead, and include it in all three classes. If they don’t need to share any code in common, it might be an empty module. Now we can at least ask “kind_of? Hip3Holding” , we’ve at least marked which classes are. There’s still no tracking or self-documentation of what API is actually supposed to be common to Hip3Holding. But it’s good that we can’t actually instantiate a ‘Hip3Holding’–since it’s an abstract idea.

If there is some common code, we could put it in the module. But there will definitely be some common API that does not have common code. For instance, each class might implement requestForUser() in an entirely different way, but all three need to provide it. So we could leave this method out of the module entirely, and now it’s just up to the programmer to remember that it’s part of our notion of Hip3Holding and maintain that. Or we could put it in the module but have the implementation just raise an exception? Or be silently no-op?

Or instead of a module, we could provide an actual super class Hip3Holding. Which seems weird to me, because I don’t think there’s any way to make it abstract, but client code should really never instantiate a Hip3Holding directly, it really is an abstract concept. Not sure what the pros and cons of this vs a module are, my instincts fail me.

And when we get to the idea of a notion of an Hip3ItemHolding that’s more specific than Hip3Holding but still abstract—I have no idea. Ruby modules can’t sub-class or extend each other, I don’t think. So we could have a Hip3ItemHolding module that’s entirely unrelated (except in our minds) to Hip3Holding? We could leave it out entirely and just have the whole concept be just in our heads (and in certain API that Serial Item and Mono Item just ‘happen’ to implement in common (duck typing?)). If we go the route of having Hip3Holding be an actual class (which is meant to be abstract, but we can’t enforce that), we could instead still have Hip3ItemHolding be an actual class sub-classing that (also meant to be abstract without us enforcing it).

Help! What’s the way to model this? If anyone reaches the end.

This entry was posted in programming. Bookmark the permalink.

4 Responses to Good class modelling in Ruby?

  1. wilig says:

    I think this is point at which most people shake thier heads at duck typing. In my opinion the correct way to model this in Ruby is to just create your three different classes. The rule of thumb being don’t care what an object is, care about what it can do.

    Coming from Java it’s hard to get used to that idea. This is Ruby, introspection is cake.

    When you go to do work with an object ask the object if it provides the required method(s).

    i.e.

    object.respond_to?(:call_number)

    Now later on, you can hand your code any object that implements the required method no matter what the class.

    Hopefully this leads to code that is even more de-coupled and flexible.

  2. jrochkind says:

    Thanks for the response willig. I admit, I find this deeply disturbing and confusing. :)

    So do you think kind_of? is the enemy of proper ‘duck typing’ ruby style?

    See, with kind_of? one could sum up respond_to? a whole bunch of methods. But what you’re suggesting… before every single method call I need to ask respond_to? That seems like so much trouble. And what do I do if the answer is ‘no’, when I was expecting it to be ‘yes’? If all I’m going to do is raise, I guess I might as well have just called the method without asking respond_to? in the first place. But that seems just so fragile to me. Which of these should I do though, when is it appropriate to call respond_to, and what hte heck should you do with an answer you don’t expect?

    I suppose when setting an attribute to an object, i could call “respond_to” on every method I might ever expect to call on that object, and throw an exception at the time of ‘setting’. But this is only slightly less trouble, and it smells like just trying to make Ruby more like Java, I’m sure it’s not what I’m supposed to do.

    Maybe I just need to look at some good ruby code to see what it does. Any reccommendations for such, or any more advice on this topic, greatly appreciated. I still find it greatly offensive to my idea of what OO _is_. I can give up static typing for dynamic typing, but OO with no typing at all? [Does ANY other OO language do this? It just seems crazy to me. When I finally ‘got’ good OO design, my conception was largely about the concept of ‘type’ (In the sense of Gamma’s _Design Patterns_, where ‘type’ is about interface and ‘class’ is about implementation.]

    2: respond_to can just check a name, not number of arguments (arrity), right? Or am I missing something?

    3: What if my classes share some attributes in common? Is that a reason to have a common superclass or method? Or no, just define attributes with the same name in all three classes? And: I guess if they end up needing to share just a bit of code, that’s a good reason for a module?

  3. Hey! You could try to use interface.rb (http://raa.ruby-lang.org/project/interface). It works like this:

    require “interface”

    MyInterface = interface{
    required_methods :foo, :bar
    }

    class MyClass
    def foo
    end
    include MyInterface
    end

    # -> Interface::MethodMissing: bar

    This will provide some sort of formal definition that your classes are related and can be used polymorphical, but it is of course far from something like guaranteed type safety.

    I think duck typing-fans would never use something like this anyway. In your example I would just use the methods in a polymorphical way – you know your 3 classes and it is very unlikely that some unknown 4th class will sneak in…

  4. jrochkind says:

    Thanks Gerald, that’s interesting.

    I guess the way I’m used to thinking about polymorphism, it _is_ always likely that a unknown (at the time you initially wrote the class hiearchy) 4th class will sneak in. The way I learned it, that’s kind of the _point_ of polymorphism and OO–to plan for that as-yet-to-be-born 4th class.

    For instance, many applications or frameworks allow local customization or extension by writing that “4th class”—which the original designers didn’t need to know about, and which should keep working even as the original designers make tweaks to their first 3 classes (and the code that can call any of them, including your new unknown-to-them 4th class)–so long as they keep their ‘contract’.

    But apparently Ruby doesn’t see it this way.

    This makes me curious about Rails. For instance, db adaptor classes in ActiveRecord. How are they written without interfaces? Someone writing a new db adaptor needs some way to know exactly what methods must be there (and what they must do), and some confidence that his adaptor class (the “4th class”) will keep working as the central maintainers change the code that will call it. How does that end up working? Just by narrative expressed in comments?

    Unit tests are part of it. Unit tests are in some sense a formal and testable description of what a certain ‘type’ is supposed to do—you don’t have an interface to write to, but you have unit tests to write your new adaptor to, it better pass them all!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s