Savvy Code

オブジェクト指向のカプセル化とは?初心者向けに分かりやすく解説!【サンプルコードあり】

オブジェクト指向のカプセル化について今日は紹介するよ!

  • オブジェクト指向のカプセル化ってなに?
  • オブジェクト指向のカプセル化は何のためにあるの?
  • 説明だけだとわからないからコードで書きたい

などの疑問をお持ちの方の悩みを解決できる記事になっています!

オブジェクト指向は初心者の人にとって難しいかもしれません。色々なサイトで見たけど、よくわからなかったという人も多いのではないでしょうか。

オブジェクト指向の重要な概念で、次のようなものがあります。

  • クラス
  • カプセル化
  • 継承
  • ポリモーフィズム

オブジェクト指向を習得するためには、これらの理解が必要です。オブジェクト指向が扱う範囲は広いです。そのため、基本をしっかりと抑えておかないと、挫折してしまうかもしれません。

この記事では、オブジェクト指向のカプセル化、カプセル化の目的、カプセル化の使い方などを完全初心者向けに解説します!

サンプルのコードをあわせて説明しますので、実際にコードを書いて動作確認してみましょう!

記事を読めば、オブジェクト指向のクラスについて理解ができますよ!

こんな人におすすめ
  • オブジェクト指向のカプセル化の使い方を知りたい人
  • オブジェクト指向のカプセル化をコードで書けるようになりたい人
  • オブジェクト指向を一言で説明できない人

オブジェクト指向のカプセル化を学ぶ前に

オブジェクト指向のカプセル化を学習する前に注意してほしいことがあります。

オブジェクト指向のカプセル化を理解するために、実際にコードを書いて動作確認をしてください。

なぜなら、コードを書かずに頭だけで理解することは難しいからです。

サンプルコードを書いて動作確認することを「写経」といいます。

写経することで、カプセル化の書き方や、なぜ動くのかを確認することができます。初心者の頃は横着してコピペする人が多いですが、これは非常にもったいないです。

手を動かして書くことで、記憶の定着が上がり、コードを書くことに慣れます。

実際に、プロのエンジニアも新しいプログラミング言語を習うときは、写経する人が多いです。

写経することで、頭の整理がしやすいからですね。

それに、体で覚える感覚を養うことができます。

私も写経は必ずやります。

オブジェクト指向をしっかり理解したい人は、サンプルコードを書くようにしてくださいね。

それでは、見てみましょう!

プログラミング言語って何?
そもそも「プログラミング」がよくわからないという人はこちらもあわせてご確認ください。 プログラミングとは?初心者向けにわかりやすく解説!

Rubyの実行環境

この記事では、カプセル化のサンプルコードは「Ruby」を書きます。

Rubyの実行環境は手元のパソコンでもいいですし、オンライン上でも大丈夫です。

例えば、こちらのサイトならオンライン上でコードを書くことができます。

Execute Ruby Online

オブジェクト指向ってなに?

オブジェクト指向とは、システム開発を効率的に行うための総合的な技術を指します。

オブジェクト指向は当初はプログラミング言語として開発されました。「クラス」「継承」「ポリモーフィズム」などの概念を取り入れ、オブジェクト指向プログラミング言語(OOP)と呼ばれるようになりました。

その後、プログラミング以外にもデザインパターンやUML、モデリングなどにも応用され、オブジェクト指向という概念が形成されました。

オブジェクト指向の基本についてこちらの記事で詳しく解説しています。オブジェクト指向のカプセル化を理解するためにも目を通しておきましょう。

【保存版】オブジェクト指向とは?プログラミング初心者向けに解説!

オブジェクト指向のクラスとは

クラスとは「種類」「分類」を意味し、「性質が同じものの集まり」です。

Rubyでクラスを書くときは、classキーワードを使います。

次の例では、車を表すCarクラスを定義しています。

class Car
    def initialize(name, color, model_year)
        @name = name
        @color = color
        @model_year = model_year
    end
   
   attr_accessor :name, :color, :model_year
end

インスタンスは、new を使って次のように書きます。

class Car
    def initialize(name, color, model_year)
        @name = name
        @color = color
        @model_year = model_year
    end
   
   attr_accessor :name, :color, :model_year
end

# インスタンスをつくる(インスタンス化させる)
toyota = Car.new("TOYOTA", "黒", "20年式")

puts toyota.name
puts toyota.color
puts toyota.model_year

クラスとインスタンスの違いや、クラスの使い方はこちらで解説しているので確認してみましょう。

オブジェクト指向のクラスとは?初心者でもわかる!

オブジェクト指向のカプセル化とは

オブジェクト指向のカプセル化とは、「インスタンスのプロパティやメソッド隠蔽してアクセスできる範囲を明確にすること」です。

なぜ、インスタンスのプロパティやメソッドを隠さなければならないのでしょうか。

例えば、車というクラスがあるとします。

車クラスは次のようなプロパティとメソッドをもっています。

クラス プロパティ メソッド
  • 名前
  • 年代
  • 年代を返す

これをRubyで表現すると次のようになります。

class Car
    def initialize(name, color, model_year)
        @name = name #名前
        @color = color #色
        @model_year = model_year #年代
    end
    
   # 年代を返す
    def get_model_year
        @model_year
    end
    
    attr_accessor :name, :color, :model_year
end

ではインスタンスを生成してみましょう。

インスタンスを生成し、車の「年代」を出力してみます。

class Car
    def initialize(name, color, model_year)
        @name = name #名前
        @color = color #色
        @model_year = model_year #年代
    end
    
    # 年代を返す
    def get_model_year
        @model_year
    end
    
    attr_accessor :name, :color, :model_year
end

# インスタンスを生成する
toyota = Car.new("TOYOTA", "黒", "20年式")

# 年代を出力する
puts toyota.get_model_year

get_model_yearを実行すると次のように「20年式」という結果がかえってきました。

get_model_yearで年代を取得できました。

しかし、get_model_yearを使わなくても年代を取得できる方法がもう一つあります。

それは、プロパティのmodel_yearに直接アクセスして取得する方法です。

インスタンスのmodel_yearにアクセスして年代を出力してみましょう。

class Car
    def initialize(name, color, model_year)
        @name = name #名前
        @color = color #色
        @model_year = model_year #年代
    end
    
    # 年代を返す
    def get_model_year
        @model_year
    end
    
    attr_accessor :name, :color, :model_year
end

# インスタンスを生成する
toyota = Car.new("TOYOTA", "黒", "20年式")

# 年代を出力する
puts toyota.get_model_year

# プロパティにアクセスして取得する
puts toyota.model_year

実行した結果は次の通りです。同じ値が出力されていますね。

「年代」を取得する方法が二つあることがわかりました。

しかし、これは年代の取得方法が、使う人によって別れてしまうかもしれません。

ある人はプロパティのmodel_yearを使用し、ある人はメソッドのget_model_yearを使用するかもしれません。

このようにバラバラに使われてしまうとクラスの変更がしづらくなります。

なぜなら、変更の影響範囲が大きくなるからです。

例えば、プロパティのmodel_yearを変更すると全てのインスタンスのmodel_yearを変更しなければなりません。100個インスタンスがあったときは、100箇所変更範囲が及びます。

また、仮にこのクラスを作った人がget_model_yearだけ使ってくれると期待して、プロパティのmodel_yearの名前をmodel_year_2に変更してしまったとします。

すると、インスタンスのmodel_yearをアクセスしてた箇所が一気にエラーになってしまいます。

変更の影響範囲が大きくなるのは、プログラミングでは好ましくありません。影響範囲はなるべく小さくして責任を明確に分けた方がメンテナンス性の高いコードになります。

それを実現するために、カプセル化が使用されます。

カプセル化はプロパティやメソッドを隠蔽して、責任を明確にするものです。

ここでは、プロパティのmodel_yearを隠蔽してget_model_yearだけアクセスできるように変更しましょう。

Rubyでインスタンスのプロパティへのアクセス制御はattr_accessorで行います。

attr_accessorで定義した値がインスタンスからアクセスできるので、model_yearをここから消します。

class Car
    def initialize(name, color, model_year)
        @name = name
        @color = color
        @model_year = model_year
    end
    
    def get_model_year
        @model_year
    end
    
    # model_yearを消す
    attr_accessor :name, :color
end


toyota = Car.new("TOYOTA", "黒", "20年式")


puts toyota.get_model_year

# プロパティにアクセスできない
# puts toyota.model_year

これで、プロパティのmodel_yearは外からアクセスできなくなりました。

こうすることで、get_model_yearだけが「年代」にアクセスできるメソッドとして定義できます。

もしmodel_yearにアクセスしようとすると、コンパイラはエラーを出力します。

カプセル化を使うことで、アクセスできる範囲を明確にできました。

このように、カプセル化はクラス内で公開にしたい項目と公開したくない項目を分けることができます。

なぜ、カプセル化が必要なのか

カプセル化の目的は、「アクセスできる範囲を明確にすること」です。

アクセスできる範囲を明確にすることで得られるメリットは次のようなものがあります。

  • 修正の影響範囲を小さくできる
  • 変更に強いクラス設計ができる

修正の影響範囲を小さくできる

前述した通り、アクセスできるプロパティやメソッドを絞ることで公開する範囲を制限できます。

範囲を制限できれば、内部のプロパティが変わっても影響範囲を最小限に抑えられます。

例えば、model_yearが公開されてる状態を考えてみましょう。

次のように、複数のインスタンスでmodel_yearを使われているとします。

class Car
    def initialize(name, color, model_year)
        @name = name
        @color = color
        @model_year = model_year
    end
    
    def get_model_year
        @model_year
    end
    
    attr_accessor :name, :color, :model_year
end


car1 = Car.new("TOYOTA", "黒", "20年式")
car2 = Car.new("TOYOTA", "赤", "10年式")
car3 = Car.new("TOYOTA", "青", "08年式")
car4 = Car.new("TOYOTA", "グレー", "06年式")


# 複数のインスタンスでmodel_yearが使われている
puts car1.model_year
puts car2.model_year
puts car3.model_year
puts car4.model_year

次に、新しいメソッド「get_name_and_model_year」を定義します。

class Car
    def initialize(name, color, model_year)
        @name = name
        @color = color
        @model_year = model_year
    end
    
    def get_model_year
        @model_year
    end
    
    # 新しいメソッドを定義する
    def get_name_and_model_year
        @name + @model_year
    end
    
    attr_accessor :name, :color, :model_year
end


car1 = Car.new("TOYOTA", "黒", "20年式")
car2 = Car.new("TOYOTA", "赤", "10年式")
car3 = Car.new("TOYOTA", "青", "08年式")
car4 = Car.new("TOYOTA", "グレー", "06年式")


puts car1.model_year
puts car2.model_year
puts car3.model_year
puts car4.model_year


puts car1.get_name_and_model_year

get_name_and_model_yearは「名前 + 年代」を繋げた値を返します。

では、ここでmodel_yearmodel_year_2に変更したいとしましょう。

変更を適用すると次のようになります。

class Car
    def initialize(name, color, model_year)
        @name = name
        @color = color
        
        # model_year_2の変更を適用
        @model_year_2 = model_year
    end
    
    def get_model_year
        # model_year_2の変更を適用
        @model_year_2
    end
    
    def get_name_and_model_year
        # model_year_2の変更を適用
        @name + @model_year_2
    end
    
    # model_year_2の変更を適用
    attr_accessor :name, :color, :model_year_2
end


car1 = Car.new("TOYOTA", "黒", "20年式")
car2 = Car.new("TOYOTA", "赤", "10年式")
car3 = Car.new("TOYOTA", "青", "08年式")
car4 = Car.new("TOYOTA", "グレー", "06年式")

# model_year_2の変更を適用
puts car1.model_year_2
puts car2.model_year_2
puts car3.model_year_2
puts car4.model_year_2


puts car1.get_name_and_model_year

クラス内だけでなく、インスタンスの方まで変更範囲が及びましたね。

では、もしget_model_yearを使っていた場合はどうでしょうか。

class Car
    def initialize(name, color, model_year)
        @name = name
        @color = color
        
        # model_year_2の変更を適用
        @model_year_2 = model_year
    end
    
    def get_model_year
        # model_year_2の変更を適用
        @model_year_2
    end
    
    def get_name_and_model_year
        # model_year_2の変更を適用
        @name + @model_year_2
    end
    
    # model_year_2の変更を適用
    attr_accessor :name, :color, :model_year_2
end


car1 = Car.new("TOYOTA", "黒", "20年式")
car2 = Car.new("TOYOTA", "赤", "10年式")
car3 = Car.new("TOYOTA", "青", "08年式")
car4 = Car.new("TOYOTA", "グレー", "06年式")

# model_year_2の変更は影響されない
puts car1.get_model_year
puts car2.get_model_year
puts car3.get_model_year
puts car4.get_model_year


puts car1.get_name_and_model_year

クラス内の影響は変わらずですが、インスタンスからのアクセス箇所は変更されていません。

このように、カプセル化することによってクラス内部の影響を外部に漏らさずに最小限で抑えることができます。

変更に強いクラス設計ができる

カプセル化で影響範囲を最小限に抑えることができるということは、変更に強いクラス設計ができるということにもなります。

オブジェクト指向は「効率的にシステム開発をするために使われる技術」です。

効率的なプログラミングをするためには、変更に強い仕組みを作らなければなりません。

なぜなら、提供するサービスやビジネスは日々進化し、それにともないシステムを変更をしなければならないからです。

システムを柔軟に変更するには、クラス設計が重要になります。

クラスの内部実装は柔軟に変更できるように構築しなければなりません。

そのためにカプセル化という技術が使われるのです。

まとめ

この記事では、オブジェクト指向のカプセル化についてまとめました。

最後にもう一度内容を確認しましょう。

  • オブジェクト指向のカプセル化とは「インスタンスのプロパティやメソッド隠蔽してアクセスできる範囲を明確にすること」
  • カプセル化でアクセスできる範囲を明確にすると「修正の影響範囲を小さくできる」「変更に強いクラス設計ができる」などのメリットを享受できる

カプセル化と聞くと難しそうに聞こえるかもしれませんが、その目的や必要性を理解できれば理にかなった概念だと気づくでしょう。

いまいちピンとこない人は、実際にコードを書いてみましょう!上記であげたサンプルコードを写経してください。

一つ一つ何をやっているのか確認しながら、書いてください。

なんとなくでも理解できれば今は十分です。

実際の現場で使うことになれば、より実践的な使い方を学べるでしょう。そのときのために頭の片隅に置いておいてください。

オブジェクト指向は難しい概念ですが、基礎を理解し、実践すれば身に付きます!

オブジェクト指向のカプセル化以外にも、クラス、継承、ポリモーフィズムも重要な概念です。

こちらもあわせてしっかり確認しておきましょう!