ぼやきごと/2011-05-08/Clone メソッドのスマートな実装方法 のバックアップソース(No.1)

#blog2navi()
*Clone メソッドのスマートな実装方法 [#x9809e4b]

C#…というか.NET Frameworkには @code{ICloneable}; というインタフェースが定義されています。~
@code{Clone}; という自身の完全コピーなインスタンスを生成するメソッドを提供する、実装されていれば便利なインタフェースなのですが、一つ問題があります。

それは、クラスを継承する際の実装コストが高くなることです。~
@code{Clone}; メソッドを持つクラスを継承することを考えればわかりますが、継承先のクラスでは @code{Clone}; メソッドを''必ず''オーバライドする必要があります(継承元で何らかの仕込みが行われている場合を除く)。~
なぜなら @code{Clone}; メソッドはそのままではベースクラスのインスタンスを返してしまうからです。

なので @code{ICloneable}; インタフェースもしくはそれ相当の機能を実装する場合、 @code{sealed}; なクラスでない限りは派生先のことを考慮した実装にする必要があります。~
最も無難ですぐに思い付くのは派生クラスから利用可能なコピーコンストラクタを用意することですが、他にも色々と方法があります。~
.NET Framework系言語で利用可能な方法については次のサイトでいくつか挙げられています。

-[[継承を考慮したICloneableの実装(C#) - reflux flow>http://refluxflow.net/2011/02/csharp-icloneable.html]]

ただ、上のサイトで挙げられている方法は、コピーコンストラクタを利用するものを除くと.NET Framework系言語以外では利用可能な言語が限られます。~
シリアライズはJavaや [[@code{boost::serialization};>http://www.kmonos.net/alang/boost/classes/serialization.html]] を導入したC++でも使えますが、強引な手法だけに実行コストが高く付き、実用性は若干微妙です(クローン可能でない既存クラスのクローンを作成する方法としては便利かもしれませんが)。

まぁそんなわけで、結局のところコピーコンストラクタを使う方法が一番無難で、実質これしかない…と私は考えていました。~
しかし、.NET Framework 3.0で導入された @code{Freezable}; クラス(@code{System.Windows}; 名前空間)がよりスマートな方法で @code{Clone}; メソッドを実装していました。

@code{Freezable}; クラスでは @code{Clone}; メソッドは仮想メソッドではなく、派生クラスでオーバライドする必要もありません((.NET Frameworkでは戻り値を派生クラスの型とするためにメソッドの隠蔽を行っている場合があります。))。~
また、コピーコンストラクタも不要です((もちろん実装しても構いませんが、.NET Frameworkの @code{Freezable}; クラスおよびその派生クラスではコピーコンストラクタは実装されていません。))。~
その代わりに、 @code{CreateInstanceCore}; メソッドと @code{CloneCore}; メソッドをオーバライドする必要があります(前者は必須、後者は場合による)。

#code(csharp,nomenu,nonumber,nooutline,noliteral,nocomment){{
protected abstract Freezable CreateInstanceCore();
}}

#code(csharp,nomenu,nonumber,nooutline,noliteral,nocomment){{
protected virtual void CloneCore(Freezable source);
}}

@code{CreateInstanceCore}; メソッドは @code{CreateInstance}; メソッドから呼び出され、派生クラスにおいてそのクラス型の新しいインスタンスを生成します。~
このメソッドは @code{Freezable}; クラスから派生したクラスでは必ずオーバライドする必要があります。~
とはいえ、 @code{return new 派生クラス();}} を書くだけなので実装コストは微々たるものです。

@code{CloneCore}; メソッドは、引数に渡された派生クラス型のインスタンスの内容を自身に詳細コピーします。~
派生クラスでフィールドを追加した場合、 @code{{base.CloneCore(source);}} を呼び出した上でそれらのフィールドもコピーするようにオーバライドする必要があります((実際は必ずしもオーバライドする必要はないのですが、今回の趣旨とは外れるため説明は省きます。))。~
要するにコピーコンストラクタ相当の処理をこのメソッドで行うわけです。

この2つのメソッドを用いると、 @code{Freezable}; クラスの @code{Clone}; メソッドは次のように書くことができます。

#code(csharp){{
// 実際の実装がこうなっているわけではなく、あくまでイメージです。
// なお、例外処理等は省略しています。
public Freezable Clone()
{
    // 内部で CreateInstanceCore メソッドが呼び出され、
    // 派生クラス型のインスタンスが生成される
    Freezable instance = CreateInstance();

    // 自身の内容を生成したインスタンスに詳細コピー
    instance.CloneCore(this);

    return instance;
}
}}

これで @code{Clone}; メソッドをオーバライドしなくとも派生先のクラスではそのクラス型のインスタンスを生成することができます。~
.NET Framework固有の機能を用いているわけでもないので、クラスを扱える言語であれば大抵は実装可能です。

この方法がコピーコンストラクタを使う方法と比べて優れている点は次の通りです。

-派生先クラスで新たにコピーする必要のあるフィールドが無い場合、 @code{CloneCore}; メソッドをオーバライドする必要が無い。
--コピーコンストラクタを用いる方法では新たにコピーする必要のあるフィールドが無くともコピーコンストラクタと @code{Clone}; メソッドを必ずオーバライドする必要がある。
-他のインスタンスの内容を自身にコピーする処理が任意のタイミングで使える。
--コピーコンストラクタが使えるタイミングはインスタンス構築時のみ。

逆にコピーコンストラクタを使う方法と比べて厄介になる点は、 @code{readonly}; なフィールドをコピーできないことでしょうか。~
この方法を用いる場合、 @code{readonly}; なフィールドは実質使えません。~
もっとも @code{get}; のみのプロパティを用いればいいのでさほど問題にはなりませんが(実装時にうっかり値を書き換えてしまうことを抑止できないくらい?)。

長々と書いた割にはそこまで圧倒的に優れている方法というわけではありませんが、まぁこんな方法もあるということで。

RIGHT:Category: [[[C#>ぼやきごと/カテゴリ/C#]]][[[プログラミング>ぼやきごと/カテゴリ/プログラミング]]] - 2011-05-08 13:06:33
----
RIGHT:&blog2trackback();
#comment(above)
#blog2navi()