Ruby - initialize inheritance, super with only certain arguments?

16,179

Solution 1

Yes, you are committing a Cardinal Sin (obviously, you are aware of it, since you are asking about it). :)

You are breaking Liskov substitution principle (and probably some other named or unnamed rules).

You should probably extract another class as a common superclass, which does not contain occupation. That will make everything much clearer and cleaner.

Solution 2

How super handles arguments

Regarding argument handling, the super keyword can behave in three ways:

When called with no arguments, super automatically passes any arguments received by the method from which it's called (at the subclass) to the corresponding method in the superclass.

class A
  def some_method(*args)
    puts "Received arguments: #{args}"
  end
end

class B < A
  def some_method(*args)
    super
  end
end

b = B.new
b.some_method("foo", "bar")     # Output: Received arguments: ["foo", "bar"]

If called with empty parentheses (empty argument list), no arguments are passed to the corresponding method in the superclass, regardless of whether the method from which super was called (on the subclass) has received any arguments.

class A
  def some_method(*args)
    puts "Received arguments: #{args}"
  end
end

class B < A
  def some_method(*args)
    super()  # Notice the empty parentheses here
  end
end

b = B.new
b.some_method("foo", "bar")     # Output: Received arguments: [ ]

When called with an explicit argument list, it sends those arguments to the corresponding method in the superclass, regardless of whether the method from which super was called (on the subclass) has received any arguments.

class A
  def some_method(*args)
    puts "Received arguments: #{args}"
  end
end

class B < A
  def some_method(*args)
    super("baz", "qux")  # Notice that specific arguments were passed here
  end
end

b = B.new
b.some_method("foo", "bar")     # Output: Received arguments: ["baz", "qux"]

Solution 3

Really this is just 2 ways - auto include all attributes (just the word super) and super where you pick which arguments get passed up. Doing super() is picking no arguments to hand to the parent class.

Edit: This was meant as a comment on the above comment but they don't allow comments when someone is new... oh well.

Solution 4

You can avoid setting a default value to your occupation parameter in Person Class by simply passing the nil argument for occupation in super(). This allows you to call Viking.new with your 3 arguments (name, age, weapon) without having to take extra consideration for occupation.

class Person
  def initialize(name, age, occupation)
    @name = name
    @age = age
    @occupation = occupation
  end
end

class Viking < Person
  def initialize(name, age, weapon)
    super(name, age, nil)
    @weapon = weapon
  end
end

eric = Viking.new("Eric", 24, 'broadsword') 
p eric

output Viking:0x00007f8e0a119f78 @name="Eric", @age=24, @occupation=nil, @weapon="broadsword"

Share:
16,179
blob
Author by

blob

Updated on June 05, 2022

Comments

  • blob
    blob almost 2 years

    I've been playing around with Ruby as of late and I can't seem to find the answer to my question.

    I have a class and a subclass. Class has some initialize method, and subclass has its own initialize method that is supposed to inherit some (but not all) variables from it and additionally add its own variables to the subclass objects.

    My Person has @name, @age and @occupation.

    My Viking is supposed to have a @name and @age which it inherits from Person, and additionally a @weapon which Person doesn't have. A Viking obviously doesn't need any @occupation, and shouldn't have one.

    # doesn't work
    class Person
      def initialize(name, age, occupation)
        @name = name
        @age = age
        @occupation = occupation
      end
    end
    
    class Viking < Person
      def initialize(name, age, weapon)
        super(name, age) # this seems to cause error
        @weapon = weapon
      end
    end
    
    eric = Viking.new("Eric", 24, 'broadsword') 
    # ArgError: wrong number of arguments (2 for 3)
    

    You can make it work in the following ways, but neither solution appeals to me

    class Person
      def initialize(name, age, occupation = 'bug hunter')
        @name = name
        @age = age
        @occupation = occupation
      end
    end
    
    class Viking < Person
      def initialize(name, age, weapon)
        super(name, age)
        @weapon = weapon
      end
    end
    
    eric = Viking.new("Eric", 24, 'broadsword') 
    # Eric now has an additional @occupation var from superclass initialize
    
    
    class Person
      def initialize(name, age, occupation)
        @name = name
        @age = age
        @occupation = occupation
      end
    end
    
    class Viking < Person
      def initialize(name, age, occupation, weapon)
        super(name, age, occupation)
        @weapon = weapon
      end
    end
    
    eric = Viking.new("Eric", 24, 'pillager', 'broadsword')
    # eric is now a pillager, but I don't want a Viking to have any @occupation
    

    The question is either

    1. is it by design and I want to commit some Cardinal Sin against OOP principles?

    2. how do I get it to work the way I want to (preferably without any crazy complicated metaprogramming techniques etc)?

  • Franz
    Franz almost 7 years
    LSP does not apply to constructors, as you don't use them in a polymorphic way (i.e. you always know which type you're dealing with). See stackoverflow.com/questions/5490824/….
  • Mladen Jablanović
    Mladen Jablanović almost 7 years
    You are probably right. However, at least in Ruby, you don't always know which type you are dealing with, as classes are objects too. So, the ultimate answer to this question depends on the way constructors are used.
  • Franz
    Franz almost 7 years
    That's true, especially with some dynamic factories. :)