コンテンツにスキップ

プログラミング/Ruby で学ぶオブジェクト指向プログラミング

出典: フリー教科書『ウィキブックス(Wikibooks)』

オブジェクト指向プログラミングは、現代のソフトウェア開発において基盤となる重要な概念です。 このカリキュラムは、Rubyを使用した初学者から中級者までのプログラマーを対象に、クラスやモジュール、継承などの基本的なOOP原則から、高度なトピックス如何に至るまでを包括しています。 実践的な演習やプロジェクトを通じて、理論を実際のコーディングに結びつけ、学習者が柔軟かつ効果的にオブジェクト指向プログラミングのスキルを磨くことを目指します。

プログラミングの基礎とRubyの紹介

[編集]

プログラミングとは何か?

[編集]

プログラミングとは、コンピュータに指示を与えて、あるタスクを自動的に実行させるための手段です。プログラムを書くことで、コンピュータが単純な作業を行ってくれるため、人間がやるべきではない繰り返し作業を効率化することができます。

Rubyとは何か?

[編集]

Rubyは、日本人の松本行弘氏によって開発されたプログラミング言語です。シンプルな構文や豊富な機能、そして美しいコードを書きやすい特性から、世界中で広く利用されています。

Rubyの基本的なデータ型

[編集]

Rubyの基本的なデータ型には以下が含まれます:

  1. Integer(整数): 整数を表現します。例えば、42-10 などが整数です。
  2. Float(浮動小数点数): 小数を表現します。例えば、3.14-0.5 などが浮動小数点数です。
  3. String(文字列): 文字の連続を表現します。シングルクォート ' ' またはダブルクォート " " で囲まれた文字列です。例えば、"Hello, World!" が文字列です。
  4. Symbol(シンボル): 一意の識別子を表現します。先頭にコロン : を付けて表現します。例えば、:foo:bar がシンボルです。
  5. Boolean(真偽値): 真偽値を表現します。truefalse の2つの値があります。
  6. Array(配列): 複数の要素を順序付けて格納します。中括弧 [] で囲まれ、カンマで要素が区切られます。例えば、[1, 2, 3] が配列です。Rubyでは、配列のほかスタックやキューの性質も併せ持っています。
  7. Hash(ハッシュ): キーと値のペアを格納します。中括弧 {} で囲まれ、キーと値は キー => 値 の形式で表現されます。例えば、{ :name => "John", :age => 30 } がハッシュです。

これらの基本的なデータ型を組み合わせて、複雑なデータ構造を構築することができます。 また、Rubyは動的型付け言語であり、変数の型を明示的に宣言する必要がないため、柔軟で使いやすい言語です。

プログラムの実行方法

[編集]

Rubyのプログラムを実行するには、ターミナル上で「ruby ファイル名.rb」というコマンドを実行します。ここで、「ファイル名.rb」には、実行したいプログラムのファイル名を指定します。

Rubyの特色
Rubyは、以下のような特色を持つプログラミング言語です。
オブジェクト指向言語

Rubyは、純粋なオブジェクト指向言語であり、すべての値がオブジェクトであることが特徴です。また、クラスやオブジェクトの定義が容易であり、メソッド呼び出しのシンタックスも簡潔で読みやすいため、オブジェクト指向の設計や開発に向いています。

動的型付け言語

Rubyは、変数の型を明示的に宣言する必要がない動的型付け言語です。そのため、柔軟性があり、実行時に変数の型を決定するため、柔軟なプログラミングが可能です。

リフレクション

Rubyは、リフレクション機能を備えています。つまり、プログラムの実行中に、オブジェクトやクラスの情報を取得できることです。そのため、実行時にプログラムの振る舞いを変更することができ、柔軟性のあるプログラミングが可能になります。

メタプログラミング

Rubyは、メタプログラミングをサポートしています。つまり、プログラムが自己修正、自己再生することができます。例えば、クラスの定義やメソッドの再定義などが可能で、柔軟なプログラミングが可能です。

豊富なライブラリ

Rubyには、豊富な標準ライブラリが含まれています。また、RubyGemsと呼ばれるパッケージ管理システムがあり、多数のライブラリやフレームワークを容易にインストールできるため、効率的な開発が可能です。

シンプルで読みやすい構文
Rubyは、簡潔で読みやすい構文が特徴です。また、自然言語に近い構文を採用しており、プログラムの可読性が高くなっています。特に、Ruby on RailsというWebアプリケーションフレームワークが有名で、Rubyのシンプルで読みやすい構文がフレームワークの利用者に高い評価を得ています。

オブジェクト指向プログラミングとは何か?

[編集]

オブジェクト指向プログラミングとは、プログラムを「オブジェクト」と呼ばれる部品に分割し、それらのオブジェクト同士のやりとりを通じてプログラムを作成する手法です。オブジェクトとは、データとそれに関連する操作をカプセル化したものです。オブジェクト指向プログラミングは、プログラムの再利用性やメンテナンス性、拡張性を高めるために使用されます。

クラスとインスタンス

[編集]

クラスとは、オブジェクトを定義するための設計図のようなものです。クラスには、オブジェクトが持つべきデータや操作を定義することができます。一方、インスタンスとは、クラスを元に作成された具体的なオブジェクトのことです。

メソッドとは何か?

[編集]

メソッドとは、オブジェクトが持つ操作のことです。クラスに定義したメソッドは、そのクラスのインスタンスが持つ操作として利用することができます。

継承とは何か?

[編集]

継承とは、既存のクラスを元にして、新しいクラスを作成する手法です。継承元のクラスは、継承先のクラスに定義されているデータや操作を引き継ぐことができます。これにより、既存のクラスを基盤として、より複雑なクラスを作成することができます。

Rubyのオブジェクト指向プログラミング

[編集]

Rubyのクラス定義とインスタンス作成

[編集]

Rubyでは、クラスを定義するために「class」というキーワードを使用します。また、インスタンスを作成するためには、「new」というメソッドを使用します。以下は、クラスの定義とインスタンスの作成の例です。

class Person
  def initialize(name, age)
    @name = name
    @age = age
  end

  def introduce
    puts "私の名前は#{@name}です。#{@age}歳です。"
  end
end

person1 = Person.new("山田太郎", 20)
person2 = Person.new("鈴木花子", 25)

person1.introduce # => 私の名前は山田太郎です。20歳です。
person2.introduce # => 私の名前は鈴木花子です。25歳です。

メソッドの定義と呼び出し

[編集]

Rubyでは、メソッドを定義するために「def」というキーワードを使用します。また、メソッドを呼び出すためには、オブジェクトのインスタンスに対して「.」を使用して呼び出すことができます。

class Person
  def initialize(name, age)
    @name = name
    @age = age
  end

  def introduce
    puts "私の名前は#{@name}です。#{@age}歳です。"
  end

  def add_age(years)
    @age += years
  end
end

person = Person.new("山田太郎", 20)
person.introduce # => 私の名前は山田太郎です。20歳です。
person.add_age(5)
person.introduce # => 私の名前は山田太郎です。25歳です。

継承

[編集]

Rubyでも継承を使用することができます。継承元のクラスに定義されているデータや操作を引き継いだ上で、新しいデータや操作を追加することができます。

class Animal
  def initialize(name)
    @name = name
  end

  def speak
    puts "#{@name}が鳴きました。"
  end
end

class Dog < Animal
  def speak
    puts "#{@name}がワンワン鳴きました。"
  end
end

animal = Animal.new("猫")
animal.speak # => 猫が鳴きました。

dog = Dog.new("犬")
dog.speak # => 犬がワンワン鳴きました。

アクセサー

[編集]

Rubyでは、インスタンス変数を外部からアクセスするためのアクセサを簡単に定義できます。アクセサを使用すると、外部からインスタンス変数にアクセスするためのgetterメソッド(取得用のメソッド)やsetterメソッド(設定用のメソッド)を自動的に定義できます。

アクセサを定義する方法は、以下の通りです。

class Person
  attr_accessor :name, :age

  def initialize(name, age)
    @name = name
    @age = age
  end
end

person = Person.new("山田太郎", 20)
puts person.name # => 山田太郎
puts person.age # => 20

person.age = 25
puts person.age # => 25

このように、attr_accessorメソッドを使用して、:nameと:ageのアクセサを定義しています。これにより、インスタンス変数にアクセスできます。

attr_accessorメソッドは、:nameと:ageのアクセサを定義するだけで、以下の2つのメソッドを自動的に定義します。

  • nameメソッド:インスタンス変数@nameの値を取得するgetterメソッド
  • name=メソッド:インスタンス変数@nameの値を設定するsetterメソッド

同様に、:ageのアクセサに対しても自動的にgetterメソッドとsetterメソッドが定義されます。

また、attr_readerメソッドを使用すると、getterメソッドだけを定義することもできます。逆に、attr_writerメソッドを使用すると、setterメソッドだけを定義することもできます。

class Person
  attr_reader :name
  attr_writer :age
end

このように、:nameのgetterメソッドと:ageのsetterメソッドを定義しています。これにより、以下のようにインスタンス変数にアクセスできます。

person = Person.new
person.name = "Alice"  # getterメソッドは定義されていないため、エラーになる
person.age = 30        # setterメソッド
puts person.name       # getterメソッドを呼び出して出力する
puts person.age        # インスタンス変数を直接参照して出力する

モジュール

[編集]

Rubyでは、モジュールを使用して、共通の機能をまとめることができます。モジュールは、クラスのように操作を定義することができますが、インスタンスを作成することはできません。

module Greeting
  def hello
    puts "Hello, Ruby!"
  end

  def goodbye
    puts "Goodbye, Ruby!"
  end
end

class Person
  include Greeting

  def initialize(name)
    @name = name
  end

  def introduce
    hello
    puts "私は#{@name}です。"
    goodbye
  end
end

person = Person.new("山田太郎")
person.introduce
実行結果
Hello, Ruby!
私は山田太郎です。
Goodbye, Ruby!

データ構造

[編集]

Rubyは、多くの便利なデータ構造をサポートしています。以下にいくつかの代表的なデータ構造の実装例を示します。

配列 (Array)

[編集]

RubyのArray(配列)は、複数の要素を順序付けて格納するデータ構造です。配列内の要素はインデックスによってアクセスされ、0から始まる整数のインデックスが割り当てられます。Rubyの配列は可変長であり、異なるデータ型の要素を含むことができます。配列は中括弧 [] を使って作成され、要素はカンマで区切られます。例えば、[1, "apple", :symbol, [2, 3], true]のようになります。配列はイテレーションや操作、検索など多くの操作に便利であり、Rubyのプログラミングで広く使用されます。

Rubyの配列 (Array) は、スタックやキューの機能を持っていますが、ここでは基本的なリテラルとインデックスを使った要素参照と操作例をテストケースで示します。

array.rb
# frozen_string_literal: true

require 'minitest/spec'
require 'minitest/autorun'

describe Array do
  let(:ary) { [] }
  let(:numbers) { [1, 2, 3, 4, 5] }
  let(:fruits) { %w[apple banana orange] }

  describe 'empty array creation' do
    it 'creates an empty array' do
      expect(ary).must_equal []
    end

    it '#empty?' do
      expect(ary.empty?).must_equal true
    end
  end

  describe 'Array Manipulation' do
    let(:ary) { [2, 3, 5, 7] }

    it '#[]' do
      expect(ary[2]).must_equal 5
      expect(ary[-1]).must_equal 7
    end

    it '#[]=' do
      ary[2] = 11
      expect(ary).must_equal [2, 3, 11, 7]
      ary[-3] = 13
      expect(ary).must_equal [2, 13, 11, 7]
    end
  end

  describe 'element deletion' do
    it '#delete' do
      fruits.delete('banana')
      expect(fruits).must_equal %w[apple orange]
    end
  end
end

スタック (Stack)

[編集]

スタック構造(Stack)は、データを一時的に保存するための抽象データ型の一つであり、後入れ先出し(Last-In, First-Out; LIFO)の動作を持ちます。これは、最後に追加された要素が最初に取り出されるという意味です。スタックは、物理的なスタックや山のようなイメージで説明されることがあります。

スタックは、以下のような基本的な操作を持ちます:

  1. push: スタックの一番上(トップ)に要素を追加する操作。
  2. pop: スタックの一番上(トップ)から要素を取り出す操作。
  3. peek: スタックの一番上(トップ)にある要素を取得するが、スタックから削除はしない操作。
  4. empty?: スタックが空かどうかを確認する操作。

スタックは、再帰的なアルゴリズムの実装や、バックトラッキング、逆ポーランド記法などの表現方法に使われることがあります。

Rubyの配列はスタックの機能も持っていて、peek を last の別名にする程度でスタックを実装できます。

stack.rb
class Stack < Array
  alias peek last
end

# テストケース
require 'minitest/autorun'

describe Stack do
  let(:stack) { Stack.new }

  describe '#push' do
    it 'adds an item to the stack' do
      stack.push(1)
      expect(stack.peek).must_equal 1
    end
  end

  describe '#pop' do
    it 'removes and returns the last item from the stack' do
      stack.push(1)
      expect(stack.pop).must_equal 1
      expect(stack.empty?).must_equal true
    end
  end

  describe '#peek' do
    it 'returns the last item from the stack without removing it' do
      stack.push(1)
      expect(stack.peek).must_equal 1
      expect(stack.empty?).must_equal false
    end
  end

  describe '#empty?' do
    it 'returns true if the stack is empty' do
      expect(stack.empty?).must_equal true
    end

    it 'returns false if the stack is not empty' do
      stack.push(1)
      expect(stack.empty?).must_equal false
    end
  end

  describe '#size' do
    it 'returns the number of items in the stack' do
      expect(stack.size).must_equal 0
      stack.push(1)
      expect(stack.size).must_equal 1
      stack.push(2)
      expect(stack.size).must_equal 2
    end
  end

  describe 'Stack-specific behavior' do
    it 'pops items in Last-In-First-Out order' do
      stack.push(1)
      stack.push(7)
      stack.push(3)
      stack.push(2)
      stack.push(0)
      expect(stack.pop).must_equal 0
      expect(stack.pop).must_equal 2
      expect(stack.pop).must_equal 3
      expect(stack.pop).must_equal 7
      expect(stack.pop).must_equal 1
      expect(stack.pop).must_be_nil
    end
  end
end

キュー (Queue)

[編集]

Rubyは Queue(キュー)を標準ライブラリに持っていますが、これはThread safeな高度な実装ですが、シングルスレッドで使うと空のキューからdeqするとデッドロック検知に抵触するなど、スレッド間通信に特化しています。

また、Rubyの配列はキューの機能も持っていて、peek を first の別名にするなどでキューを実装できます。

queue.rb
class MyQueue < Array
  alias enqueue push
  alias dequeue shift
  alias size length
  alias peek first
end

require 'minitest/spec'
require 'minitest/autorun'

describe MyQueue do
  let(:queue) { MyQueue.new }

  describe 'empty queue creation' do
    it 'creates an empty queue' do
      expect(queue).must_equal []
    end

    it '#empty?' do
      expect(queue.empty?).must_equal true
    end
  end

  describe 'queue operation' do
    it '#dequeue' do
      expect(queue.dequeue).must_be_nil
    end

    it '#enqueue and #dequeue' do
      queue.enqueue(1)
      queue.enqueue(2).enqueue(3)
      expect(queue.dequeue).must_equal 1
      expect(queue.dequeue).must_equal 2
      expect(queue.dequeue).must_equal 3
      expect(queue.dequeue).must_be_nil
    end

    it '#size' do
      expect(queue.size).must_equal 0
      queue.enqueue(1)
      queue.enqueue(2)
      expect(queue.size).must_equal 2
    end

    it '#peek' do
      queue.enqueue(1)
      queue.enqueue(2)
      expect(queue.peek).must_equal 1
      expect(queue.size).must_equal 2
    end
  end
end

連結リスト (Linked List)

[編集]

Rubyには、標準ライブラリに連結リスト (Linked List)の実装は含まれていませんが、配列(Array)は提供されています。配列はインデックスによるランダムアクセスや動的なサイズ変更が可能で、多くの場合で効率的なデータ構造です。

一方で、リンクリストはメモリ内の連続した領域を使用せず、ポインターによって各要素が次の要素を指し示す形でデータを格納します。これにより、挿入や削除などの操作が効率的に行えますが、ランダムアクセスができないため、要素を順にたどる必要があります。

Rubyでリンクリストを実装する場合、独自のクラスを定義し、それぞれの要素が次の要素を指すポインターを持つようにすることが一般的です。ただし、Rubyにおいて配列が多くのケースで十分に効率的なデータ構造であるため、リンクリストを実装する必要がある場合は、その使用ケースが特定の条件を満たしているかどうかを検討することが重要です。

list.rb
# frozen_string_literal: true

class List < Array
  def to_s = "(#{join(' ')})"
  def inspect = "#{self.class}(#{to_a.join ', '})"
end

require 'minitest/spec'

describe List do
  let(:list0) { List.new }
  let(:list3) do
    list = List.new
    [1, 2, 3].each{ |el| list.push el }
    list
  end
  
  describe '#initialize' do
    it 'initializes an empty list if no arguments are provided' do
      list = List.new
      expect(list.to_a).must_equal []
    end
  end

  describe '#unshift' do
    it 'adds elements to the beginning of the list' do
      list0.unshift(1).unshift(2).unshift(3)
      expect(list0.to_a).must_equal [3, 2, 1]
    end
  end

  describe '#push' do
    it 'adds elements to the end of the list' do
      list0.push(1).push(2).push(3)
      expect(list0.to_a).must_equal [1, 2, 3]
    end
  end

  describe '#shift' do
    it 'removes and returns the first element from the list' do
      expect(list3.shift).must_equal 1
      expect(list3.to_a).must_equal [2, 3]
    end

    it 'Ill. shift args.' do
      expect { list3.shift('unexpected') }.must_raise TypeError
    end

    it 'returns nil if the list is empty' do
      expect(list0.shift).must_be_nil
    end
  end

  describe '#pop' do
    it 'removes and returns the last element from the list' do
      expect(list3.pop).must_equal 3
      expect(list3.to_a).must_equal [1, 2]
    end

    it 'Ill. pop args.' do
      expect { list3.shift('unexpected') }.must_raise TypeError
    end

    it 'returns nil if the list is empty' do
      expect(list0.pop).must_be_nil
    end
  end

  describe '#each' do
    it 'iterates over each element in the list' do
      result = []
      list3.each { |x| result << x }
      expect(result).must_equal [1, 2, 3]
    end

    it 'returns an enumerator if no block is given' do
      expect(list3.each).must_be_kind_of Enumerator
    end
  end

  describe '#to_s' do
    it 'returns a string representation of the list' do
      expect(list3.to_s).must_equal "(1 2 3)"
    end
  end

  describe '#inspect' do
    it 'returns a debug representation of the list' do
      expect(list3.inspect).must_equal "List(1, 2, 3)"
    end
  end

  describe '#empty?' do
    it 'empty' do
      expect(list0.empty?).must_equal true
      expect(list3.empty?).must_equal false
    end
  end
end
    
Minitest.run if $PROGRAM_NAME == __FILE__

ハッシュ (Hash)

[編集]

RubyのHash(ハッシュ)は、キーと値のペアを格納するデータ構造です。キーと値は関連付けられ、キーは一意で重複しない必要があります。Hashはハッシュテーブルを基にしており、高速なデータの検索や取得が可能です。Hashは中括弧 {} を使って作成し、キーと値は キー => 値 の形式で記述します。例えば、{ key1 => value1, key2 => value2 } のようになります。さまざまなオブジェクトがキーとして利用でき、HashはRubyプログラミングで頻繁に使用されます。またキーがシンボル(Symbol)の場合は { :key1 => value1, :key2 => value2 }{ key1: value1, key2: value2 } と書く省略記法があります。

hash.rb
# frozen_string_literal: true

require 'minitest/spec'
require 'minitest/autorun'

describe Hash do
  let(:assoc) { {} }
  let(:fruits) { {apple: 150, banana: 230, orange: 198 } }

  describe 'empty array creation' do
    it 'creates an empty array' do
      expect(assoc).must_equal ({})
    end

    it '#empty?' do
      expect(assoc.empty?).must_equal true
    end
  end

  describe 'Hash Manipulation' do
    let(:tbl) { {a:2, b:3, c:5, d:7} }

    it '#[]' do
      expect(tbl[:c]).must_equal 5
      expect(tbl[:d]).must_equal 7
      expect(tbl[:x]).must_be_nil
    end

    it '#[]=' do
      tbl[:x] = 11
      tbl[:a] = 13
      expect(tbl).must_equal ({:a=>13, :b=>3, :c=>5, :d=>7, :x=>11})
    end
  end

  describe 'element deletion' do
    it '#delete' do
      fruits.delete('banana')
      expect(fruits).must_equal ({:apple=>150, :banana=>230, :orange=>198})
      fruits.delete('cherry')
      expect(fruits).must_equal ({:apple=>150, :banana=>230, :orange=>198})
    end
  end
end

木構造 (Tree)

[編集]

Rubyには、標準ライブラリに木構造の実装は含まれていませんが、独自のクラスを定義して木構造を実装することができます。

通常、木構造はノードと呼ばれるオブジェクトから構成され、各ノードはデータを保持し、他のノードへの参照を持ちます。根ノードから始まり、それぞれのノードが子ノードを持つことがあります。通常、木構造は再帰的なデータ構造であり、再帰的なアルゴリズムを使用して操作されます。

tree.rb
# frozen_string_literal: true

class Tree
  include Enumerable

  Node = Struct.new(:value, :left, :right)

  def initialize(*_args)
    @root = nil
  end

  def height(node = @root) = node.nil? ? 0 : 1 + [height(node.left), height(node.right)].max

  def each(node = @root, &block)
    return to_enum(__method__, node) unless block

    def core(node, &block)
      return if node.nil?

      core(node.left, &block)
      yield node.value
      core(node.right, &block)
    end
    core(node, &block)
    self
  end

  def insert(value, node = @root)
    check_value(value)

    def core(value, node)
      return Node.new(value) if node.nil?

      case value <=> node.value
      when -1 then node.left = core(value, node.left)
      when 1 then node.right = core(value, node.right)
      when 0 # sum value
      else raise TypeError, value.inspect
      end

      node
    end

    @root = core(value, node)
    self
  end

  def search(key, node = @root)
    check_value(key)

    def core(key, node)
      return false if node.nil?

      case key <=> node.value
      when -1 then core(key, node.left)
      when +1 then core(key, node.right)
      when 0 then true
      else raise TypeError, "#{self.class}#core: #{key.inspect}"
      end
    end

    core(key, node)
  end

  def delete(key, node = @root)
    check_value(key)

    def core(key, node)
      return node if node.nil?

      case key <=> node.value
      when -1
        node.left = core(key, node.left)
        return node
      when +1
        node.right = core(key, node.right)
        return node
      when 0 # sum value
      else raise TypeError, value.inspect
      end

      if node.left.nil?
        return node.right
      else
        succParent, succ = node, node.right
        succParent, succ = succ, succ.left while succ.left
        if succParent != node
          succParent.left = succ.right
        else
          succParent.right = succ.right
        end

        node.value = succ.value
        node
      end
    end
    @root = core(key, node)
    self
  end

  def to_s =  "(#{to_a.join ' '})"
  def inspect ="#{self.class}(#{to_a.join ', '})"

  protected

  def check_value(value)
    raise TypeError, "Invalid value: #{value.inspect}" unless value.respond_to?(:<)
    raise TypeError, "Invalid value: #{value.inspect}" if value.is_a?(Numeric) && !value.finite?
  end
end

require 'minitest/spec'
require 'minitest/autorun'

describe Tree do
  let(:tree0) { Tree.new }
  let(:tree3) do
    tree = Tree.new
    [1, 2, 3].each { |el| tree.insert el }
    tree
  end

  describe '#initialize' do
    it 'initializes an empty list if no arguments are provided' do
      expect(tree0.to_a).must_equal []
    end
  end

  describe '#insert' do
    let(:ary) { (0...1000).to_a }

    it 'adds elements to tree' do
      tree0.insert 10
      expect(tree0.to_a).must_equal [10]
      tree0.insert 20
      expect(tree0.to_a).must_equal [10, 20]
      tree0.insert 30
      expect(tree0.to_a).must_equal [10, 20, 30]
      tree0.insert 25
      expect(tree0.to_a).must_equal [10, 20, 25, 30]
      tree0.insert 15
      expect(tree0.to_a).must_equal [10, 15, 20, 25, 30]
    end

    it 'adds elements to tree by sequebce order' do
      ary.each { |el| tree0.insert el }
      expect(tree0.to_a).must_equal ary
    end

    it 'adds elements to tree by shuffle' do
      ary.shuffle.each { |el| tree0.insert el }
      expect(tree0.to_a).must_equal ary
    end
  end

  describe '#search' do
    it 'find' do
      expect(tree3.to_a).must_equal [1, 2, 3]
      expect(tree3.search(1)).must_equal true
      expect(tree3.search(3)).must_equal true
      expect(tree3.search(2)).must_equal true
    end

    it 'nofound' do
      expect(tree3.to_a).must_equal [1, 2, 3]
      expect(tree3.search(0)).must_equal false
      expect(tree3.search(100)).must_equal false
      expect(tree3.search(4)).must_equal false
    end
  end

  describe '#delete' do
    it 'find' do
      expect(tree3.to_a).must_equal [1, 2, 3]
      expect(tree3.delete(1)).must_be_kind_of Tree
      expect(tree3.to_a).must_equal [2, 3]
      expect(tree3.delete(3)).must_be_kind_of Tree
      expect(tree3.to_a).must_equal [2]
      expect(tree3.delete(2)).must_be_kind_of Tree
      expect(tree3.to_a).must_equal []
    end

    it 'nofound' do
      expect(tree3.to_a).must_equal [1, 2, 3]
      expect(tree3.delete(0)).must_be_kind_of Tree
      expect(tree3.delete(100)).must_be_kind_of Tree
      expect(tree3.delete(4)).must_be_kind_of Tree
      expect(tree3.to_a).must_equal [1, 2, 3]
    end
  end
end

オブジェクト指向プログラミングとデザインパターン

[編集]

オブジェクト指向プログラミングは、プログラミングの一種で、オブジェクトと呼ばれるデータ構造と、それらを操作するメソッドを中心に構成されています。オブジェクトは、それ自体が独自の状態を持ち、他のオブジェクトと相互作用することができます。このように、オブジェクト指向プログラミングは、複雑な問題を扱いやすく、保守性と拡張性に優れたコードを書くことができます。

デザインパターンは、オブジェクト指向プログラミングにおける再利用可能なソリューションのことです。デザインパターンは、共通の問題に対する解決策を提供し、コードの再利用性、保守性、拡張性を向上させます。デザインパターンは、一般的なコーディングのベストプラクティスを体系化し、標準化された方法で問題を解決するための枠組みを提供するため、オブジェクト指向プログラミングの重要な概念です。

デザインパターンは、GoF(Gang of Four)が定義した23のパターンが最も有名です。これらのパターンは、それぞれ特定の問題を解決するために使用されます。例えば、シングルトンパターンは、特定のクラスのインスタンスが必要な場合に、1つのインスタンスしか作成しないようにするために使用されます。ファクトリーパターンは、異なる種類のオブジェクトを作成するために使用されます。また、ストラテジーパターンは、アルゴリズムのファミリーを定義し、それらをカプセル化し、相互に交換可能にするために使用されます。

デザインパターンは、オブジェクト指向プログラミングの基礎であり、再利用可能なコードを書くために非常に重要です。オブジェクト指向プログラミングを学ぶ際には、デザインパターンも一緒に学ぶことをお勧めします。

ファクトリーメソッドパターン

[編集]

ファクトリーメソッドパターンは、インスタンスの生成を専門に行うクラスを定義することで、複雑なインスタンスの生成処理を隠蔽するパターンです。

class AnimalFactory
  def create_animal(type, name)
    case type
    when :cat
      Cat.new(name)
    when :dog
      Dog.new(name)
    end
  end
end

class Cat
  def initialize(name)
    @name = name
  end

  def speak
    puts "#{@name}がニャーと鳴きました。"
  end
end

class Dog
  def initialize(name)
    @name = name
  end

  def speak
    puts "#{@name}がワンワン鳴きました。"
  end
end

factory = AnimalFactory.new
animal1 = factory.create_animal(:cat, "たま")
animal1.speak # => たまがニャーと鳴きました。

animal2 = factory.create_animal(:dog, "ポチ")
animal2.speak # => ポチがワンワン鳴きました。

シングルトンパターン

[編集]

シングルトンパターンは、インスタンスを1つだけ生成し、それを共有するパターンです。

class SingletonObject
  private_class_method :new

  @@instance = nil

  def self.instance
    @@instance ||= new
  end

  def message
    puts "Hello, Singleton!"
  end
end

singleton1 = SingletonObject.instance
singleton1.message # => Hello, Singleton!

singleton2 = SingletonObject.instance
puts singleton1 == singleton2 # => true

上記のコードは説明のために実装の細部を示しましたが、Rubyにはシングルトンを実装するためのモジュール Singleton があります。

require 'singleton'

class SingletonObject
  include Singleton

  def message
    puts "Hello, Singleton!"
  end
end

singleton1 = SingletonObject.instance
singleton1.message # => Hello, Singleton!

singleton2 = SingletonObject.instance
puts singleton1 == singleton2 # => true

Singleton モジュールは、シングルトンパターンの実装を支援するためのものであり、クラスに include Singleton を追加することでシングルトンパターンが利用できるようになります。Singleton モジュールはクラスメソッド instance を提供し、このメソッドを呼び出すことで唯一のインスタンスを取得できます。また、clonedup メソッドがオーバーライドされており、これによりシングルトンインスタンスのクローンや複製が防がれています。

まとめ

[編集]

この本では、Rubyを使用したオブジェクト指向プログラミングの基本的な概念と使い方について学びました。また、応用的な使い方として、ファクトリーメソッドパターンやシングルトンパターンについても紹介しました。

これらの概念や使い方は、プログラミングにおいて非常に重要なものであり、これらの理解と習得は、プログラミングスキルの向上につながります。

特に、オブジェクト指向プログラミングは、複雑な処理を扱う場合に非常に有用です。オブジェクト指向プログラミングによって、処理をオブジェクトに分割することで、大規模なプログラムの開発が容易になり、保守性や拡張性が向上します。

本書で学んだ内容を実践的に活用し、オブジェクト指向プログラミングのスキルを向上させることをお勧めします。また、本書がプログラミングの初心者でも理解できるように配慮して執筆していますので、プログラミングの初学者にも是非挑戦していただきたいと思います。