C++で書くインターフェースの話 - やまざき@BinaryTechnology
「デスマーチと戦う武蔵流プログラマ やまざき のページ」

TopPage
(サイトマップ)


Book
(書籍)


「火事場プロジェクトの法則」
サポートページ


「LHAとZIP」
サポートページ



Document
(文章)

デスマーチの記録に見る
運命の分かれ道
NEW!

武蔵流プログラマからの提言

武蔵流プログラマが斬る Eclipse

コードデザイン最前線
1
2 3 4 5 6 7 8 9
10 11 12 13 ML

C++で読む
デザインパターン


ポインタ不要論

データ圧縮の基礎

プログラマへの
アドバイス


データ指向の話1 2

インターフェースの話


Diary & Books
(日記と本屋)

やまざきの
はてなダイアリ
(日記)
[] [PC] [資産運用]
[デスマ] [映画] [2足ロボ]

やまざきの本屋


SoftWorks
(ソフトウェア)


(1) DeepFreezer
(ディープ・フリーザー)
高速アーカイバ

(English Page)

(2) Closedown-Planet
(クローズダウン・プラネット)
アクションパズルゲーム


(3) PieceMaker
(ピース・メーカー)
ファイル分割/結合


(4) WakuPita
(枠ピタ)
ウィンドウ移動便利ツール

(English Page)

(5) ググ郎
(Bookmarklet)
選択文字列をGoogleで検索

NEW!


Developing
(開発中)


(1) DeepFreezer2
yz2dlg.dll alpha6


C Magazine特集yz2


Hobby & Favorite
(道楽/お気に入り)


2LegRobo
MindStorms



p.s.
(雑談)


Profile

i_want^^;


やまざきが書いた本


[システム開発]
火事場プロジェクトの法則
どうすればデスマーチをなくせるか?
2006/09/13 発売


LHAとZIP
圧縮アルゴリズム×プログラミング入門

奥村さんと共著です。
2003/12/01 発売


やまざきが寄稿した本


SEの読書術
「本質を読む」力を磨く10の哲学 2006/02発売。



開発の現場 Vol.002
「反デスマーチ大研究」という記事。2005/09/13発売。



Software People Vol.3
「武蔵流プログラマからの提言」という記事。2003/10/31発売。



Eclipse パーフェクトマニュアル vol.1
「武蔵流プログラマが斬る Eclipse」という記事。2003/06/05発売。




C++で書くインターフェースの話

● はじめに


 最近、プログラミングの概念として、 インターフェースについてあれこれと考えるようになりまして、 すこし、自分の頭の中を整理する意味で、このページを書いています。
 ですから、このページを読む時に「勉強になる」とか「メリットがある」などとは 考えないでください。 主に、私が頭の中で考えたことをだらだらと書きだしているだけです。
 
ただ、プログラミングのさいの何らかのヒントになれば幸いと思っています。

● C++でのインターフェースの書き方


 いきなりですが、インターフェースをコーディングするさいの 書き方について取り上げてみます。まずは、C++でインターフェースを記述するにはどんな書き方があるのか、 思いつくまま列挙してみます。


●その1 パラメータ+継承


 パラメータと継承を使う おそらく一番オーソドックスなタイプと思われます。 オリジナルのインターフェースを継承して、新しいインターフェースをつくり、 実行時にパラメータとして渡す方法。

// 継承 inherit + パラメータ param

#include < stdio.h >  // printf(), getchar()

// インターフェース(実装付き)
class OrgInterface
{
public:
    virtual void  Method1()
    {
        puts( "OrgInterface::Method1()" ) ;
    }

public:
    virtual void  Method2()
    {
        puts( "OrgInterface::Method2()" ) ;
    }
} ;

// ボディ(OrgInterface を利用して実装されている)
class OrgBody
{
public:
    void  CallMethod(
        OrgInterface &  in_param  // 引数 param
    )
    {
        in_param.Method1() ;
        in_param.Method2() ;
    }
} ;

//---------------------------
// 新しいインターフェースを作る

class NewInterface1 : public OrgInterface // 継承 inherit

    // 新しいメソッドに置き換える
public:
    virtual void  Method1()
    {
        puts( "NewInterface1::Method1()" ) ;
    }

    // 新しいメソッドに置き換える
public:
    virtual void  Method2()
    {
        puts( "NewInterface1::Method2()" ) ;
    }
} ;

class NewInterface2 : public OrgInterface // 継承 inherit

    // 新しいメソッドに置き換える
public
    virtual void  Method1()
    {
        puts( "NewInterface2::Method1()" ) ;
    }

    // オリジナルのメソッドを使う(置き換えない)
    // public: virtual void  Method2()
    // {
    //     puts( "NewInterface2::Method2()" ) ;
    // }
} ;

//---------------------------
// 実装
int  main( void )
{
    // 新しいインターフェースの実体を作る。
    NewInterface1  new_interface1 ;
    NewInterface2  new_interface2 ;

    // ボディの実体を作る。
    OrgBody  org_body ;

    // パラメータでインターフェースを渡すので、
    // 1つのボディを2種類のインターフェースで実行できる。
    org_body.CallMethod( new_interface1 ) ;
    org_body.CallMethod( new_interface2 ) ;

    getchar() ; // 終了前に一時停止

    return 0 ;
}

/* 実行結果
NewInterface1::Method1()
NewInterface1::Method2()
NewInterface2::Method1()
OrgInterface::Method2()
*/



● その2 パラメータ+テンプレート


 パラメータとテンプレートを使う方法。 その1の方法では、継承を使ってますが、 継承の代わりにテンプレート関数を使っての実装方法です。 パラメータとして渡すのは同じですが、 継承と違ってオリジナルのインターフェースを用意する必要がありません。 それが、メリットなのか?デメリットなのか?、いまのところなんとも言えません。 各自で考えてほしいです。

// パラメータparameta + テンプレートtemplate

#include < stdio.h >  // printf(), getchar()

/* インターフェースの記述は必要ない
class OrgInterface 
{
public:
    void  Method1()
    {
        puts( "OrgInterface::Method1()" ) ;
    }

public:
    void  Method2()
    {
        puts( "OrgInterface::Method2()" ) ;
    }
}; 
*/


// ボディ( _T を利用して実装されている)
class OrgBody
{
public:
    templateclass _T >              // テンプレート関数
    void CallMethod( _T &  in_param ) // パラメータ
    { 
        in_param.Method1() ;
        in_param.Method2() ;
    } 
} ;

//---------------------------
// 新しいインターフェースを作る

class NewInterface1
{
    // メソッドの実装(置き換えるという考え方ではない)
public:
    void  Method1()
    {
        puts( "NewInterface1::Method1()" ) ;
    }

public:
    void  Method2()
    {
        puts( "NewInterface1::Method2()" ) ;
    } 
} ;

class NewInterface2

    // メソッドの実装(置き換えるという考え方ではない)
public:
    void  Method1()
    {
        puts( "NewInterface2::Method1()" ) ;
    }

public:
    void  Method2()
    {
        puts( "NewInterface2::Method2()" ) ;
    } 
} ;

//---------------------------
// 実装
int  main( void )
{
    // 新しいインターフェースの実体を作る。
    NewInterface1  new_interface1 ;
    NewInterface2  new_interface2 ;

    // ボディの実体を作る。
    OrgBody  org_body ;

    // パラメータでインターフェースを渡すので、
    // 1つのボディを2種類のインターフェースで実行できる。
    org_body.CallMethod( new_interface1 ) ; 
    org_body.CallMethod( new_interface2 ) ;

    getchar() ; // 終了前に一時停止

    return 0 ; 
}

/* 実行結果
NewInterface1::Method1()
NewInterface1::Method2()
NewInterface2::Method1()
NewInterface2::Method2()

*/



● その3 パラメータ+テンプレート+継承


 今度は、パラメータ、テンプレート、継承、すべてを使う方法です。 その1と、その2の 合わせ技のようなモノです。 オリジナルのインターフェースは用意されていますが、 継承して使っても使わなくてもどちらの使い方でもできるメリットがあります。

// パラメータ parameta + テンプレート template + 継承 inherit

#include < stdio.h >  // printf(), getchar()

// インターフェース(実装付き)
class OrgInterface 
{
public:
    void  Method1()
    {
        puts( "OrgInterface::Method1()" ) ;
    }
     
public:
    void  Method2()
    {
        puts( "OrgInterface::Method2()" ) ;
    }
};

// ボディ( _T を利用して実装されている)
class OrgBody
{
public:
    templateclass _T > // テンプレート関数
    void  CallMethod( _T &  in_param )
    { 
        in_param.Method1() ;
        in_param.Method2() ;
    } 
} ;

//---------------------------
// 新しいインターフェースを作る

class NewInterface1 : public OrgInterface // 継承 inherit

    // 新しいメソッドに置き換える
public:
    void  Method1()
    {
        puts( "NewBody1::Method1()" ) ;
    }

    // 新しいメソッドに置き換える
public:
    void  Method2()
    {
        puts( "NewBody1::Method2()" ) ;
    }
} ;

class NewInterface2 : public OrgInterface
{
    // 新しいメソッドに置き換える
public:
    void  Method1()
    {
        puts( "NewBody2::Method1()" ) ;
    }
 
    // オリジナルのメソッドを使う(置き換えない)
    // public: void  Method2()
    // {
    //     puts( "NewBody2::Method2()" ) ;
    // }
}; 

//---------------------------
// 実装
int  main( void )

    // 新しいインターフェースの実体を作る。
    NewInterface1  new_interface1 ;
    NewInterface2  new_interface2 ;

    // ボディの実体を作る。
    OrgBody  org_body ;

    // パラメータでインターフェースを渡すので、
    // 1つのボディを2種類のインターフェースで実行できる。
    org_body.CallMethod( new_interface1 ) ;
    org_body.CallMethod( new_interface2 ) ;

    getchar() ; // 終了前に一時停止

    return 0 ;


/* 実行結果
NewBody1::Method1()
NewBody1::Method2()
NewBody2::Method1()
OrgInterface::Method2()

*/



● その4 包含+継承


 次は、個人的に気に入っている「包含」を使った書き方です。 包含と継承を使って、インターフェースを実装しています。 その1〜その3で使ってきたパラメータの方式では、 ダイナミックにインターフェースの変更ができますが、 逆に、毎回インターフェースを指定しなければならない というデメリットもあります。 包含の方式では、宣言時に一度インターフェースを決めると二度と変更することは できなくなります。 しかし、毎回指定する必要は無くなり、すっきりとしたコードになります。 (個人的には、変更できないほうが良いように思います。)

// 包含 contain + 継承 inherit

#include < stdio.h >  // puts(), getchar()

// インターフェース(実装付き)
class OrgInterface 
{
public:
    virtual void  Method1()
    {
        puts( "OrgInterface::Method1()" ) ;
    }

public:
    virtual void  Method2()
    {
        puts( "OrgInterface::Method2()" ) ;
    } 
} ;

// ボディ(OrgInterface を利用して実装されている)
class OrgBody
{
    private: OrgInterface &  org ; // 包含 contain
public:
    OrgBody( OrgInterface &  in_org ) : org( in_org ) { }
public:
    void CallMethod()
    { 
        org.Method1() ;
        org.Method2() ;
    } 
} ;

//---------------------------
// 新しいインターフェースを作る

class NewInterface1 : public OrgInterface // 継承 inherit

    // 新しいメソッドに置き換える
public:
    virtual void  Method1()
    {
        puts( "NewInterface1::Method1()" ) ;
    }

    // 新しいメソッドに置き換える
public:
    virtual void  Method2()
    {
        puts( "NewInterface1::Method2()" ) ;
    } 
} ;

class NewInterface2 : public OrgInterface // 継承 inherit

    // 新しいメソッドに置き換える
public:
    virtual void  Method1()
    {
        puts( "NewInterface2::Method1()" ) ;
    }
    
    // オリジナルのメソッドを使う(置き換えない)
    // public: virtual void  Method2()
    // {
    //     puts( "NewInterface2::Method2()" ) ;
    // }
} ;

//---------------------------
// 実装
int  main( void )
{
    // 新しいインターフェースの実体を作る。
    NewInterface1  new_interface1 ;
    NewInterface2  new_interface2 ;

    // ボディの実体を作る。
    OrgBody  org_body1( new_interface1 ) ;
    OrgBody  org_body2( new_interface2 ) ;

    // インターフェースはボディ宣言時に決まるので、
    // 途中でインターフェースの変更はできない。
    org_body1.CallMethod() ; 
    org_body2.CallMethod() ;

    getchar() ; // 終了前に一時停止

    return 0 ; 
}

/* 実行結果
NewInterface1::Method1()
NewInterface1::Method2()
NewInterface2::Method1()
OrgInterface::Method2()

*/



● その5 包含+テンプレート


 包含とテンプレートを使う方法です。 その2の方法と同じように、継承ではなく、継承の変わりにテンプレート関数を使っての実装方法です。 (個人的には、継承が嫌いなので、かなり気に入っている書き方でもあります。) デメリットとして、オリジナルのインターフェースが無いというのは、 インターフェースを実装するときに困るように思えます。

// 包含 contain + テンプレートtemplate

#include < stdio.h >  // printf(), getchar()

/* インターフェースの記述は必要ない
class OrgInterface 
{
public:
    void  Method1()
    {
        puts( "OrgInterface::Method1()" ) ;
    }

public:
    void  Method2()
    {
        puts( "OrgInterface::Method2()" ) ;
    }
}; 
*/


// ボディ( _T を利用して実装されている)
templateclass _T >
class OrgBody
{
    private: _T  t ;   // 包含 contain
public:
     void CallMethod()
    { 
        t.Method1(); 
        t.Method2(); 
    } 
} ;

//---------------------------
// 新しいインターフェースを作る

class NewInterface1
{
    // メソッドの実装(置き換えるという考え方ではない)
public:
    void  Method1()
    {
        puts( "NewInterface1::Method1()" ) ;
    }

public:
    void  Method2()
    {
        puts( "NewInterface1::Method2()" ) ;
    } 
} ;

class NewInterface2

    // メソッドの実装(置き換えるという考え方ではない)
public:
    void  Method1()
    {
        puts( "NewInterface2::Method1()" ) ;
    }

public:
    void  Method2()
    {
        puts( "NewInterface2::Method2()" ) ;
    } 
} ;

//---------------------------
// 実装
int  main( void )

    // 新しいボディの実体を作る。
    // ここで、すでにインターフェースは決まる。
    OrgBody< NewInterface1 >  org_body1 ; 
    OrgBody< NewInterface2 >  org_body2 ; 

    // インターフェースはボディ宣言時に決まるので、
    // 途中でインターフェースの変更はできない。
    org_body1.CallMethod() ; 
    org_body2.CallMethod() ;

    getchar() ; // 終了前に一時停止

    return 0 ; 
}

/* 実行結果
NewInterface1::Method1()
NewInterface1::Method2()
NewInterface2::Method1()
NewInterface2::Method2()

*/



● その6 包含+テンプレート+継承


 で、やはり、その3と同じように、 その4とその5を合わせたような方式です。 包含、テンプレート、継承、とC++の機能をフルに使った方式とも言えます。 これも、その3と同じように、継承を使っても使わなくてもよいところが特徴と言えば特徴です。 その1〜その6に書いた書き方の中では、おそらく、この書き方が 一番汎用性が高く、記述しやすい書き方ではないかと思います。

// 包含 contain + テンプレート template + 継承 inherit

#include < stdio.h >  // printf(), getchar()

// インターフェース(実装付き)
class OrgInterface 
{
public:
    void  Method1()
    {
        puts( "OrgInterface::Method1()" ) ;
    }
     
public:
    void  Method2()
    {
        puts( "OrgInterface::Method2()" ) ;
    }
};

// ボディ( _T を利用して実装されている)
templateclass _T > // テンプレート
class OrgBody
{
    private: _T org ; // 包含 contain
public:
    void  CallMethod()
    { 
        org.Method1() ;
        org.Method2() ;
    } 
} ;

//---------------------------
// 新しいインターフェースを作る

class NewInterface1 : public OrgInterface // 継承 inherit

    // 新しいメソッドに置き換える
public:
    void  Method1()
    {
        puts( "NewBody1::Method1()" ) ;
    }
     
    // 新しいメソッドに置き換える
public:
    void  Method2()
    {
        puts( "NewBody1::Method2()" ) ;
    }
} ;

class NewInterface2 : public OrgInterface
{
    // 新しいメソッドに置き換える
public:
    void  Method1()
    {
        puts( "NewBody2::Method1()" ) ;
    }
 
    // オリジナルのメソッドを使う(置き換えない)
    // public: void  Method2()
    // {
    //     puts( "NewBody2::Method2()" ) ;
    // }
}; 

//---------------------------
// 実装
int  main( void )

    // 新しいボディの実体を作る。
    OrgBody< NewInterface1 >  new_body1 ; 
    OrgBody< NewInterface2 >  new_body2 ; 

    // インターフェースは NewBody クラス宣言時に決まるので、
    // 途中でインターフェースの変更はできない。
    new_body1.CallMethod() ; 
    new_body2.CallMethod() ;

    getchar() ; // 終了前に一時停止

    return 0 ; 


/* 実行結果
NewBody1::Method1()
NewBody1::Method2()
NewBody2::Method1()
OrgInterface::Method2()

*/



● おわりに


 テンプレート+キャスト+多重継承 最後に、参考までに、某マイクロソフトが開発(?)した インターフェースの記述を紹介します。 なんと、自分自身をテンプレートとして使い、さらにキャスト、そして多重継承と、 なんともコンパイラ泣かせなコーディングです。 よくこんなコードが通るものだと、関心してしまいます。 よーく読まないと、なにがどうなっているのかさっぱり理解できませんが、 「インターフェースとボディを多重継承して、新しいボディを作っている」と 考えると理解しやすいでしょう。 確かに、こう書けば書けるというのはわかりますが、可読性やメンテナンスを考えると 少し引いてしまいます。 「無条件に信じる」のは止めましょう。 もう少し客観的に見て、本当にこの書き方にメリットがあるのかどうか、 各自で確かめることをお勧めします。

// テンプレート template +キャスト + 多重継承

#include < stdio.h >  // printf(), getchar()

// インターフェース(実装付き)
class OrgInterface 
{
public:
    void  Method1()
    {
        puts( "OrgInterface::Method1()" ) ;
    }
     
public:
    void  Method2()
    {
        puts( "OrgInterface::Method2()" ) ;
    }
} ;

// ボディ( _T を利用して実装されている)
templateclass _T > // テンプレート
class OrgBody
{
public:
    void  CallMethod()
    { 
        (static_cast< _T * >(this))->Method1() ; // キャスト
        (static_cast< _T * >(this))->Method2() ; // キャスト
    } 
} ;

//---------------------------
// 新しいインターフェースを作る

class NewBody1 : public OrgBody< NewBody1 > // 変則テンプレート
               , public OrgInterface        // なんと多重継承 

    // 新しいメソッドに置き換える
public:
    void  Method1()
    {
        puts( "NewBody1::Method1()" ) ;
    }
     
    // 新しいメソッドに置き換える
public:
    void  Method2()
    {
        puts( "NewBody1::Method2()" ) ;
    }
} ;

class NewBody2 : public OrgBody< NewBody2 > // 変則テンプレート
               , public OrgInterface        // なんと多重継承 
{
    // 新しいメソッドに置き換える
public:
    void  Method1()
    {
        puts( "NewBody2::Method1()" ) ;
    }
 
    // オリジナルのメソッドを使う(置き換えない)
    // public: void  Method2()
    // {
    //     puts( "NewBody2::Method2()" ) ;
    // }
} ;

//---------------------------
// 実装
int  main( void )

    // 新しいボディの実体を作る。
    NewBody1  new_body1 ;
    NewBody2  new_body2 ;

    // インターフェースは NewBody クラス宣言時に決まるので、
    // 途中でインターフェースの変更はできない。
    new_body1.CallMethod() ;
    new_body2.CallMethod() ;

    getchar() ; // 終了前に一時停止

    return 0 ; 


/* 実行結果
NewBody1::Method1()
NewBody1::Method2()
NewBody2::Method1()
OrgInterface::Method2()

*/

 このようにC++では、いろいろなインターフェースの書き方が存在します。 どの書き方が良いか?という答えは簡単には出ないでしょう。 おそらく「この場合はこの書き方」などと、 その場にあわせた書き方になるような気もします。 個人的には「その6」のような書き方が一番気に入っていますが、 まだまだ、考えが変わる可能性はあります。

 以下に、その1〜その6までの各書き方のメリット/デメリットをまとめてみました。

書き方 1 2 3 4 5 6 メリット デメリット
パラメータ ○ ○ ○ − − − ダイナミックに変更可能 毎回指定する必要がある
包含 − − − ○ ○ ○ コードがすっきりとして可読性が良い 途中で変更できない
継承 ○ − ○ ○ − ○ インターフェースの仕様が明確になる。オリジナルインターフェースが利用可能。 インターフェースを必ず書く必要がある
テンプレート − ○ ○ − ○ ○ インターフェースの記述が無くても良い。継承を使わずに実装できるので、オブジェ クトを軽く作ることができる。 インターフェースの仕様が不明確になる。


 パラメータと包含の書き方は、排他的に見えます。 両方を混在する書き方もできるとは思いますが、使う立場から考えると どちらかに統一されていたほうが理解しやすいと思われます。 個人的には、包含の書き方が好みです。
 継承とテンプレートの書き方は排他的ではなく、混在する書き方も可能です。 混在する書き方で、両者のデメリットがある程度緩和できるように思えます。 個人的に、継承は好きではないのですが、インターフェース継承まで嫌うわけには いかないようです。
 この考察の結果、やはり「その6」の書き方に一番メリットがあるように見えます。 場合によっては他の書き方が有効な時もあるでしょうが、それはその都度臨機応変に 対応するということにして、インターフェースの書き方の基本は「その6」で 一旦、決着をつけることにします。
 もし、この文章を読んだ人のなかで、他に 「こんな書き方のほうが良いのでは?」という案などありましたら、 掲示板のほうへドシドシ書き込んでください。お待ちしています。



現状では、ここまでです。 コードの紹介で終わりです。 そのうち、解説が書けるといいなぁとは思っています。 が、いつになることやら。


last update 2002/03/05
since 2000/05/23



やまざきのおすすめエレクトロニクス


やまざきのおすすめ本

やまざきのおすすめDVD

やまざきのおすすめCD



Copyright(c) 1998-2006.
YAMAZAKI Satoshi.
All rights reserved.

since 1997/12/15


このページのURLをメールで送る(友人・知人に教えてあげる)
このページを「お気に入り」に追加する(忘れないように…)
● お手紙はこちら↓。仕事の話は大歓迎です。(忙しくて返信できなかったらごめんなさい。)