ホーム » スタッフ » 斉藤徹 » 隠蔽化とコンストラクタとgetter,setter

2014年4月
« 3月   5月 »
 12345
6789101112
13141516171819
20212223242526
27282930  

最近の投稿(電子情報)

アーカイブ

カテゴリー

隠蔽化とコンストラクタとgetter,setter

前回までの授業で、簡単なクラスを作るまでを説明したので、 複素数クラスのリファクタリングを通して、隠蔽化を説明。

getter,setterメソッド

前回の授業では、以下のプログラムを示していたが、 例えば名前nameや年齢ageを外部で使用したい場合には、 name,age のprivate設定を、public にする方法が考えられる。

class Person {
private: // publicにする?
char name[ 20 ] ;
int  age ;
public:
void set( char s[] , int a ) {
strcpy( name , s ) ;
age = a ;
}
void print() {
printf( "%s %d¥n" , name , age ) ;
}
} ;

しかし、name,age を public にすると、不容易にクラス内の変数を 触られてトラブルの元凶となる場合がある。 こういった場合、フィールドを参照するだけのメソッド(getter), フィールドに値を参照するだけのメソッド(setter)を定義する方法が 有名である。 また、getterメソッドなどは、値を参照するだけで、クラス内に副作用を与えないことを 明示するために、const メソッドを指定することもできる。 setter では、代入時に想定外の値が入力されないように、 開発中だけ、値のチェック処理を埋め込むなどの使い方もできる。

class Person {
private:
char name[ 20 ] ;
int  age ;
public:
// getter
const char* get_name() const {
return name ;
}
int get_age() const {
return age ;
}
// setter
void set_name( char s[] ) {
strcpy( name , s ) ;
}
void set_age( int a ) {
// リリース版ではif文をコメントアウト
if ( a < 0 )printf( "set_age(int) : domain error¥n" ) ;
age = a ;
}
} ;

コンストラクタとデストラクタ

最初の例のset() メソッドのように、オブジェクトは何らかの初期化をするのが通常であり、 逆に初期化を忘れたオブジェクトは、後々異常動作の元になる。 このため、データの初期化を簡単に扱えるようにコンストラクタ(構築子)を用いる。

また、データを生成し何らかの処理を行った後、そのデータを廃棄する前に必ず処理を 呼び出したい場合がある。こういった場合にはデストラクタを用いる。

class Person {
private:
char name[ 20 ] ;
int  age ;
public:
// コンストラクタ(データを初期化するメソッド)
Person( char s[] , int a ) {
strcpy( name , s ) ;
age = a ;
}
// デストラクタ(データを廃棄するときに自動的に呼び出し)
~Person() {
printf( "destructor %s,%d¥n" , name , age ) ;
}
void print() {
printf( "%s %d¥n" , name , age ) ;
}
} ;
int main() {
Person saitoh( "t-saitoh" , 49 ) ;
saitoh.print() ;
// saitoh.print() で "t-saitoh 49"が表示され
// mainを抜ける際にデストラクタが呼び出され、
// "destructor t-saitoh,49"が表示される
return 0 ;
}

複素数クラスで隠蔽化とリファクタリング

前述のような、getter,setterによりデータを不用意に扱われないようにする 対策に合わせ、クラス利用者にクラス内の情報を知らなくても扱える(データ構造の隠蔽化)、 そのメソッドがどのように実装されているか知らなくても使える(手続きの隠蔽化)を 行うことを総称して、隠蔽化(あるいはブラックボックス化)と呼ぶ。

隠蔽化を施せば、後で内部構造や内部処理を書き換えても、クラスの利用者には 影響を少なくできることを示すために複素数クラスを考える。 最も一般的な方法であれば、実部と虚部で以下のように実装するだろう。

class Complex {
private:
double re , im ;
public:
Complex( double r , double i )
: re( r ) , im( i ) {
// 上記は要素を指定値で初期化する場合の書き方。
}
void print() {
printf( "%lf+j%lf" , re , im ) ;
}
void add( Complex z ) {
re = re + z.re ;
im = im + z.im ;
}
void mul( Complex z ) {
double nr = re * z.re - im * z.im ;
double ni = im * z.re + re * z.im ;
re = nr ;
im = ni ;
}
} ;
int main() {
Complex a( 1 , 2 ) ;
Complex b( 2 , 3 ) ;
Complex c( 3 , 4 ) ;
a.add( b ) ;  // a.add( Complex( 2,3 ) ) ; もアリ
a.print() ;   // 3+j5 を表示。
a.mul( c ) ;
a.print() ;   // (3+j5) * (3+j4)
return 0 ;
}

上記のプログラムでは、add() はシンプルに記述できたけど、 乗算はそれなりに複雑(という程でもないけど…)になった。 しかし、複素数を絶対値と偏角で記述することも可能となる。

class Complex {
private:
double abs , arg ;
public:
Complex( double r , double i ) {
abs = sqrt( r*r + i*i ) ;
arg = atan2( i , r ) ;
}
void mul( Complex z ) {
abs *= z.abs ;
arg += z.arg ;
}
// 他のメソッドは記載省略
} ;

上記の例の様に、当初のクラス設計で不具合があった場合に 後で「内部設計・メソッドを全面変更」を行うと、通常であれば クラス利用者にも影響が大きい。 しかし、内部設計の変更の影響が出ないように、適切なメソッドを 定義しておけば、クラス利用者に内部変更が解らない様にすることができる。 これが、隠蔽化の利点である。

このように、適切な隠蔽化が行われていれば、クラス内の設計変更は 気軽に行えるようになる。 そして、クラス内部の設計変更を積極的に行うことで、プログラムの改善を 行うことはリファクタリングなどと呼ばれる。