LoginSignup
0
0

More than 3 years have passed since last update.

オブジェクト指向の継承についてとyieldについて Ruby

Posted at

継承について

 オブジェクト指向に存在する継承という能力があるそうです。クラス定義した中身を書かずして利用したり、中で定義されているものをそのクラスで使いやすいように改造して使えるようになったりします。

メリット

  • 機能拡張、いわゆる、プラガブルな仕組みを作ることができる能力

デメリット

  • 継承させすぎによる保守不全(大元を変更した瞬間、継承先で不具合が多発するなど・・・)

実際に使ってみる

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

    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

            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 "引数を指定してください"
    exit 1
end

x_max =  x_max.to_i
y_max =  y_max.to_i

(6..16).each do |times|
    puts "3 x #{times}のビリヤード台で、実行します"
    bt = MyBilliardTable.new(length_x: 3, length_y: times, ball: Ball.new)
    bt.cue
end

また引き続き、以前でも書いたコードを使って継承をさせてみます。実際このコードの中で継承させている部分は、

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

 ここの部分です。不等号を使って親クラスを指定することで、クラス継承をすることができます。今回は出力する部分のメソッドを変更しています。
 そして、この定義したクラスを使ってオブジェクトを生成してみます。

shori.rb
(6..16).each do |times|
    puts "3 x #{times}のビリヤード台で、実行します"
    bt = MyBilliardTable.new(length_x: 3, length_y: times, ball: Ball.new)
    bt.cue
end

 処理部分は上記の部分になります。出力が変更された形になっているはずですし、また、MyBilliardTableクラスでは定義していませんが、BilliardTabelクラスで定義しているメソッドはちゃんと機能していることが理解できると思います。

余談

 余談ですが、オブジェクト指向でクラス設計することによって、BilliardTableをオブジェクトにできることによって、盤面にパターンを持たせることができるようになりました。今回は3かけxの盤面を表現しています。

banmen.rb
    puts "3 x #{times}のビリヤード台で、実行します"

 以前の記事で書いたベタ書きのコードもやろうと思えばできますが、それは二重のループを作ることになりますし、ループさせる分、コードが一回り大きくなります、つまり、コードの階層が深まります。結果として、読みづらく、修正しにくいです。想像するだけで、改修しにくい、読みにくいコードになると思います。

こういうような条件が増えた時の表現のしやすさ、書きやすさもやはりオブジェクト指向の利点と言えると思います。

yieldについて

 規模の小さいものであれば、ブロック単位でことが済ませられるyieldを使って機能拡張をすることも可能です。
yieldは「取って変わる」の意味の通り、statusメソッドの部分を取って代わるようにしています。

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

    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

            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 "blockがあります"
        else
            puts "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 "引数を指定してください"
    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 "ステップ数は#{status["step"]}でx座標は#{status["x"]}、y座標は#{status["y"]}です"
end

拡張したい機能の規模の大小で、継承を用いるか、yieldを使ってブロック渡しをするかを見極めると良いと思われます。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0