简体   繁体   中英

Reverse (?) Class inheritance in Ruby

just messing around with Ruby and was trying the following. I know i could do it with a case statement but what would be the proper way to achieve it?

What i want it to be able to type

Animals.new.interact

and get a proper reply depending on the Animal.

At the moment i get back a reply

You are a eagle
Not defined !
(repl):9:in `fly'
(repl):14:in `interact'
(repl):1:in `<main>'

but i would expect something along those lines (if the sample is a Penguin for example)

You are a penguin
you cannot fly !

Any ideas appreciated.

class Animals
  SPECIES = []
  attr_accessor :name
  def initialize 
    @name = SPECIES.sample
  end

def fly
raise "Not defined !"
end

def interact
 puts "You are #{self}"
 fly
end

  def to_s
    "a #{@name}"
  end

end

class Penguin < Animals

  def initialize
    @name = "penguin"
    SPECIES << @name
  end

  def fly
    puts "you cannot fly !"
  end

end

class Eagle < Animals

  def initialize
    @name = "eagle"
        SPECIES << @name

  end

  def fly
    puts "you fly high up the mountains !"
  end
end
Penguin.new
Eagle.new

While you've got roughly the right idea with subclasses and overriding methods, the mistake here is that your design requires you to instantiate Animals instances before they show up in the parent class, a very unusual way of defining these things. You're also modifying what should be a constant, which is bad form, and your initialize method merely populates the name, it doesn't select the appropriate type.

In Ruby when your initialize method is called your object is locked in with a particular type that cannot be changed. If you want to emit animals of various types then you need to store those in an array already pre-constructed.

You can always request notification when your base class is used for inheritance by defining an inherited method:

class Animals
  def self.inherited(subclass)
    (@types ||= [ ]) << subclass
  end
end

This allows you to select one at random using a factory method :

class Animals
  def self.random
    @types and @types.sample.new
  end
end

This simplifies the implementation of the other instances and does not require you to do anything special to get them to link up properly:

class Penguin < Animals
  def initialize
    @name = "penguin"
  end

  def fly
    puts "you cannot fly !"
  end
end

class Eagle < Animals
  def initialize
    @name = "eagle"
  end

  def fly
    puts "you fly high up the mountains !"
  end
end

So when you call the factory method you should get a random animal instance:

Animals.random
# => #<Eagle:0x007fc8f4109090 @name="eagle">

You definitely haven't understood classes. Why do you expect to change the behavior of an object by changing one instance variable?

When you do Animals.new you request a new animal. And an Animal behaves exactly like you defined in the Animal class, in which you defined the fly method as:

def fly
raise "Not defined !"
end

So there's absolutely no reason to expect you cannot fly ! .


You could do something like this, however:

SPECIES = []
class Animals
  attr_accessor :name
  def initialize 
    @name = SPECIES.sample
  end

  def fly
  raise "Not defined !"
  end

  def interact
   puts "You are #{self}"
   fly
  end

  def to_s
    "a #{@name}"
  end

end

class Penguin < Animals
  SPECIES << self

  def initialize
    @name = "penguin"
  end

  def fly
    puts "you cannot fly !"
  end
end

class Eagle < Animals
  SPECIES << self

  def initialize
    @name = "eagle"
  end

  def fly
    puts "you fly high up the mountains !"
  end
end

SPECIES.sample.new.interact

Now the SPECIES variable that I have made global stores the different Animal subclasses. When you call SPECIES.sample you get a class like Penguin or Eagle. You instantiate that class, ie you get an instance of Penguin that behaves exactly like a penguin and will correctly no whether it can fly.

Firstly, on the topic of why it doesn't work.

Class#new always returns the newly allocated instance, no matter what is your last statement, return statements etc. Hence you will get a non-specific Animal with Animal.new .


Secondly, on how to "properly" do it.

A class that knows about all of his children is an OO wtf moment. Instead create a separate service for picking an animal:

module AnimalPicker
  ANIMALS = [Penguin, Eagle]

  def self.random
    ANIMALS.sample.new
  end
end

AnimalPicker.random

Thirdly, on "but I really want to do it this way!!!!1111!" .

You can redefine Animal#new :

class Animal
  def self.new
    [Penguin, Eagle].sample.new
  end
end

Animal.new

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM