本文へジャンプ

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む


お客様が developerWorks に初めてサインインすると、プロフィールが作成されます。プロフィールで選択した情報は公開されますが、いつでもその情報を編集できます。お客様の姓名(非表示設定にしていない限り)とディスプレイ・ネームは、投稿するコンテンツと一緒に表示されます。

送信されたすべての情報は安全です。

  • 閉じる [x]

developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む


送信されたすべての情報は安全です。

  • 閉じる [x]

Javaコードの診断: キラー・コンボ -- ミックスイン、Jam、単体テスト

クラスを親と分離し、コードの再利用とテストをより簡単にする

Eric Allen, Software Engineer, Cycorp, Inc.
Eric Allen氏は、コーネル大学でコンピューター・サイエンスと数学の学士号を取得しています。現在は、ライス大学の博士課程の大学院生としてJavaプログラミング言語チームに加わっています。学位を終了するためにライス大学に戻るまでは、Cycorp, IncでJavaソフトウェア開発主任として勤務していました。彼は、JavaWorldで「Java Beginner」ディスカッション・フォーラムの司会者も務めています。主な研究対象は、Java言語のセマンティック・モデルと静的分析ツールの開発であり、いずれもソース・レベルとバイトコード・レベルで研究しています。Ericは、NextGenというプログラミング言語 (汎用ランタイム型によるJava言語の拡張版) のためのライスのコンパイラーの開発にも携わってきました。連絡先は、eallen@cs.rice.edu です。

概要: Java言語では、単一継承プログラミングによる安全性の代償として、場合によっては、継承の複数の階層に渡ってコードをコピーしなければならないこともあります。単一継承によってJavaコードで失われた表現力の多くを取り戻すためのJava拡張として、ミックスインを取り入れることができます。今月は、Eric Allen氏が、ミックスイン (親クラスによってパラメーター化されたクラス) の概念と、それらが単体テストにもたらす利点について説明します。さらに、ミックスイン・ベース・プログラミング用のツールについて紹介し、ミックスインをJavaコードに追加する方法についても説明します。

日付:  2002年 12月 01日
レベル:  中級 この記事の原文:  英語
アクティビティー: 1511 ビュー
お気軽にご意見・ご感想をお寄せください: 


オブジェクト指向プログラミングが始まって以来、1つの基本的な問題がオブジェクト指向言語設計の悩みの種でした。ドメイン分析において開発する存在論は、複数の親から継承されるクラスを含む傾向があります。これは単に、実際のオブジェクトがシンプルな単一継承の階層に適合しないという理由によるものです。お気に入りのおいしいビールも、おなかを満たしてはくれないといったところでしょうか。一方で、プログラミング言語の多重継承を可能にすると、セマンティクスがひどく複雑なものになります。

この複雑さを言語に取り入れるとバグが増える可能性があるため、Java言語は、単一継承に固執したアプローチを採用してきました (セマンティクスが非常に単純なインターフェース継承を除く)。その結果、Javaプログラムの多くのクラス構造には、継承階層の複数の分岐に従ってコピーされたコードか、または、Chain of Responsibility、Command、あるいはStrategyのデザイン・パターンを使用した、間接アクセス用に追加されたコードが含まれます。

Commandパターン

は、特定の1つのモジュールにのみ要求を転送します。このパターンは、特定のアクションに対する要求を1つのオブジェクトに含ませて、そのオブジェクトに既知のパブリック・インターフェースを設定します。これによりクライアントは、実行される実際のアクションの具体的な内容を知らなくても要求を行えるようになり、また、クライアント・プログラムに何の影響も与えることなくそのアクションを変更できるようになります。

Chain of Responsibilityパターンは、他のクラスの処理能力を互いに意識することなく、1つの要求を複数のクラスで処理させることができます。このパターンでは、これらのクラス同士を1つの共通リンクで結合し、要求をこれらのクラス間で順に渡していきます。処理能力のあるクラスが見つかるまで、このリンクに従って要求が渡されていきます。

Strategyパターンは、Context と呼ばれるドライバー・クラスにカプセル化された複数の関連アルゴリズムで構成されます。クライアント・プログラムがこれらの異なるアルゴリズムの1つを選択するか、場合によっては、Context が最適なアルゴリズムを選択することもあります。これは、ハードコードされた条件文を使わずにアルゴリズムを簡単に切り替えることを目的としています。ユーザーは通常、適用する戦略を複数の中から選択します。Context クラス内では、一度に1つの戦略がインスタンス化されアクティブ化されることが多いようです。

たとえば、GUIライブラリーのスクロール可能なペインに関するUML分析図の次の例について考えてみましょう。


図1. 選択GUI要素の分析図
図1. 選択GUI要素の分析図

この図を直接Javaプログラミングのクラス階層に変換できれば理想的ですが、単一継承であるためできません。多重インターフェース継承を使えば、対応するインターフェースのセットを作成できますが、これらのインターフェースを実装するクラスは、直接Javaの継承構造に従えません。代わりに、継承階層の複数のパスに渡ってコードをコピーするか、コードのコピーを回避するには、Strategyパターン (または、包含を使用した他の何らかの方法) を使用しなければなりません。ただし、どちらのアプローチも完全ではありません。

両方の良いとこ取り

多重継承はエラーが発生しやすく、単一継承は制限的すぎるとしたら、双方の利点を提供し、Javaプログラミングに追加できる何らかの言語機能はないのでしょうか。実はあるのです。それは、ミックスインとして知られています。

ミックスインは、親クラスによってパラメーター化されたクラスです。または、クラスを新しいサブクラスへマッピングする関数とも考えることができます。ミックスインは、特定のコンテキストの要件ごとに、異なる親クラスでインスタンス化することができます。

たとえば、図1のScrollPaneのクラス階層は、次のようにミックスインを使用して実装することができるでしょう (矢印付きの破線は、ミックスインから親クラスへのインスタンス化の関係を表す)。


図2. ミックスイン継承図
図2. ミックスイン継承図

図2では、Scrollable クラスを、異なるコンテキストの異なるクラスを拡張できるミックスインに変えました。このコンテキストでは、Scrollable をインスタンス化してPane を拡張し、ScrollPane を作成します。また、Scrollable をインスタンス化してDialog を拡張したり、さまざまなコンテキストに応じて、あらゆる種類の他のGUIコンポーネントを拡張することができます。



ミックスインの簡単な歴史

ミックスインという言葉が初めて使用されたのは、CLOSコミュニティーでした。このコミュニティーでのミックスインとは、実は、この言語の多重継承の扱いにくさを制御するためのデザイン・パターンでした。ミックスイン・デザイン・パターンは、C++ コミュニティーでも、同じ目的で適用されました。

そのようなクラスを、さまざまな方法で他のクラスと一緒にミックスできるため、ミックスインという名前が使用されました。ミックスインは、これらの言語では単なるデザイン・パターンですが、ミックスインを言語レベルでサポートできない理由はありません。ミックスインをJava言語に追加するための多くの提案がありましたが、これまでのところ、最も人気のある提案は、Jamです。これは、イタリアの研究者である、Davide Ancona氏、Giovanni Lagorio氏、Elena Zucca氏が提案した、ミックスインによるJavaの拡張です。



ミックスイン、Javaコード、Jam

Jamは、Javaプラットフォーム、v1.0の下位互換性のある拡張です (mixininherited という2つの新しいキーワードを持つ)。.NETに対するJavaプログラムを書いているのでない限り、Javaのかなり古いバージョンが使用されている可能性があることは明らかですが、基本的な設計は、より現代的なバージョンに拡張することができるでしょう。

実装は、Jam - Java言語変換プログラムとして提供されます。jamc 実装は、プログラムの完全な型チェックを実行しないことに注意してください。代わりに、この実装は、Javaソースへの変換を行い、Java型チェッカーによって型エラーをキャッチします。これにより、Jam実装はよりシンプルになりますが、同時にコンパイラーから返されるエラー・メッセージの診断がより難しくなることを意味します。その理由は、実際に作成したソース・コードから1段階除かれたものに対するエラーメッセージになってしまうからです。結局は、独立したJamの型チェッカーが本番用に不可欠となるでしょう。

Jamでは、inherited <signature> のように、親クラスに必要なメソッドが、ミックスイン・クラスのdef 内部の宣言によって宣言されます。

ミックスインのインスタンス化は、class NAME = MIXIN extends CLASS {CONSTRUCTOR*}のように記述されます。

CONSTRUCTOR 作成の終わりの* は、その作成のオカレンスがないかまたはそれ以上のオカレンスがあることを示します。コンストラクターがミックスインのインスタンス化で指定されない場合は、デフォルトの0引数 (zeroary) コンストラクターが想定されます。

たとえば、UML図 (図2) に採用されるミックスインは、次のように作成できるでしょう (PanessetVisible() メソッドが含まれ、ミックスインScrollablemaxScrollSize フィールドが含まれる)。


リスト1. Jamでミックスインをインスタンス化する
                
class Pane {
  ...
    void setVisible(boolean value) {
    ...
  }
}
class DialogBox {
  ...
}
mixin Scrollable {
  int maxScrollSize;
  inherited void setVisible(boolean value);
}
class ScrollDialog = Scrollable extends DialogBox {
  ScrollDialog() {
    this.maxScrollSize = 10;
  }
}
class ScrollPane = Scrollable extends Pane {
  ScrollPane(int maxScrollSize) {
    this.maxScrollSize = maxScrollSize;
  }
}

Jamは、「ミックスインのコピーの原理」として知られている次の原則に従います。

親クラスP でミックスインM をインスタンス化することによって取得されるクラスは、M で定義されるすべてのコンポーネントのコピーを本体に含む、P の通常の継承者と同じ振る舞いを持つ。

ミックスインの概念は多くの言語に適用されていますが、Jamは、強く型付けされた言語のコンテキストで厳密にミックスイン・ベース・プログラミングを導入するという点で新しいものです。Jamのミックスインは、普通のクラスと同じように型を定義します。ミックスインのインスタンス化には、ミックスインの型と親の型の両方があります。ミックスインは、複数のインターフェースを実装することができます。

コンストラクターはミックスインでは宣言できませんが、ミックスインのインスタンス化に対してのみ宣言することができます。Jamステートの設計機能として、コンストラクターは、「その固有のクラスの実装と緊密に結合しているため、署名はあまり汎用的でない」という理由から、設計の選択肢として許容されませんでした。

この言語について注意すべきいくつかの一般的な性質があります。

  • フィールド・メンバーは、標準のJava言語と同じ規則によってアクセスできます。
  • 静的メンバーは、ミックスインをインスタンス化したものに対応しています。ミックスインの静的メンバーが「共有される」ことはありません。

さらに、Jamでは、ミックスインのインスタンス化に5つの制約があります。

  1. 不法オーバーライド / 隠蔽。 対応する「コピーされた」クラスが合法である (つまり、メソッドに、オーバーライドされた親クラスのメソッドと、同じ引数型で異なるreturn 型がある、異なるthrows 文節がある、もしくは、static対インスタンスのような互換性のない修飾子がある、といったことがない)場合は、親クラスのメソッドの偶然のオーバーライド (「偶発的な」オーバーライドとしても知られる) が許容されます。
  2. あいまいな多重定義(オーバーロード)。 メソッドの引数がミックスイン型である可能性があるため、あいまいな多重定義は問題となります。これにより多重定義された2つのメソッドが共に適用可能で、どちらがより適切であるか判断できないという状況がありえるからです。この問題は、2つのメソッドが同じ数の同じ型の引数を持っている場合に、多重定義を許容しないことによって解決されます。これらのメソッドで異なる参照型の引数を持つ場合は除きます。
  3. メソッドの注釈。 継承されたメソッドには、「親の」型の注釈が付きます。
  4. クラスのみのインスタンス化。 Jamミックスインは、クラスでのみインスタンス化することができます。Jiazziのコンポーネントと異なり、ミックスインには合成という概念はありません (ただしJamチームは、そのような拡張について検討してみたいと考えています)。Jiazziおよびコンポーネント・ベース・プログラミングの詳細については、参考文献を参照してください。
  5. 「this」を渡さない。 これは、Jamのうまいところだと思うのですが、ミックスイン内部から、メソッドまたはコンストラクターの引数として「this」を渡すことが禁止されています!Jamのこのような特質はタイプ・システムの健全性を保護するのに必要です。これがないと、Jamミックスイン型があらゆるインスタンス化に有効であることを保障する方法がありません。しかしながら、これは、ミックスインになる資格のあるクラス・セットを制限してしまうという点で、非常に残念な制限ではあります。

内部: 実装について簡単に見る

Java言語に変換するために、Jamミックスイン型はインターフェースとして表され、インスタンス化(すべてコンパイル時に静的に解決されます)によって実装されます。ミックスインに導入されたフィールドを処理するために、ゲッター/セッターメソッドのM_$get$_fM_$set$_f が、インターフェースに導入されます。次に、f が、すべてのインスタンス化したクラスのフィールドとして宣言され、メソッドが適切に実装されます (また、外部コードからの静的型M の式のすべてのフィールド・アクセスが、ゲッター/セッターへの呼び出しに変換されます)。ミックスインの静的フィールドは、ミックスインのインスタンス化したクラスの間で共有されず、したがって、単に各インスタンス化に別々に挿入されます。

ミックスインの各インスタンス化は、個別のJavaクラスにコンパイルされます。バイトコードの共有はコピー間で発生しません。また、ミックスインの親に対してもインターフェースが作成されます。この親インターフェースは、(親クラスのインスタンス化によってではなく) ミックスインのインターフェースによって拡張されます。


ミックスインと単体テスト

ミックスインは、落とし穴がまったくない、言語の多重継承の力を回復する方法として、プログラマーに一般的に使用されています。しかし、(GUI要素やRMIプロキシー・クラスのように) 親クラスを直接にテストすることが困難であるような場合は特に、ミックスインが、既存のクラスの新しい拡張をテストする強力な方法を提供するという重要な点にも注目すべきです。

実際に、Jiazziが、インポートするパッケージから独立してパッケージをテストする方法を提供するように、Jam (または、Java言語の他のミックスイン・ベースの拡張) によって、親が同じパッケージ内に存在している場合でも、その親から独立してクラスをテストすることができます。リスト3の例に従うと、スーパー・メソッドのすべての呼び出しを単に記録する、親クラスのRecorderによって、ミックスインをインスタンス化できるようになります。


リスト2. インスタンス化から独立してミックスインをテストする
                
class TestLog {
  private StringBuffer recording = new StringBuffer("");
  public void record(String message) {
    recording.append(message);
  }
  public String toString() {
    return recording.toString();
  }
}
class WidgetRecorder {
  public TestLog testLog;
  public void setVisible(boolean value) {
    testLog.record("setVisible(" + value + "); ");
  }
}
class ScrollableWidgetRecorder = Scrollable extends WidgetRecorder {
  public TestScrollable() {
    this.maxScrollSize = 10;
  }
}

こうして、次のように、呼び出しの期待されるシーケンスに対してこのログをチェックできるようになります。


リスト3. ミックスインのためのJUnit TestCases
                
import junit.framework.*;
public class ScrollableTest extends TestCase {
  public ScrollableTest(String name) {super(name);}
  public void testSetVisible() {
    ScrollableWidgetRecorder test = new ScrollableWidgetRecorder();
    test.initialize();
    assertEquals("Scrollable initialization should've called setVisible(true)",
                 "setVisible(true); ",
                 test.testLog.toString())
  }
  ...
}

このように、親クラスのロケーションに関係なく、それだけではテストが困難なクラスをテスト用に拡張できるようになります。テストが困難である中心的な機能性を親クラスの小さなセットに分離できるようになり、また、それに依存する機能性が、十分にテストされたミックスイン・クラスに依存できるようになります。



ミックスインと汎用型に関する最後の言葉

最後に、少なくとも簡単に、Javaに汎用型を追加するJSR-14提案にミックスインがどのように関連するかについて説明しなければ、Javaプログラミングのミックスインの説明としては怠慢というものでしょう。

汎用型を使用すると、参照する型によってクラスをパラメーター化できるようになるため、Java言語の汎用型に対する本当に優れたサポートは、必然的に、ミックスインの形式をサポートするものでなければなりません。そうすれば、クラスを定義して、型変数を拡張できるようになります。

しかし残念ながら、汎用型は静的コンパイルの間に消去されるので、SunのJSR-14プロトタイプ・コンパイラーによって使用されるアプローチは、そのような(実行系によって直接サポートされる)ファースト・クラスの言語要素を導入する拡張が不可能になっており、汎用型の情報は、実行時には存在さえしません。ミックスインの場合は、ミックスインの親クラスが、型変数の共通親クラスにまで消去されてしまいますが、これは明らかに望ましくありません。

対照的に、NextGen方式による汎用型 (2002年12月にRice JavaPLTからベータ・リリースの予定) は、汎用型の情報を実行時に利用できるようにします。したがって、それは、ミックスインなどの優れた汎用型をサポートするよう拡張できるでしょう。実際、最初のベータ・リリース後すぐの拡張は、正にそのような機能性が含まれるでしょう。拡張言語の設計については、参考文献を参照してください。

この記事と前回の記事で説明したように、今日存在するJava言語は、言語設計の最終段階にあるものではありません。特に、テスト・ファースト・プログラミングのスタイルを採用する場合はなおさらです。より迅速に、また、より完全にプログラムをテストすることができる、強力で自然な言語拡張が数多くあります。

しかし、前回と今回の2つの記事で、Java言語が可能とする非常に大きな柔軟性と拡張性も示せたのであればうれしいです。このような拡張性は、言語とJVM設計の安全と移植性に関する直接的な結果です。元の設計者の将来への見通しのおかげで、Java言語は、これからも長い間、非常に強力で適切な言語として、ますます複雑なアプリケーションを作成していくプログラマーを支えつづけるでしょう。


参考文献

著者について

Eric Allen氏は、コーネル大学でコンピューター・サイエンスと数学の学士号を取得しています。現在は、ライス大学の博士課程の大学院生としてJavaプログラミング言語チームに加わっています。学位を終了するためにライス大学に戻るまでは、Cycorp, IncでJavaソフトウェア開発主任として勤務していました。彼は、JavaWorldで「Java Beginner」ディスカッション・フォーラムの司会者も務めています。主な研究対象は、Java言語のセマンティック・モデルと静的分析ツールの開発であり、いずれもソース・レベルとバイトコード・レベルで研究しています。Ericは、NextGenというプログラミング言語 (汎用ランタイム型によるJava言語の拡張版) のためのライスのコンパイラーの開発にも携わってきました。連絡先は、eallen@cs.rice.edu です。

不正使用の報告のヘルプ

不正使用の報告

ありがとうございます。 このエントリーは、モデレーターの注目フラグが設定されました。


不正使用の報告のヘルプ

不正使用の報告

不正使用の報告の送信に失敗しました。


developerWorks: サイン・イン


IBM ID が必要ですか?
IBM IDをお忘れですか?


パスワードをお忘れですか?
パスワードの変更

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 利用条件

 


お客様が developerWorks に初めてサインインすると、プロフィールが作成されます。 プロフィールで選択した情報は公開されますが、いつでもその情報を編集できます。 お客様の姓名(非表示設定にしていない限り)とディスプレイ・ネームは、投稿するコンテンツと一緒に表示されます。

表示名をお選びください

developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

(半角英数字で3文字以上31文字以下にする必要があります)


「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 利用条件

 


この記事を評価する

コメント

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Java technology
ArticleID=219626
ArticleTitle=Javaコードの診断: キラー・コンボ -- ミックスイン、Jam、単体テスト
publish-date=12012002
author1-email=
author1-email-cc=

タグ

Help
このタグで、My developerWorks のすべてのタイプのコンテンツを見つけるために検索フィールドを使用します。

スライダーバーを使用することで、より多く(少なく)タグを表示します。

人気のタグは、この特定のコンテンツ・ゾーン(例えば、Java テクノロジー、Linux や WebSphere など)に対するトップのタグを表示します。

マイ・タグは、この特定のコンテンツ・ゾーン(例えば、Java テクノロジー、Linux や WebSphere など)に対するお客様ご自身のタグを表示します。

このタグで、My developerWorks のすべてのタイプのコンテンツを見つけるために検索フィールドを使用します。人気のタグは、この特定のコンテンツ・ゾーン(例えば、Java テクノロジー、Linux や WebSphere など)に対するトップのタグを表示します。マイ・タグは、この特定のコンテンツ・ゾーン(例えば、Java テクノロジー、Linux や WebSphere など)に対するお客様ご自身のタグを表示します。