Ruby, polymorphism, inheritance and self.class - ruby

I'm quite new to ruby. I'm used to Java and C++ though. What I was trying to understand is how to use polymorphism of the language. And also rules of inheritance. I've made a simple working code but I can't understand how good is it. Are there any other ways? There are always other ways in ruby as far as I know.
What it does: compares children like children if they belong to the same class. Otherwise compares them like their parent.
Sorry if I've wasted your time. Googling didn't help, I can't make a good query.
class Hero
public
#redefine comparison like this just for the sake of having it use protected method
def <=> hero
#mass <=> hero.get_mass
end
def initialize mass
#mass = mass
end
protected
#this protected method is used for inheritance testing purposes
def get_mass
#mass
end
end
class Finn < Hero
def initialize mass, sword_length
#mass = mass
#sword_length = sword_length
end
# THIS! Am I doing this right?
def <=> finn
if finn.class == self.class
(#mass+#sword_length) <=> (finn.get_mass+finn.get_sword_length)
else
super(finn)
end
end
# probably not...
protected
def get_sword_length
#sword_length
end
end
class Jake < Hero
def initialize mass, fist_length
#mass = mass
#fist_length = fist_length
end
def <=> jake
if jake.class == self.class
(#mass+#fist_length) <=> (jake.get_mass+jake.get_fist_length)
else
super(jake)
end
end
protected
def get_fist_length
#fist_length
end
end
hero1 = Finn.new(10, 80)
hero4 = Jake.new(50, 18)
puts " error? "+(hero1<=>hero4).to_s

A couple of notes:
Typically you would use an accessor of the same name as your instance variable rather than your get style naming.
Typically you would not check the class, but rather what the object can do.
class Hero
def initialize(mass)
#mass = mass
end
def <=>(hero)
mass <=> hero.mass
end
protected #I put this as protected since you did, but I would likely leave it public
attr_reader :mass
end
class Finn < Hero
def initialize(mass, sword_length)
#mass = mass
#sword_length = sword_length
end
def <=>(finn)
if finn.respond_to? :sword_length
(mass + sword_length) <=> (finn.mass + finn.sword_length)
else
super(finn)
end
end
protected
attr_reader :sword_length
end
class Jake < Hero
def initialize(mass, fist_length)
#mass = mass
#fist_length = fist_length
end
#edited
def <=>(jake)
if jake.respond_to? :fist_length
(mass + fist_length) <=> (jake.mass + jake.fist_length)
else
super(jake)
end
end
protected
attr_reader :fist_length
end

I have tried to refactor your code, Here is how it looks:-
class Hero
attr_reader :mass
# more info in attr_reader and attr_accessor is here http://stackoverflow.com/questions/5046831/why-use-rubys-attr-accessor-attr-reader-and-attr-writer
def initialize mass
#mass = mass
end
def <=> hero
mass <=> hero.mass
end
end
class Finn < Hero
attr_reader :sword_length
def initialize mass, sword_length
#sword_length = sword_length
super(mass)
end
def <=> finn
if finn.class == self.class
(#mass+#sword_length) <=> (finn.mass+finn.sword_length)
else
super(finn)
end
end
end
class Jake < Hero
attr_reader :fist_length
def initialize mass, fist_length
#fist_length = fist_length
super(mass)
end
def <=> jake
#alternate dense way to do it
jake.class == self.class ? ((#mass+#fist_length) <=> (jake.mass+jake.fist_length)) : super(jake)
end
end
hero1 = Finn.new(10, 80)
hero4 = Jake.new(50, 18)
puts " error? "+(hero1<=>hero4).to_s

Related

interaction of 2 classes

class Airport
attr_reader :plane
CAPACITY = 20
def initialize
#plane = []
#capacity = CAPACITY
end
#can land a plane
def land(plane)
fail 'plane is full' if #plane.count >= #capacity
#plane << plane
end
def take_off
fail 'weather is stormy' if #weather
#plane
end
def weather
#weather = weather
end
end
class Weather
def stormy?
[true, false].sample
end
end
I am new to Ruby. I am a little confused in how to use the method of the Weather class in my Airport class.
I want the take_off method in my Airport class to fail when the weather is reported to be stormy. I am unsure where to begin in understanding object interactions. Any help would be greatly appreciated, thank you
If you want to fail if weather is stormy, you have to call its stormy? method:
def take_off
fail 'weather is stormy' if #weather.stormy?
# do something
end
But in order to get the above working, weather needs to be an instance of Weather. I would simply pass it to the constructor. In addition, I would make it a settable attribute, so you can easily update it:
class Airport
attr_accessor :weather
attr_reader :plane
CAPACITY = 20
def initialize(weather)
#weather = weather
#plane = []
#capacity = CAPACITY
end
# ...
end
I would also make the weather a little more deterministic, e.g. by having a wind force attribute:
class Weather
attr_reader :wind_force
def initialize(wind_force)
#wind_force = wind_force
end
def stormy?
wind_force >= 10
end
end
Usage:
weather = Weather.new(3)
airport = Airport.new(weather)
airport.take_off # no exception
airport.weather = Weather.new(12)
airport.take_off # RuntimeError: weather is stormy

Possible moves on a chess board, using instance from one class in another.

I'm working on making a chess game. I want to keep everything that has to do with possible moves inside each type of pieces class. In order to do this I need to refer back to #board[][]. The commented section of code in the possible_moves method is what I would like to do, except it does not work and throws an error.
class Board
attr_accessor :board, :choice
def initialize
#board = Array.new(8){Array.new(8," ")}
#choice = choice
end
def set_board
#board[0][2] = Bishop.new([0,2],false)
end
end
class Bishop
attr_accessor :x_position, :y_position, :piece, :color, :moves
def initialize(position,is_white)
#x_position = position[0]
#y_position = position[1]
#piece = is_white ? "♝" : "♗"
#color = is_white ? "white" : "black"
#moves = [[+1,-1],
[+1,+1],
[-1,+1],
[-1,-1]]
end
def possible_moves
move_list = Array.new
#moves.each do |moves|
x = #x_position
y = #y_position
loop do
x += moves[0]
y += moves[1]
break if x.between?(0,7) == false
break if y.between?(0,7) == false
# This is where I want to refer back to #board from the Board class.
# if #board[x][y].color.....
move_list << [x,y]
#end
end
end
p move_list
end
end
You can pass the board into the Bishop constructor:
class Bishop
attr_reader :board
# etc.
def initialize(position,is_white,board)
#board = board.board # or just #board = board and you can fetch the matrix later
# etc.
end
end
class Board
# etc.
def set_board
#board[0][2] = Bishop.new([0,2],false, self)
end
end

Possible help in code refactoring

Sandi Metz says in SOLID OOPS concepts from GORUCO that presence of if..else blocks in Ruby can be considered to be a deviation from Open-Close Principle. What all methods can be used to avoid not-urgent if..else conditions? I tried the following code:
class Fun
def park(s=String.new)
puts s
end
def park(i=Fixnum.new)
i=i+2
end
end
and found out that function overloading does not work in Ruby. What are other methods through which the code can be made to obey OCP?
I could have simply gone for:
class Fun
def park(i)
i=i+2 if i.class==1.class
puts i if i.class=="asd".class
end
end
but this is in violation to OCP.
With your current example, and wanting to avoid type detection, I would use Ruby's capability to re-open classes to add functionality you need to Integer and String:
class Integer
def park
puts self + 2
end
end
class String
def park
puts self
end
end
This would work more cleanly when altering your own classes. But maybe it doesn't fit your conceptual model (it depends what Fun represents, and why it can take those two different classes in a single method).
An equivalent but keeping your Fun class might be:
class Fun
def park_fixnum i
puts i + 2
end
def park_string s
puts s
end
def park param
send("park_#{param.class.to_s.downcase}", param)
end
end
As an opinion, I am not sure you will gain much writing Ruby in this way. The principles you are learning may be good ones (I don't know), but applying them forcefully "against the grain" of the language may create less readable code, regardless of whether it meets a well-intentioned design.
So what I would probably do in practice is this:
class Fun
def park param
case param
when Integer
puts param + 2
when String
puts param
end
end
end
This does not meet your principles, but is idiomatic Ruby and slightly easier to read and maintain than an if block (where the conditions could be far more complex so take longer for a human to parse).
You could just create handled classes for Fun like so
class Fun
def park(obj)
#parker ||= Object.const_get("#{obj.class}Park").new(obj)
#parker.park
rescue NameError => e
raise ArgumentError, "expected String or Fixnum but recieved #{obj.class.name}"
end
end
class Park
def initialize(p)
#park = p
end
def park
#park
end
end
class FixnumPark < Park
def park
#park += 2
end
end
class StringPark < Park
end
Then things like this will work
f = Fun.new
f.park("string")
#=> "string"
f.instance_variable_get("#parker")
#=> #<StringPark:0x1e04b48 #park="string">
f = Fun.new
f.park(2)
#=> 4
f.instance_variable_get("#parker")
#=> #<FixnumPark:0x1e04b48 #park=4>
f.park(22)
#=> 6 because the instance is already loaded and 4 + 2 = 6
Fun.new.park(12.3)
#=> ArgumentError: expected String or Fixnum but received Float
You could do something like this:
class Parent
attr_reader :s
def initialize(s='')
#s = s
end
def park
puts s
end
end
class Child1 < Parent
attr_reader :x
def initialize(s, x)
super(s)
#x = x
end
def park
puts x
end
end
class Child2 < Parent
attr_reader :y
def initialize(s, y)
super(s)
#y = y
end
def park
puts y
end
end
objects = [
Parent.new('hello'),
Child1.new('goodbye', 1),
Child2.new('adios', 2),
]
objects.each do |obj|
obj.park
end
--output:--
hello
1
2
Or, maybe I overlooked one of your twists:
class Parent
attr_reader :x
def initialize(s='')
#x = s
end
def park
puts x
end
end
class Child1 < Parent
def initialize(x)
super
end
def park
x + 2
end
end
class Child2 < Parent
def initialize(x)
super
end
def park
x * 2
end
end
objects = [
Parent.new('hello'),
Child1.new(2),
Child2.new(100),
]
results = objects.map do |obj|
obj.park
end
p results
--output:--
hello
[nil, 4, 200]
And another example using blocks, which are like anonymous functions. You can pass in the desired behavior to park() as a function:
class Function
attr_reader :block
def initialize(&park)
#block = park
end
def park
raise "Not implemented"
end
end
class StringFunction < Function
def initialize(&park)
super
end
def park
block.call
end
end
class AdditionFunction < Function
def initialize(&park)
super
end
def park
block.call 1
end
end
class DogFunction < Function
class Dog
def bark
puts 'woof, woof'
end
end
def initialize(&park)
super
end
def park
block.call Dog.new
end
end
objects = [
StringFunction.new {puts 'hello'},
AdditionFunction.new {|i| i+2},
DogFunction.new {|dog| dog.bark},
]
results = objects.map do |obj|
obj.park
end
p results
--output:--
hello
woof, woof
[nil, 3, nil]
Look at the is_a? method
def park(i)
i.is_a?(Fixnum) ? (i + 2) : i
end
But even better not to check a type, but use duck typing:
def park(i)
i.respond_to?(:+) ? (i + 2) : i
end
UPD: After reading comments. Yes, both examples above don't solve the OCP problem. That is how I would do it:
class Fun
# The method doesn't know how to pluck data. But it knows a guy
# who knows the trick
def pluck(i)
return __pluck_string__(i) if i.is_a? String
__pluck_fixnum__(i) if i.is_a? Fixnum
end
private
# Every method is responsible for plucking data in some special way
# Only one cause of possible changes for each of them
def __pluck_string__(i)
puts i
end
def __pluck_fixnum__(i)
i + 2
end
end
I understand or equal to operation in ruby but can you explain what
you have done with:
Object.const_get("#{obj.class}Park").new(obj)
In ruby, something that starts with a capital letter is a constant. Here is a simpler example of how const_get() works:
class Dog
def bark
puts 'woof'
end
end
dog_class = Object.const_get("Dog")
dog_class.new.bark
--output:--
woof
Of course, you can also pass arguments to dog_class.new:
class Dog
attr_reader :name
def initialize(name)
#name = name
end
def bark
puts "#{name} says woof!"
end
end
dog_class = Object.const_get("Dog")
dog_class.new('Ralph').bark
--output:--
Ralph says woof!
And the following line is just a variation of the above:
Object.const_get("#{obj.class}Park").new(obj)
If obj = 'hello', the first portion:
Object.const_get("#{obj.class}Park")
is equivalent to:
Object.const_get("#{String}Park")
And when the String class object is interpolated into a string, it is simply converted to the string "String", giving you:
Object.const_get("StringPark")
And that line retrieves the StringPark class, giving you:
Object.const_get("StringPark")
|
V
StringPark
Then, adding the second portion of the original line gives you:
StringPark.new(obj)
And because obj = 'hello', that is equivalent to:
StringPark.new('hello')
Capice?

Ruby Check Class Owner From Other Inheritance With Default Library

I wondering of how to check the owner of certain method/class from other class.
For example:
class Value
attr_accessor :money
def initialize
#money = 0.0
end
def get_money
return self.money
end
def transfer_money(target, amount)
self.money -= amount
target.money += amount
end
end
class Nation
attr_accessor :value
def initialize
#value = Value.new
end
end
class Nation_A < Nation
def initialize
super
end
def pay_tribute_to_decendant_country
value.transfer_money(Nation_B.value, 500)
end
end
class Nation_B < Nation
def initialize
super
end
def pay_tribute_to_decendant_country
value.transfer_money(Nation_C.value, 200)
end
end
class Nation_C < Nation
def initialize
super
end
def pay_tribute_to_decendant_country
value.transfer_money(Nation_A.value, 300)
end
end
Yea, makes no sense how the decendant goes in a circle, but I'd like to implement the idea that different subclass has different argument.
The list is pretty long (I've installed at least 40 of these already with much more complex desendant branches and much more methods that call transfer_money from Value class). Then I have some idea to implement to the structure. I'd like to add currency, but to override all transfer_money method call would be a tremendous task for me to apply. Therefore I create a hash table that generate the call for me.
class Nation
def self.get_descendants
ObjectSpace.each_object(Class).select { |klass| klass < self }
end
end
module Additional_Value
currency_table = {}
min = 50
max = 100
def self.range (min, max)
rand * (max-min) + min
end
Nation.get_descendants.each do |derived_classes|
currency_table[derived_classes] == self.range min, max
end
end
class Value
attr_accessor :currency
def initialize
#money = 0
#currency = Additional_Value::currency_table
end
def transfer_money(target, amount)
self.money -= amount
amount = amount * #currency[self.class.owner] / #currency[target.class.owner]
target.money += amount
end
end
and I need to figure out how to define owner class. I tried using the caller, but it returns me string / array of string instead of object, method or calle work only for the same method, the 'sender' gem gives me an idea, but it's written in C, and I need to use the default library due to my circumstances.
Greatly appreciated.
Edit:
I'll rewrite the problem in a shorter way:
class Slave
def who_is_the_owner_of_me
return self.class.owner unless self.class.owner.nil?
end
end
class Test
attr_accessor :testing
def initialize
#testing = Slave.new
end
end
class Test2 < Test1
end
a = Test.new
b = Test2.new
c = Slave.new
a.testing.who_is_the_owner_of_me #=> Test
b.testing.who_is_the_owner_of_me #=> Test2
c.who_is_the_owner_of_me #=> main

How do I pass variables between Ruby classes?

I'm creating a card game with multiple classes. Currently, I'm using global variables to hold the $shuffled_deck, $players_hand, and $dealers_hand variables, but I worry when using global variables (perhaps, needlessly) and would prefer to use instance variables.
I've been reading around, but nothing is really clicking. Can anyone help point me in the right direction with this?
Using instance variables I haven't been able to save the #players_hand and #dealers_hand to be able to use them in other classes. For instance, I have #players_hand from the Player class. I have the Dealer class draw a card, but I can't pull that #players_hand into the Dealer class to add the two together.
My current code is:
class Blackjack
def initialize
#player = Player.new
#dealer = Dealer.new
end
end
class Dealer
def initialize
#deck = Deck.new
$dealers_hand = 0
end
def hit_dealer
#deck.hit_dealer
end
def hit_player
#deck.hit_player
end
def draw_card
#hit = $shuffled_deck
end
def shuffle
#deck.suits
end
end
class Player
def initialize
$players_hand = 0
end
end
class Deck
def suits
#code that shuffled the deck..
$shuffled_deck = #shuffled_deck
end
def hit_player
#hit = $shuffled_deck.pop
end
def hit_dealer
#hit = $shuffled_deck.pop
end
end
using your example you can do it like this
class Blackjack
attr_reader :player, :dealer
def initialize
#player = Player.new
#dealer = Dealer.new
end
end
class Dealer
def dealers_hand #the long java way of a getter
#dealers_hand
end
#and now the short ruby way
attr_reader :dealers_hand #if you only need to read the attribute
attr_writer :dealers_hand #if you only need to write the attribute
attr_accessor: dealers_hand #if you need both
def initialize
#deck = Deck.new
#dealers_hand = 5
end
def hit_dealer
#deck.hit_dealer
end
def hit_player
#deck.hit_player
end
def draw_card
#hit = $shuffled_deck
end
def shuffle
#deck.suits
end
end
class Player
attr_reader :players_hand
def initialize
#players_hand = 0
end
end
class Deck
def suits
attr_reader :shuffled_deck
#shuffled_deck = #shuffled_deck
end
def hit_player
#hit = $shuffled_deck.pop
end
def hit_dealer
#hit = $shuffled_deck.pop
end
end
game = Blackjack.new
p game.dealer.dealers_hand
game.dealer.dealers_hand = 4
p game.dealer.dealers_hand
You want to use attr_reader, attr_writer, or attr_accessor. Here's how they work:
attr_reader :players_hand: Allows you to write some_player.players_hand to get the value of that player's players_hand instance variable
attr_writer :players_hand: Allows you to write some_player.players_hand = 0 to set the variable to 0
attr_accessor :players_hand: Allows you to both read and write, as though you'd used both attr_reader and attr_writer.
Incidentally, all these do is write methods for you. If you wanted, you could do it manually like this:
class Player
def initialize
#players_hand = 0
end
def players_hand
#players_hand
end
def players_hand=(new_value)
#players_hand = new_value
end
end

Resources