隠蔽化とコンストラクタと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 ; } // 他のメソッドは記載省略 } ;
上記の例の様に、当初のクラス設計で不具合があった場合に 後で「内部設計・メソッドを全面変更」を行うと、通常であれば クラス利用者にも影響が大きい。 しかし、内部設計の変更の影響が出ないように、適切なメソッドを 定義しておけば、クラス利用者に内部変更が解らない様にすることができる。 これが、隠蔽化の利点である。
このように、適切な隠蔽化が行われていれば、クラス内の設計変更は 気軽に行えるようになる。 そして、クラス内部の設計変更を積極的に行うことで、プログラムの改善を 行うことはリファクタリングなどと呼ばれる。
IPアドレスの管理スクリプト適用
自宅のサーバで、IPアドレスの管理のスクリプトを書いたので、 早々に職場にも適用。 /etc/dhcp/dhcpd.conf,/etc/hosts,/etc/samba/lmhosts を テンプレート生成するようにしてみた。
hosts,lmhosts は、あまり使われないけど、 ネットワークトラブル時に、この辺の設定があると 対応がしやすいので…