Null Object Pattern

#1

Hello!

Playing with Null Object Pattern, I discovered that it can be handy to use method_missing macro:

class Dog
  def walk(direction)
    "step #{direction}"
  end
end

dog = Dog.new
dog.walk "forward" # => "step forward"

class NilDog
  macro method_missing(call)
  end
end

dog = NilDog.new
dog.bark 1, 2, 3 { :foo } # => nil

But if Dog has to inherit from some abstract Animal, it doesn’t work anymore:

abstract class Animal
  abstract def walk(direction)
end

class Dog < Animal
  def walk(direction)
    "step #{direction}"
  end
end

class NilDog < Animal
  macro method_missing(call)
  end
end

Error: abstract `def Animal#walk(direction)` must be implemented by NilDog

Apparently, abstract def has a precedence over method_missing here, so the question is, is it possible to trick abstract def into believing that the corresponding method was implemented by NilDog?

0 Likes

#2

Probably method_missing should count as everything being implemented.

That said, I think method_missing was a mistake and I’d like to remove it from the language.

0 Likes

#3

Ok, let’s put method_missing away and try something else.
It seems, one has to mirror all the methods (and preserve arity) of Dog, into NillDog, or is it possible to make NilDog responsive to any method call without that tedious mirroring?

abstract class Animal
  abstract def walk(direction)
end

class Dog < Animal
  def walk(direction)
    "step #{direction}"
  end

  def some_other_method(..., ...)
    ...
  end

  def and_other_method(..., ...)
    ...
  end
end

class NilDog < Animal
  def walk(direction) # need to mirror all the public methods of `Dog`,
  end                 # to keep the API consistent
                      # ↓ ↓ ↓ ↓ ↓ ↓ ↓
  def some_other_method(..., ...)
    ...
  end

  def and_other_method(..., ...)
    ...
  end
end

At least, is it possible to accept and throw away all the arguments passed to a method? Something like Ruby does with:

def foo(*)
end
0 Likes

#4

I think best you will get is def some_other_method(*args, **kwargs) to catch all arguments. But the methods involve blocks/yield you will need two defs for each method.

Unless all the params of methods have type restrictions is impossible to have 100% the same API.
If there they are restricted probably con macros you are able to generate the NilDog on the fly.
But I would go with *args, **kwargs first.

0 Likes

#5

I see, thank you for help!

0 Likes

#6

Throw away method_missing from the language? Is it a real plan? In other (dynamic languages), it’s possible to do pretty cool things with it (used in ORMs, XML/JSON parsers, proxying is easy with it)…

0 Likes