Ruby
 Computer >> コンピューター >  >> プログラミング >> Ruby

なぜクラスを作るのか?抽象化の重要性と良いクラス設計の基本

前回の記事「なぜnilを使うのか」に続き、普段当たり前と思って使っているものについても、同じ問いを立ててみるのは良いアイデアではないでしょうか。

例えば…

巨大で醜いコードの塊を1つ作るのではなく、なぜ私たちはクラスを使うのでしょうか?

基本的なレベルで言えば、コードとデータを論理的な単位に整理するためにクラスを使います。しかし、それだけではありません。

クラスを使うと、抽象化(abstraction)を作り出せるのです。

抽象化とは何か?

実は、私たちは毎日、毎分、抽象化を利用しています。

今この記事を書いているキーボードは、次のようなもので構成されています。

  • ケーブル
  • Cherry MX Blackスイッチ
  • 全体をまとめるプラスチックの筐体
  • 電気信号をコンピュータへ伝える基板

キーボードとは、これらを正しく組み合わせたときに生まれる「概念」にすぎません。

これが抽象化である理由は、内部の仕組みを知らなくても使えるからです。構成部品を知る必要すらありません。

接続して、タイピングを始めるだけ。まさにこれこそが、クラスが私たちに可能にしてくれることなのです。

なぜ抽象化が重要なのか?

抽象化は、複雑さの管理に役立ちます。

『Code Complete』の著者Steve McConnellはこう述べています。

「クラスを作成する最も重要な理由は、プログラムの複雑さを軽減することである」

では、抽象化はどのように複雑さを軽減するのでしょうか?

それは、仕組みに関する詳細を隠し、公開インターフェースを通じてサービスを提供することによって実現されます。

Rubyのクラスでは、インターフェースはpublicメソッドで構成され、詳細はprivateメソッドで隠されます。

なぜクラスを作るのか?抽象化の重要性と良いクラス設計の基本

ここでいう「詳細」とは、次のようなものです。

  • インスタンス変数
  • 内部メソッド
  • アルゴリズムとデータ構造

これらが隠されていれば、クラスの利用者に影響を与えることなく、いつでも自由に変更できます。

例えば、データを配列で保存するGPSクラスがあるとします。「配列のインデックス0に緯度、インデックス1に経度が入っている」ということを知らなければならないとしたら、それはクラスの利用者が知るべきではない詳細です。

良いクラスの書き方

さて、これは非常に大きなテーマです。

良いクラスを作るには、SOLID原則に従うなど、考慮すべき要素がたくさんあります。

しかし最も重要なのは、クラスが何らかの役割(role)を持つことであり、無関係なメソッドの寄せ集めであってはならないという点です。

例えば、ビジネスロジック(計算やルール)とプレゼンテーションロジック(フォーマット処理)は役割が異なるため、分離すべきです。

次のコードを見てください

require 'rest-client'
require 'nokogiri'

class ImageCounter
  def initialize(url)
    @url = url
  end

  def find_images_and_report
    data   = RestClient.get(@url).body
    html   = Nokogiri::HTML.parse(data)
    images = html.css("img")

    report = ""
    report << "=" * 40 << "\n"
    report << "Found #{images.size} images on #{@url} \n"
    report << "=" * 40 << "\n"
  end
end

reporter = ImageCounter.new("rubyguides.com")
report   = reporter.find_images_and_report

このクラスが、画像の取得・カウントという計算処理と、レポートの整形という表示処理の両方に関わっていることがわかるでしょう。手早く書く「使い捨て」スクリプトなら問題ありませんが、本格的なアプリケーションでは分離すべきです。

責任を分離すれば、別のプレゼンテーション層を差し替えたり、ビジネスルールを変更しながら同じプレゼンテーションを再利用したりできます。

クラスによるこのコード再利用の側面は、クラスをもっと活用すべきもう一つの大きな理由です。

より良いクラスを書くための考え方は他にもあります。さらに学びを深めたい方は、次のキーワードを調べてみてください。

  • 凝集度と結合度(Cohesion & Coupling)
  • Tell, Don't Ask(聞くな、命じよ)
  • デメテルの法則(Law of Demeter)

そもそもなぜクラスを書くのかを理解することが、良い出発点になります。最後まで読んでくださったあなた、素晴らしい!

まとめ

今回は、なぜクラスを書くのか、抽象化とは何か、そしてそれがなぜ重要なのかを学びました。さらに、良いクラスを書くための基本と、さらなる学習のためのキーワードも紹介しました。

コードをBig Ball of Mud(泥だんご)にしたくなければ、今日からこれらの概念を取り入れて、クラス設計を改善していきましょう🙂

この記事が役に立ったら、Twitterでのシェアをお忘れなく!

  1. 【C++入門】ネストされたクラス(入れ子クラス)の定義方法と使い方

    ネストされたクラスとはネストされたクラス(入れ子クラス)とは、別のクラスの内部で宣言されたクラスのことです。ネストされたクラスは、それを囲む外側のクラスのメンバーの一つとして扱われ、他のメンバーと同じアクセス権限を持ちます。ただし注意点として、外側のクラスのメンバー関数が、ネストされたクラスのメンバーに対して特別なアクセス権を持つことはありません。つまり、外側のクラスだからといって、ネストされたクラスのprivateメンバーに自由にアクセスできるわけではありません。以下に、C++におけるネストされたクラスの基本的な使用例を示します。サンプルプログラム#include<iostream&g

  2. Javaのラッパークラスとは?必要な理由とオートボクシング・アンボクシングの使い方

    ラッパークラス(Wrapper class)とは、プリミティブデータ型(int、char、short、byteなど)を包み込んで扱うためのクラスです。言い換えると、ラッパークラスを使うことで、プリミティブデータ型をオブジェクトとして操作できるようになります。 Javaでは、各プリミティブ型に対応するラッパークラスがjava.langパッケージに用意されています。主な対応関係は以下のとおりです。 byte → Byte short → Short int → Integer long → Long float → Float double → Double char → Character b