2006年01月17日

イミュータブル 補足

イミュータブルでないRegionオブジェクトで値の設定メソッドを属性値毎に用意するとどうなるでしょう。

public class Region implements Cloneable {
public Region(double x, double y, double width, double height) {
x_ = x;
y_ = y;
width_ = width;
height_ = height;
}

public final synchronized double getX() {
return (x_);
}

public final synchronized double getY() {
return (y_);
}

public final synchronized double getWidth() {
return (width_);
}

public final synchronized double getHeight() {
return (height_);
}

public final synchronized void setX(double x) {
x_ = x;
}

public final synchronized void setY(double y) {
y_ = y;
}

public final synchronized void setWidth(double width) {
width_ = width;
}

public final synchronized void getHeight(double height) {
height_ = height;
}

private double x_;
private double y_;
private double width_;
private double height_;
}

複数の属性(この場合は4つ)を同時に変更する場合、相互排除を考慮すると以下のようにする必要があります。
マルチスレッド環境では属性の値を変更するのもひと苦労。

Region myRegion = new Region(0, 0, 10, 10);
synchronized (myRegion) {
myRegion.setX(20);
myRegion.setY(50);
myRegion.setWidth(100);
myRegion.setHeight(70);
}
posted by シンビアン at 20:11| Comment(0) | TrackBack(1) | Symbian OS C++ 実践開発技法 | このブログの読者になる | 更新情報をチェックする

イミュータブルでない場合

次にイミュータブルでないバージョンは次のようになります。
setValuesというメソッドを定義したことにより、オブジェクトの値は、このオブジェクトを参照しているかもしれない複数のオブジェクトの中の一つでもその意思を持てば変更できるようになりました。
このため各メソッドでsynchronizedによる相互排除が必要になっています。
synchronizedによる相互排除は即、性能劣化の要因となります。

public class Region implements Cloneable {
public Region(double x, double y, double width, double height) {
x_ = x;
y_ = y;
width_ = width;
height_ = height;
}

public final synchronized double getX() {
return (x_);
}

public final synchronized double getY() {
return (y_);
}

public final synchronized double getWidth() {
return (width_);
}

public final synchronized double getHeight() {
return (height_);
}

public final synchronized void setValues(
double x, double y, double width, double height
) {
x_ = x;
y_ = y;
width_ = width;
height_ = height;
}

private double x_;
private double y_;
private double width_;
private double height_;
}

さらに影響があるのがオブジェクトを使う時。

public class Figure {
public Figure(Region region) {
region_ = region.clone();
}

public Region getRegion() {
return (region_.clone());
}

private Region region_;
}


Regionオブジェクトのクローンをしまくっています。
これは以下のような使われ方をしても誤動作しないため。イミュータブルにしないということは、非常にコストのかかることなのです。

Region myRegion = new Region(0, 0, 10, 10);
Figure figure = new Figure(myRegion);
myRegion.setValue(100, 100, 10, 10); //★もしcloneしていないとFigureのRegion値が変ってしまう
Region anothorRegion = figure.getRegion();
anotherRegion.setValue(10, 50, 200, 100); //★もしcloneしていないとFigureのRegion値が変ってしまう
posted by シンビアン at 20:06| Comment(0) | TrackBack(0) | Symbian OS C++ 実践開発技法 | このブログの読者になる | 更新情報をチェックする

イミュータブルオブジェクトの実装

イミュータブルオブジェクトの例としてX/Y座標の中の範囲を表すRegionを考えてみます。
イミュータブルオブジェクトは以下の方針で実装します。

* 属性値はコンストラクタでのみ設定
* 属性値の参照メソッドを実装
* 属性値の更新メソッドは実装しない
* 属性値を格納するオブジェクト変数はpublicにしない

public class Region {
public Region(double x, double y, double width, double height) {
x_ = x;
y_ = y;
width_ = width;
height_ = height;
}

public final double getX() {
return (x_);
}

public final double getY() {
return (y_);
}

public final double getWidth() {
return (width_);
}

public final double getHeight() {
return (height_);
}

private double x_;
private double y_;
private double width_;
private double height_;
}

イミュータブルオブジェクトの使用例として、Regionを属性値として持つオブジェクトFigureを考えてみます。

public class Figure {
public Figure(Region region) {
region_ = region;
}

public Region getRegion() {
return (region_);
}

private Region region_;
}

一見ごく普通の実装に見えますね。
しかしイミュータブルでない場合と比較すると違いは明らかになります。
posted by シンビアン at 20:03| Comment(0) | TrackBack(0) | Symbian OS C++ 実践開発技法 | このブログの読者になる | 更新情報をチェックする

イミュータブル

イミュータブル(Immutable、不変)というオブジェクトの性質はオブジェクト指向プログラミングにおいて非常に大きな意味合いを持っています。
複製可能である性質、整列化可能である性質はjava.lang.Cloneableや java.io.Serializableというインタフェースによって言語仕様の表層に出ていますが、イミュータブルという性質は言語仕様では直接サポートはされない設計上のコツといったもので、直接取り上げられる機会は少ないといえます。しかしその重要度は決して低いものではありません。

不変という日本語訳からも分かる通り、イミュータブルはオブジェクトの生成時に設定された属性を変更することができないオブジェクトの性質です。
一見、属性を後から変更することができない性質はデメリットであるようにしかみえません。

class Figure {
....

public void move(double x, double y) {
region_.x = x;
region_.y = y;
}

Region region_;
}

としたいところを

class Figure {
....

public void move(double x, double y) {
region_ = new Region(x + region_.getX(), y + region_.getY());
}

Region region_;
}

としなければならないのはあまりにもムダと思えるものです。

しかしイミュータブルにはこの欠点を補ってあまりあるメリットがあるのです。
オブジェクトをイミュータブルとすることのメリットは以下の2つ。

* オブジェクトの所属を気にしなくて良い。
* オブジェクトの相互排除を気にしなくて良い。

つまりオブジェクト指向プログラミングで取り扱わなければならない2つの大きな問題をまったく気にする必要がなくなるのです。プログラミングの負荷の軽減に大きく貢献します。
またこれはプログラミング上の都合だけでなく性能にも影響します。オブジェクトの所属を気にする必要がある場合にはオブジェクトの複製を生成する可能性が高くなり、その処理の分、性能にインパクトがあります。もちろんメモリ資源も余分に必要となりガベージコレクションの発生、ワーキングセットの増大といった2次的な要因も無視できません。

さらにオブジェクトの相互排除ではsynchronizedによるモニタ機能を使用する必要がありますが、この機能を使用することは相応の性能劣化が発生します。
イミュータブルにできるクラスはできるだけイミュータブルとして設計する。これはJavaのプログラミングテクニックとして非常に重要であるといえます。

Javaではjava.lang.Stringがイミュータブルの典型例です。 java.lang.Integerやjava.lang.Floatなどのラッパクラスや、java.awt.Color といったところもイミュータブル。
イミュータブルはすでにJavaのプログラミングテクニックとして広く利用されていることが分かります。
posted by シンビアン at 20:00| Comment(0) | TrackBack(0) | Symbian OS C++ 実践開発技法 | このブログの読者になる | 更新情報をチェックする

コピーコンストラクタ

コピーコンストラクタはC++で良く使われる手法で、オペレータオーバローディングを言語仕様でサポートしているC++ではクラス設計の時の基本要素となっています。
そして、JavaでもC++ほどではないにしても、クラス設計時に意識しておくべき機能であるといえます。

イミュータブルでないカージナルオブジェクトやエンティティオブジェクトを使用する場合、頻繁にオブジェクトのコピーを行うことが必要になります。 (「イミュータブル」参照)
このようなコピーの生成を行うための機能としてjava.lang.Objectにはclone メソッドが定義されており、java.lang.Clonableをインプリメントしたクラスのオブジェクトはこのcloneメソッドで複製を作成できるようになっています。

Foo fooOrg;
...
Foo fooNew = (Foo)fooOrg.clone();

これがJavaでデータの複製を作成するための一般的な方法なのですが、今一つ美しくありません。ちわざわざキャストする必要があるのも悔しいところです。
そこで次のように新しいオブジェクトが生成できるようなオブジェクトを考えてみましょう。

Foo fooOrg;
...
Foo fooNew = new Foo(fooOrg);

何でもない機能のようですが、実際のコーディングがずいぶん楽になります。

このクラスは次のように定義できます。
自クラスを引数にしたコンストラクタを用意するのがポイントです。
このコンストラクタがコピーコンストラクタです。

public class Foo {
public Foo(Foo foo) {
name_ = foo.name_;
}

private String name_;
}

ここまで定義するのあればついでにcopyメソッドを定義しておくと便利です。

public class Foo {
public Foo(Foo foo) {
copy(foo);
}

public void copy(Foo foo) {
name_ = foo.name_;
}

private String name_;
}

のようにしておくことで以下のようなコーディングでエンティティオブジェクトの値の設定が可能になるわけです。

Foo fooOrg = new Foo();
Foo fooNew = new Foo();
...
fooNew.copy(fooOrg);

コピーコンストラクタは便利な機能なのですが、インヘリタンスが発生する場合いわゆるスライシングと呼ばれる副作用が発生する可能性があります。(スライシングについては別途説明する予定です)
スライシングを避けるためにはcloneメソッドを使用することが必要で、結局はこの基本機能に戻ってくることになるともいえます。しかし、コピーコンストラクタを用意することで、プログラミングが楽になるのは確かで、カージナルオブジェクトやエンティティオブジェクトではできる限り用意しておくようにしておくのが得策です。

スライシングが絶対に発生しないことを保証するためにはクラスをfinalにしておく方法があります。カージナルオブジェクトやエンティティオブジェクトの目的からすると、この手法は十分に選択可能であるといえるでしょう。

public final class Foo {
public Foo(Foo foo) {
copy(foo);
}

public void copy(Foo foo) {
name_ = foo.name_;
}

private String name_;
}

posted by シンビアン at 19:53| Comment(0) | TrackBack(1) | Symbian OS C++ 実践開発技法 | このブログの読者になる | 更新情報をチェックする

広告


この広告は60日以上更新がないブログに表示がされております。

以下のいずれかの方法で非表示にすることが可能です。

・記事の投稿、編集をおこなう
・マイブログの【設定】 > 【広告設定】 より、「60日間更新が無い場合」 の 「広告を表示しない」にチェックを入れて保存する。


×

この広告は1年以上新しい記事の投稿がないブログに表示されております。