[Ruby] About object-oriented inheritance and yield Ruby

4 minute read

About inheritance

It seems that there is an object-oriented ability to inherit. You can use it without writing the contents of the class definition, or you can modify the contents defined in the class to make it easier to use in that class.

merit

  • Functional expansion, the ability to create so-called pluggable mechanism

Demerit

  • Maintenance failure due to too much inheritance (the moment the source is changed, many defects occur at the destination…)

Try it out

keishou.rb


class Ball
attr_accessor :x, :y, :x_way, :y_way, :step

def initialize(x: 1, y: 1, x_way: 1, y_way: 1, step: 1)
@x = x
@y = y
@x_way = x_way
@y_way = y_way
@step = step
end
It's a sequel.
def move
@x += @x_way
@y += @y_way
@step += 1
end

def reflect_x
if @x_way == 1
@x_way = -1
elsif @x_way == -1
@x_way = 1
end
end

def reflect_y
if @y_way == 1
@y_way = -1
elsif @y_way == -1
@y_way = 1
end
end

def goal?(x_max, y_max)
@x == x_max && y == 1 \
|| @x == 1 && @y == y_max \
|| @x == 1 && @y == 1 \
|| @x == x_max && @y == y_max
end

def boundary_x?(x_max)
@x == x_max || @x == 1
end

def boundary_y?(y_max)
@y == y_max || @y == 1
end
end

class BilliardTable
attr_accessor :length_x, :length_y, :ball

def initialize(length_x: nil, length_y: nil, ball: nil)
@length_x = length_x
@length_y = length_y
@ball = ball
end

def cue
print_status

loop do
@ball.move

print_status
It's a sequel.
if @ball.goal?(@length_x, @length_y)
puts "GOAL!!"
break
elsif @ball.boundary_x?(@length_x)
@ball.reflect_x
elsif @ball.boundary_y?(@length_y)
@ball.reflect_y
end
end
end

def print_status
puts "#{@ball.step}, (#{@ball.x}, #{@ball.y})"
end
end

class MyBilliardTable <BilliardTable
def print_status
puts "step = #{@ball.step}, x = #{@ball.x}, y = #{@ball.y}"
end
end

x_max = ARGV[0]
y_max = ARGV[1]

if !x_max || !y_max
puts "Please specify arguments"
exit 1
end

x_max = x_max.to_i
y_max = y_max.to_i

(6..16).each do |times|
puts "Run on the 3 x #{times} pool table"
bt = MyBilliardTable.new(length_x: 3, length_y: times, ball: Ball.new)
bt.cue
end

In addition, I will continue to use the code I wrote before to inherit. In fact, the part that is inherited in this code is

keishoububun.rb


class MyBilliardTable <BilliardTable
    def print_status
        puts "step = #{@ball.step}, x = #{@ball.x}, y = #{@ball.y}"
    end
end

Here is the part. You can inherit classes by specifying the parent class using an inequality sign. This time, the method of the output part is changed. Then, try to create an object using this defined class.

shori.rb


(6..16).each do |times|
puts "Run on the 3 x #{times} pool table"
bt = MyBilliardTable.new(length_x: 3, length_y: times, ball: Ball.new)
bt.cue
end

The processing part is the above part. The output should have been modified, and I haven’t defined it in the MyBilliardTable class, but you can see that the methods defined in the BilliardTabel class are working fine.

Digression

As an aside, by designing a class in an object-oriented manner, by making BilliardTable an object, it became possible to have a pattern on the board. This time, the board surface of 3 times x is expressed.

banmen.rb


    puts "Run on the 3 x #{times} pool table"

I could do the solid code I wrote in previous article, but that would create a double loop, and a loop The more you do, the bigger the code is, that is, the deeper the hierarchy of code. As a result, it is hard to read and hard to fix. Just imagine it and it will be hard to modify and hard to read.

I think that the ease of expression and the ease of writing when such conditions increase can also be said to be an advantage of object orientation.

About yield

If the scale is small, it is possible to extend the function by using the yield that can be done in block units. Yield replaces the status method part as the meaning of “replace”.

yield.rb


class Ball
attr_accessor :x, :y, :x_way, :y_way, :step

def initialize(x: 1, y: 1, x_way: 1, y_way: 1, step: 1)
@x = x
@y = y
@x_way = x_way
@y_way = y_way
@step = step
end
It's a sequel.
def move
@x += @x_way
@y += @y_way
@step += 1
end

def reflect_x
if @x_way == 1
@x_way = -1
elsif @x_way == -1
@x_way = 1
end
end

def reflect_y
if @y_way == 1
@y_way = -1
elsif @y_way == -1
@y_way = 1
end
end

def goal?(x_max, y_max)
@x == x_max && y == 1 \
|| @x == 1 && @y == y_max \
|| @x == 1 && @y == 1 \
|| @x == x_max && @y == y_max
end

def boundary_x?(x_max)
@x == x_max || @x == 1
end

def boundary_y?(y_max)
@y == y_max || @y == 1
end
end

class BilliardTable
attr_accessor :length_x, :length_y, :ball

def initialize(length_x: nil, length_y: nil, ball: nil)
@length_x = length_x
@length_y = length_y
@ball = ball
end

def cue
print_status

loop do
@ball.move

print_status
It's a sequel.
if @ball.goal?(@length_x, @length_y)
puts "GOAL!!"
break
elsif @ball.boundary_x?(@length_x)
@ball.reflect_x
elsif @ball.boundary_y?(@length_y)
@ball.reflect_y
end
end
end

def cue_2
if block_given?
puts "there is a block"
else
puts "Please specify block"
return
end

loop do
@ball.move

yield status

if @ball.goal?(@length_x, @length_y)
puts "GOAL!!"
break
elsif @ball.boundary_x?(@length_x)
@ball.reflect_x
elsif @ball.boundary_y?(@length_y)
@ball.reflect_y
end
end
end

def status
{
"step" => @ball.step,
"x" => @ball.x,
"y" => @ball.y
}
end
end

x_max = ARGV[0]
y_max = ARGV[1]

if !x_max || !y_max
puts "Please specify arguments"
exit 1
end

x_max = x_max.to_i
y_max = y_max.to_i

ball = Ball.new()

bt = BilliardTable.new(length_x: x_max, length_y: y_max, ball: ball)

# bt.cue

bt.cue_2 do |status|
puts "The number of steps is #{status["step"]}, the x coordinate is #{status["x"]}, and the y coordinate is #{status["y"]}"
end

#### **`Depending on the size of the function you want to extend, it may be useful to determine whether to use inheritance or block passing using yield. `**