分散オブジェクト環境を学ぶ

連載:HORBと遊ぼう(3)

オブジェクト指向にシームレスな
分散オブジェクト環境

萩本順三
HORB Openマネージャ
株式会社豆蔵
2001/1/13

 (1) HORBにおける継承とは

 「オブジェクト指向言語にシームレス」であることを一言でいうと「オブジェクト指向言語に一体化された技術(または環境)のように見える」ということでしょうか?

 でもこれは難しいことです。そもそもオブジェクト指向って、何をもってオブジェクト指向かという定義もはっきりしないのですから(笑)。しかし対象の技術がオブジェクト指向をサポートしているというならば、次のような機能・メカニズムをサポートしているに違いありません。それ以外の技術は概念的にオブジェクト指向という考えをサポートしているんだと考える方が、頭がスッキリしますね。

(1)カプセル化(データ隠ぺい)
(2)クラス、インスタンス
(3)メッセージ通信(メソッド呼び出し)
(4)継承
(5)ポルモルフィズム(多態性)

 前回までで、クラスでカプセル化されたリモート・オブジェクトを代理オブジェクト(Proxy)を通じて生成(new)し、メッセージ呼び出しをしました。よって、HORBは、(1)、(2)、(3)をサポートしているといえます。

 では、残りの(4)継承と、(5)ポルモルフィズムはどうなっているのでしょうか?

 もちろんHORBは両方サポートしています。だからオブジェクト指向言語にシームレスといわれるわけですが、その効力としてJava言語を理解できればHORBが超簡単に使えるようになるわけです。

 HORBが簡単であるワケは、実はHORBは、この「オブジェクト指向言語にシームレス」な分散オブジェクト環境を追求してきたからこそなんです。

 では、さっそくHORBがどんなふうに継承をサポートしているかについて、簡単なサンプルを使って見ていきます。その前に、まずはいつものようにスタンドアロンのJavaプログラミングから解説しましょう。

■クラスの継承利用

 リスト1は、前回使用したTest2クラスです。リスト2(Test2Debug)は、Test2から継承して作成したクラスです。このクラスは、Test2を実行時に生成(new)するタイミングや、消去されるタイミングを観察するメソッドをTest2に追加することを目的としています。

 今回のソースはここ(examples3.zip)からダウンロードしてください。

public class Test2{
  private String name;
  public void setName(String name){
    this.name = name;
  }
  public void print(){
    System.out.println("私の名前は "+name+ "です。");
  }
}
リスト1 Test2.java

public class Test2Debug extends Test2{
  private static int count;
  private int instanceNo;
  Test2Debug(){
    instanceNo = ++count;
    System.out.println("["+instanceNo+
    "]個目のインスタンスがnewされます");
  }
  public void finalize(){
    System.out.println("インスタンス["
    +instanceNo+"]が消去されます");
  }
}
リスト2 Test2Debug.java

 Test2から継承したTest2Debugには、継承を使ってTest2の属性(String name)とメソッド(setName、print)を引き継いでいます。よって、Test2Debugは、Test2のように使えます。さらに、Test2Debugは、コンストラクタ・メソッドとファナライザ・メソッド(finalize)を追加しています。

 図1は、Test2クラスとTest2Debugクラスをクラス図というオブジェクト指向開発で使用する表記法で書いたものです。それぞれのクラスはそれぞれのクラスとして表現されています。

図1 Test2とTest2Debug(クラス図)

 図2は、Test2インスタンスとTest2Debugインスタンスの内部構造をイメージした図です。この図から、Test2DebugはTest2の構造(メソッドと属性)を包含していることが分かるでしょう。

 

図1 Test2とTest2Debug(インスタンスの内部構造)

 コンストラクタとは、クラス名と同じ名前を持つメソッド(Test2Debug)で、Test2Debugが生成(new)されるときに自動的に呼び出されます。つまりオブジェクトが生成されるときに必要となる初期処理をコンストラクタとして定義することができます。一方、ファナライザは、実行中に、どこからも参照されなくなったオブジェクトが、Javaのガーベッジ・コレクションによってメモリから解放される際に自動的に呼ばれるメソッドのことで、ソース中のように戻り値と引数がないメソッド(finalize)として定義します。

 Test2Debugは、両者のメソッドを実装することで、オブジェクトの生成と消滅のタイミングを標準出力に表示する機能を持ったTest2の拡張クラスとなります。

 Test2Debugを使ったJavaのスタンドアロンプログラムLocalInstanceTestをリスト3に示します。このソースの説明は、プログラムを動かした後の実行結果を見ながら行います。

001:public class LocalInstanceTest{
002:  public static void main( String argv[] )throws Exception {
003:    Test2[] test = new Test2[5000];
004:
005:    System.out.println("---<Test2Debugをnewします>---");
006:    for(int i=0;i<test.length;i++)
007:      test[i] = new Test2Debug();
008:    Thread.sleep(2000);
009:
010:    System.out.println("---<メッセージを呼び出します>---");
011:    for(int i=0;i<test.length;i++){
012:      test[i].setName("LocalInstanceTestの"+i);
013:      test[i].print();
014:    }
015:    Thread.sleep(2000);
016:
017:    System.out.println("---<Test2Debugをnewします[2回目]>---");
018:    for(int i=0;i<test.length;i++)
019:      test[i] = new Test2Debug();
020:    Thread.sleep(2000);
021:
022:    System.out.println("---<メッセージを呼び出します[2回目]>---");
023:    for(int i=0;i<test.length;i++){
024:      test[i].setName("LocalInstanceTestの"+i);
025:    test[i].print();
026:    }
027:  }
028:}
リスト3 LocalInstanceTest.java

○コンパイル

>javac Test2.java Test2Debug.java LocalInstanceTest.java

○実行結果

 LocalInstanceTestの実行結果は次のようになります。では、この実行結果をLocalInstanceTestのソース(リスト3)と照らし合わせながら見ていきましょう。

 まず、リスト3の3行目で、Test2型の5000個の配列を用意し、7行目でTest2Debugクラスのインスタンスを生成(new)しています。これを実行すると、インスタンス生成の際にコンストラクタ・メソッドが呼び出され、「[n]個目のインスタンスがnewされます」と5000個分が標準出力に表示されます(結果A)。

 次に、12、13行目で、配列に入ったそれぞれのオブジェクトにメソッドを送ります。この2つのメソッドはTest2から継承されたものです(結果B)。

 その後、19行目で、Test2Debugクラスのインスタンスを生成(new)し、同じ配列(test)に代入しています。これによって、Test2Debugのコンストラクタの出力結果が表示されます(結果C)。

 この出力後、実行結果をよく観察していると、ある時点から「インスタンス[n]が消去されます」と表示されるようになると思います(結果D)。これは、21行目で生成されたインスタンスをtest配列のi番目に代入したため、前にその配列のi番目から参照されていたTest2Debugインスタンスがどこからも参照されないゴミとして登録され、JVMガーベッジ・コレクションのスレッドによってゴミとなったインスタンスがメモリから解放される契機でfinalizeメソッドが呼ばれているのです。どの段階で、インスタンスが生成され、どんなときにインスタンスが解放されるのか、この実行結果によって分かると思います(注1)。

 また、Test2Debugクラスのように、継承を使えば、あるクラスを拡張することができるということもお分かりになったでしょう。

注1:JDKの環境によっては、本サンプルでfinalizeメソッドが呼ばれないケースもあるかもしれません。そのときには、リスト3の3行目の配列個数を増やしてみてください。例えば1万件といった数字にしてください。

>java LocalInstanceTest
---<Test2Debugをnewします>---
[1]個目のインスタンスがnewされます …………… 結果A
[2]個目のインスタンスがnewされます
[3]個目のインスタンスがnewされます
(省略)
[5000]個目のインスタンスがnewされます
---<メッセージを呼び出します>---
私の名前は LocalInstanceTestの1です。 ……… 結果B
私の名前は LocalInstanceTestの2です。
私の名前は LocalInstanceTestの3です。
(省略)
私の名前は LocalInstanceTestの5000です。
---<Test2Debugをnewします[2回目]>---
[1]個目のインスタンスがnewされます …………… 結果C
[2]個目のインスタンスがnewされます
[3]個目のインスタンスがnewされます
(省略)
[6391]個目のインスタンスがnewされます
インスタンス[233]が消去されます ……………… 結果D
[6392]個目のインスタンスがnewされます
インスタンス[234]が消去されます
インスタンス[235]が消去されます
インスタンス[236]が消去されます
[6393]個目のインスタンスがnewされます
インスタンス[237]が消去されます
[6394]個目のインスタンスがnewされます
(省略)
---<メッセージを呼び出します[2回目]>---
私の名前は LocalInstanceTestの1です。
私の名前は LocalInstanceTestの2です。
私の名前は LocalInstanceTestの3です。
(省略)
私の名前は LocalInstanceTestの636です。
インスタンス[4674]が消去されます
私の名前は LocalInstanceTestの637です。
インスタンス[4675]が消去されます
私の名前は LocalInstanceTestの638です。
インスタンス[4676]が消去されます

■HORBリモート・クラスの継承利用

 HORBは、クラス継承が可能です。これはHORB特有の機能です。では、HORBでは、どのようにして継承を使うのでしょうか。

 やり方は簡単です。Test2.java(リスト1)とTest2Debug.java(リスト2)は、そのままで結構です。LocalInstanceTestをRemoteInstanceTest(リスト4)と差し替えてください。

 LocalInstanceTestからRemoteInstanceTestへの変更点は、8行目と20行目で生成(new)するオブジェクトのクラス名をTest2DebugからTest2Debug_Proxyに変更した部分となります。

 こうすることで、サーバ側に存在するTest2Debugを、あたかもクライアントに存在しているかのように操作できます。

001:public class RemoteInstanceTest{
002:   public static void main( String argv[] )throws Exception {
003:     String host = (argv.length == 1) ? argv[0] : "localhost";
004:     Test2_Proxy[] test = new Test2_Proxy[5000];
005:
006:     System.out.println("---<Test2Debugをnewします>---");
007:     for(int i=0;i<test.length;i++)
008:       test[i] = new Test2Debug_Proxy("horb://"+host);
009:     Thread.sleep(2000);
010:
011:     System.out.println("---<メッセージを呼び出します>---");
012:     for(int i=0;i<test.length;i++){
013:       test[i].setName("LocalInstanceTestの"+i);
014:     test[i].print();
015:     }
016:     Thread.sleep(2000);
017:     System.out.println("---<Test2Debugをnewします[2回目]>---");
018:
019:     for(int i=0;i<test.length;i++)
020:       test[i] = new Test2Debug_Proxy("horb://"+host);
021:     Thread.sleep(2000);
022:     System.out.println("---<メッセージを呼び出します[2回目]>---");
023:     for(int i=0;i<test.length;i++){
024:       test[i].setName("LocalInstanceTestの"+i);
025:     test[i].print();
026:     }
027:   }
028: }
リスト4 RemoteInstanceTest.java

○コンパイル

 Test2.javaとTest2Debug.javaをhorbcによってコンパイルします。例によってワーニングが出ますが、本例には関係ないことですので無視してください。

 horbcの後、RemoteInstanceTest.javaをjavacによってコンパイルします。

>horbc -delete Test2.java Test2Debug.java
compiling Test2.java
generating Test2_Proxy.java
Warning: Test2: private variable 'name' will not be copied when this object is t
ransfered by Proxy.
compiling Test2_Proxy.java
generating Test2_Skeleton.java
compiling Test2_Skeleton.java
compiling Test2Debug.java
generating Test2Debug_Proxy.java
Warning: Test2Debug: finalize() is renamed to _finalize().
Warning: Test2Debug: private variable 'count' will not be copied when this objec
t is transfered by Proxy.
Warning: Test2Debug: private variable 'instanceNo' will not be copied when this
object is transfered by Proxy.
compiling Test2Debug_Proxy.java
generating Test2Debug_Skeleton.java
compiling Test2Debug_Skeleton.java


>javac RemoteInstanceTest.java

○実行方法

 まず、HORBサーバを立ち上げましょう。DOSコマンドプロンプトを開いて、horbを立ち上げてください。

>horb

  次は、クライアントの起動です。もう1つDOSコマンドプロンプトを開いて、次のように入力してください。もし、HORBサーバと異なるマシンでクライアントを立ち上げるならば、「localhost」の部分に、HORBサーバを立ち上げたマシンのホスト名を指定してください。

>java RemoteInstanceTest localhost

○実行結果

 実行結果は、クライアントとHORBサーバの両方に出力されます。以下は、実行中の出力結果の一部を示したものです。この実行結果をじっくり観察してください。

 クライアントで生成された代理オブジェクトTest2Debug_Proxyは、サーバ側に存在するTest2Debugの代理のオブジェクトとして完全に動作しています。これはまさに影武者のように完璧にTest2Debugを演じているといえませんか?

 なぜなら、Test2Debug_Proxyは、Test2Debugの代理を演じる中で、次のようなことを実現しているからです。

  • 継承を実現している
    「Test2DebugのスーパークラスはTest2である」という実クラスの継承関係を「Test2Debug_ProxyのスーパークラスはTest2_Proxyである」という代理オブジェクトのクラス関係として忠実に再現している(図3参照
図3 オブジェクト(AddressBook)転送のイメージ図

  • ポルモルフィズムを実現している
    Test2Debug_ProxyクラスのインスタンスをTest2_Proxy変数に入れて操作ができる。これは、Test2DebugのインスタンスをTest2変数に入れて操作するということをリモート環境にて再現しているといえる

  • オブジェクトの生成と消滅タイミング
    代理オブジェクト(Test2Debug_Proxy)を生成(new)すると、実オブジェクトTest2Debugがサーバ側で生成(new)される。代理オブジェクト(Test2Debug_Proxy)の参照がなくなるとGC(ガーベッジ・コレクション)の対象となる。この時点で、サーバ側でも実オブジェクトがGCの対象となる

 これらが示すことは、代理オブジェクトの実行時のライフサイクル(オブジェクトが生まれて消滅するまで)が実オブジェクトのライフサイクルと同等であるということです(若干の時間的なタイミングのずれはあります)。

[クライアント]
>java RemoteInstanceTest localhost
---<Test2Debugをnewします>---
---<メッセージを呼び出します>---
---<Test2Debugをnewします[2回目]>---

[HORBサーバ]
>horb
(省略)
[7354]個目のインスタンスがnewされます
インスタンス[1787]が消去されます
[7355]個目のインスタンスがnewされます
インスタンス[1788]が消去されます
[7356]個目のインスタンスがnewされます
インスタンス[1789]が消去されます
[7357]個目のインスタンスがnewされます

 いかがですか、HORBの分散環境によって、継承を使った例を示しました。このほか、HORBは、インターフェイスの継承や、インターフェイス同士の継承などについてもサポートしています。

 HORBの継承って結構いけそうでしょ。これがあるから、分散環境にて、クラス継承を使いたいと思ったときに簡単に使えてしまうので非常に便利なのです。

(2) HORBでオブジェクトが飛ぶ


Index

第3回 オブジェクト指向にシームレスな分散オブジェクト環境

(1) HORBにおける継承とは
 クラスの継承利用
 HORBリモート・クラスの継承利用
  (2) HORBでオブジェクトが飛ぶ
 オブジェクトがネットワーク間を渡り歩くとは?
 

連載記事一覧



Java Agile フォーラム 新着記事
@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)

注目のテーマ

Java Agile 記事ランキング

本日 月間