Mixin 的な実装の継承を実現する: Ruby module, Java interface, PHP trait


概要

  • 一般的なクラスの継承機能を使用せずに、 Mixin を実現するサンプルコードを紹介する

Mixin (Mix-in, ミックスイン) とは

実装されているある機能について、プログラミング言語のクラス継承機能などで、機能を引き継いで使えるようにする機能である。
プログラミング言語によっては、一般的なクラスの継承機能を使用せずに Mixin を実現している。

Mixin - Wikipedia

mixin とはオブジェクト指向プログラミング言語において、サブクラスによって継承されることにより機能を提供し、単体で動作することを意図しないクラスである。

Mixin と似たような概念に Trait というものがある。

トレイト - Wikipedia

トレイト (英: trait) とはコンピュータープログラミングにおける概念で、構造的にオブジェクト指向プログラミングを行うための簡素な概念モデルとして使われるメソッド群の集まりである。

トレイトはミクスインと類似しているが、ミクスインでは継承操作のみによってメソッドを合成させるが、トレイトでは、対称的な足し合せ、メソッド排除、別名化など、より多くの方法でメソッドを合成できる。また、トレイトは合成時にメソッドの型指定のみならず実装も与えるという点で、インタフェースとも異なっている。

Ruby module で Mixin を実現する

Ruby では module を利用して Mixin を実現できる。

はじめに (Ruby 2.6.0)

Rubyは多重継承は複雑さの源であるという見地から、 意図的に多重継承を持っていませんが、 モジュールを使ってクラス階層を横断して実装を共有できます。 この機能を"Mix-in"と呼びます。

サンプルコード

module Printer

  # コンテンツを出力する
  def print
    # クラス側の @content 変数を読み込んで出力する
    puts "#{@content}"
  end
end

class Message

  # Printer モジュールを Mix-in する
  include Printer

  def initialize(content)
    @content = content
  end
end

# Message クラスは Printer モジュールを Mix-in しているので
# print メソッドが使えるようになる
m = Message.new('Hello, world')
m.print

動作確認環境と実行結果

$ ruby -v
ruby 2.6.3p62 (2019-04-16 revision 67580) [x86_64-darwin18]

$ ruby mixin.rb 
Hello, world

PHP trait で Mixin を実現する

PHP では trait を利用して Mixin を実現できる。

PHP: トレイト - Manual

PHP 5.4.0 以降では、コードを再利用するための「トレイト」という仕組みが導入されました。
トレイトは、PHP のような単一継承言語でコードを再利用するための仕組みのひとつです。 トレイトは、単一継承の制約を減らすために作られたもので、 いくつかのメソッド群を異なるクラス階層にある独立したクラスで再利用できるようにします。 トレイトとクラスを組み合わせた構文は複雑さを軽減させてくれ、 多重継承や Mixin に関連するありがちな問題を回避することもできます。
トレイトはクラスと似ていますが、トレイトは単にいくつかの機能をまとめるためだけのものです。 トレイト自身のインスタンスを作成することはできません。 昔ながらの継承に機能を加えて、振る舞いを水平方向で構成できるようになります。 つまり、継承しなくてもクラスのメンバーに追加できるようになります。

サンプルコード

<?php

trait Printer {

  /**
   * コンテンツを返す。
   * @return string コンテンツ文字列
   */
  abstract protected function getContent();

  /**
   * コンテンツを出力する。
   */
  public function print() {
    echo $this->getContent(), "\n";
  }
}

class Message {

  # Printer トレイトを Mix-in する
  use Printer;

  private $content;

  /**
   * コンストラクタ。
   * @param string $content コンテンツ文字列
   */
  public function __construct(string $content) {
    $this->content = $content;
  }

  /**
   * コンテンツを返す。
   * @return string コンテンツ文字列
   */
  protected function getContent() {
    return $this->content;
  }
}

# Message クラスは Printer トレイトを Mix-in しているので
# print メソッドが使えるようになる
$m = new Message('Hello, world');
$m->print();

動作確認環境と実行結果

$ php --version
PHP 7.3.7 (cli) (built: Jul  5 2019 12:44:05) ( NTS )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.3.7, Copyright (c) 1998-2018 Zend Technologies
    with Zend OPcache v7.3.7, Copyright (c) 1999-2018, by Zend Technologies

$ php mixin.php 
Hello, world

Java interface で Mixin を実現する

Java では interface の default method を利用して Mixin を実現できる。

進化するJavaインタフェース (Java Magazine Vol.37 JANUARY/FEBRUARY 2018)

デフォルト・メソッドは、メソッド本体、すなわちデフォルトの実装を持つインタフェースのメソッドです。デフォルト・ メソッドは、メソッドのシグネチャの先頭にdefault修飾子を付けて定義するもので、完全なメソッドの本体を持ち ます。

サンプルコード

interface Printer {

  /**
   * コンテンツを返す。
   * @return コンテンツ文字列
   */
  String getContent();

  /**
   * コンテンツを出力する。
   */
  default void print() {
    // クラス側で実装されている getContent メソッドから
    // コンテンツを取得して出力する
    System.out.println(getContent());
  }
}
// Printer インターフェースを Mix-in する
public class Message implements Printer {

  private String content;

  /**
   * コンストラクタ。
   * @param content コンテンツ文字列
   */
  public Message(String content) {
    this.content = content;
  }

  /**
   * コンテンツを返す。
   * @return コンテンツ文字列
   */
  @Override
  public String getContent() {
    return content;
  }

  public static void main(String[] args) {
    // Message クラスは Printer インターフェースを Mix-in しているので
    // print メソッドが使えるようになる
    Message m = new Message("Hello, world");
    m.print();
  }
}

動作確認環境と実行結果

$ java -version
openjdk version "11.0.2" 2019-01-15
OpenJDK Runtime Environment 18.9 (build 11.0.2+9)
OpenJDK 64-Bit Server VM 18.9 (build 11.0.2+9, mixed mode)

$ javac Printer.java Message.java 

$ java Message
Hello, world