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

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

**メリット**

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

**デメリット**

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

## 実際に使ってみる

```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}のビリヤード台で、実行します"
```
　[以前の記事](https://qiita.com/Nedward/items/a39573633c0f1bacc13b)で書いたベタ書きのコードもやろうと思えばできますが、それは二重のループを作ることになりますし、ループさせる分、コードが一回り大きくなります、つまり、コードの階層が深まります。結果として、読みづらく、修正しにくいです。想像するだけで、改修しにくい、読みにくいコードになると思います。

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

## 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を使ってブロック渡しをするかを見極めると良いと思われます。