「コーディングを支える技術」を読んでだので、クラスに関して整理してみた

背景

「コーディングを支える技術」を読んだので、クラスに関して理解したことを整理するとともに、 普段Rubyを使う機会が多いため、クラスの機能がRubyでどのように表現できるのか?に関しても考えてみたいと思います。

クラスとは?

クラスという機能がプログラミング言語に導入されるに至った背景や、概念としての機能を整理しておきます。

  • オブジェクト指向とクラス
    • クラスに至るまでのニーズや背景
      • 現実世界のモノ(object)をコンピュータで扱えるようにするための概念をつくりたい
      • 変数と関数をまとめて模型をつくりたい
      • クラスの概念とプログラミング言語への実装
      • オブジェクト指向の設計者によって概念が異なることに注意
    • はじまりは分類として
      • C++Javaで今日のクラスの役割ができた
  • クラスの機能(概念)
    1. まとまったもの(object)を作る生成器
    2. どのような操作が可能かという仕様
      • インターフェース
    3. コードを再利用する単位
      • 継承

オブジェクト指向プログラミングにおけるクラスの機能

前述のクラスの機能に関しての概念をもとに、オブジェクト指向プログラミングにおけるクラスの機能を書き出したのが以下となります。

  • まとまったもの(object)を作る生成器
  • どのような操作が可能かという仕様
  • コードを再利用する単位
    • 継承

Rubyにおけるクラスの表現

Rubyにおいて前述の「クラスの機能」を表現する方法として以下の2機能が用意されています。

  • module
    • メソッドと定数をまとめたもの
    • インスタンスを生成することはできない
    • クラスとその他のモジュールにmix-inすることができる
    • 継承することはできない
  • class
    • インスタンスを作成することができる
    • インタンス変数として内部に状態を持つことができる
    • 他のクラスから継承することができる
    • モジュールを継承することはできない

modlueとclassを使用して、「クラスの機能」をRubyで表現する方法に関して整理や使い分けに関して考えてみようと思います。

まとまったもの(object)を作る生成器

「module」や「class」が該当します。 それぞれの役割として基本的には - module: メソッドや定数をまとめたいが、内部に状態(インスタンス変数)を持つ必要がない - class: 内部に状態を持つ必要がある の観点で使い分けます。

また、型に関しては、Rubyは動的型付け言語であるためオブジェクトの型はそのクラスとは厳密には結びついておらず、オブジェクトが反応するメソッドの集合であることから、objectの生成器としてではなく、操作の仕様(後述)として分類することにしました。

どのような操作が可能かという仕様

動的型付け言語のため、インターフェースの定義のようなものは言語機能としては用意されていないため、使い分けなどの観点などはなさそうでした。 (インタフェースの定義を実現したい場合は、moduleを使用することで、近しいものを実装することは可能のようです。) また、ポリモーフィズムはダックタイピングにより実現されています。

コードを再利用する単位

  • 継承
    • Rubyにおいては単一継承のみが可能
    • 多重継承を実現する場合はmix-inを活用する
  • mix-in
    • 基本的にモジュールのメンバメソッドやクラスメソッドに委譲して使用する
    • インスタンス変数は使用せず、委譲し、引数経由で状態の受け渡しを行うことで再利用性を高く保ようにする

classによる単一継承、moduleを活用したmix-inが用意されています。 基本はmix-inを活用し、委譲によりコードの再利用をすることが推奨されていますが、以下の観点で使い分けを行うとよさそうです。

  • 継承
    • 1つのクラスから再利用できる部分が1つだけの場合
  • mix-in
    • 1つのクラスから再利用できる部分が複数にわたる(オブジェクトに共通の振る舞いがある)場合

どちらの場合もテンプレートメソッドパターンやフックメソッドを使用して、使用者に変化可能な箇所とそうではないインターフェースに関して伝える努力をすることが重要です。

また、継承やmix-inの使い分け以前に「継承可能なコード」を書くことがポイントとなります。

継承可能なコードの状態とは、以下のような状態を指します。 ※「オブジェクト指向設計実践ガイド」に詳しく書かれています。

  • 抽象スーパークラス内のコードを使わないサブクラスがないこと 
  • リスコフの置換原則に従っていること
  • テンプレートメソッドが使用されていること
  • 継承する側でsuperを呼び出すことを避け、継承時の親子関係を疎結合にすること(フックメッセージの活用)
  • 階層構造を浅く保つこと です。

上記より継承かmix-inかを決めきれない場合には、以下のような流れで設計を進めるとよさそうです。

  1. 継承可能なコードにする
  2. その上で、共通の振る舞いを持つオブジェクトがあれば、その振る舞いをmoduleとして切り出しmix-inする

おわりに

クラスという機能が言語仕様として実装された背景や機能としての概念をベースに、Rubyのクラスという機能の表現に関して改めて考えることができました。

gemやフレームワークを提供する側になると使用者にどのように使ってほしいか?をコードからも意図伝える必要が出くるのではないでしょうか。

前述した、Javaのinterfaceに近しいものをmoduleで実現するように、その他の言語におけるクラスの機能を持ち込むための工夫(abstract classを実現する)に関しても今後深堀っていきたいと思います。

参考

【書籍】 www.amazon.co.jp

www.amazon.co.jp

【その他】

https://xtech.nikkei.com/it/article/COLUMN/20050912/220974/