凪瀬 Blog
Programming SHOT BAR

目次

Blog 利用状況
  • 投稿数 - 260
  • 記事 - 0
  • コメント - 47055
  • トラックバック - 192
ニュース
広告
  • Java開発者募集中
  • 経歴不問
  • 腕に自信のある方
  • 富山市内
  • (株)凪瀬アーキテクツ
アクセサリ
  • あわせて読みたい
凪瀬悠輝(なぎせ ゆうき)
  • Java技術者
  • お茶好き。カクテル好き。
  • 所属は(株)凪瀬アーキテクツ
  • Twitter:@nagise

書庫

日記カテゴリ

 

前回のエントリで 「外部とのインターフェースとなる部分、つまり、メソッド引数やreturn値の型、またインスタンスフィールドは厳密に抽象型を用いるべき」 と主張していたのですが、 中さんから

「なぜ抽象型を持ち上げるのかぜひ別エントリで書いてほしいと思います。
私はあくまで不要だとおもってますから。
#全然いらないという意味ではなく、必要に応じて程度でいいと思っている程度」

とリクエストされたので少し掘り下げて。

あらかじめ用語について断っておきますが、Javaのinterfaceキーワードで定義される 抽象型についてはアルファベットのinterfaceで表記します。 カタカナの「インターフェース」は一般的なプログラム用語としての接続口の意味として使います。

具象型でインターフェースを書いた場合のデメリット

具象型でインターフェースを書いた場合のデメリットは型の不整合が発生しやすく、その都度修正を迫られる点です。

Javaのコレクションフレームワークの継承階層をざっくり表現すると

  • Collection
    • Set (重複要素のないコレクション)
      • HashSet (ハッシュを用いた実装)
      • TreeSet (赤黒木によって要素の順序づけがされるSet)
    • List (順序づけられたコレクション。インデックスによる要素へのアクセスが可能)
      • ArrayList (1.2から追加された同期を伴わない可変長配列)
      • Vector (1.0から存在する可変長配列)
      • LinkedList (双方向リスト)

といった継承関係があります。(正確ではないですが、この話題ではこの程度の理解で十分でしょう)

Javaの1.1まではこれらコレクションフレームワークは整備されておらず、 可変長の配列といえばVectorのみでした。Listといった抽象的なinterfaceもなかったのです。 ですから、Javaが1.2になり、コレクションフレームワークへの移行がされるまでの間は、 次のようなインターフェースを持つメソッドがしばしば見受けられました。

public void hoge(Vector vector) { ... }

1.2以降のJavaではVectorではなくArrayListを主体として用います。 すると、この過去のメソッドhogeにArrayList型のオブジェクトを渡すことができません。

ArrayList arrayList = new ArrayList();
hoge(arrayList); // 型が合わないのでコンパイルエラー

この時期にJava界隈では可変長配列を扱う際は以下のように抽象型Listを用いるように矯正されました。

List list = new ArrayList();

このころに矯正されて抽象型で型宣言を行う癖がついている人が多いと思います。 インターフェースはできるだけ抽象型で宣言することが推奨されます。

public void hoge(List list) { ... }

このように抽象型であるListをインターフェースとして用いている場合

ArrayList arrayList = new ArrayList();
hoge(arrayList);

Vector vector = new Vector();
hoge(vector);

LinkedList linkedList = new LinkedList();
hoge(linkedList);

といったように、Listの継承型であれば何を渡しても処理できるようになります。 さらに、メソッドの作り上、ListでもSetでも一緒くたに処理できるなら

public void hoge(Collection collection) { ... }

と宣言しておけば、Setの実装も受け入れることができます。 プログラムをしているとき、つい具象型でものを考えがちですが、 そこの処理は本当にその具象型に依存するのでしょうか? 受け入れられる範囲でより抽象的な型を処理できるようにしておくと再利用性が高まります。

デメリットが小さい状況、メリットが活きない状況

元の記事にあったように「99 パーセントは ArrayList」というような状況下では インターフェースをArrayListとしておいても困ることは少ないでしょう。 プロジェクト内でListの具象型はArrayListしか使わないというなら問題は起こらない。

また、メソッドの再利用なんて考えていなくて、新たな具象型が出てきたら その都度その型専用に実装を作るというのであれば、それはそれで問題とはならない。 (もっともそんなコードクローンが蔓延するようなコードは保守性の点で難がありますが…)

次に、いざ型が合わなくなった場合に抽象度を高めることができる環境の場合。 最初から抽象的に考えずとも具象型で宣言しておき、 行き当たりばったりで変更をするということが可能な環境ではそれほど問題にならないことでしょう。

もし、そのライブラリを公に公開するとしましょう。 使い手は不特定多数ですが型を変更してくれと依頼があれば直すという姿勢では ライブラリが扱いにくくて仕方がない。 いろいろな使い方がされることを見越して、ライブラリの修正なしに広く使えるように、 つまるところ再利用性が高くなるように設計するのが通常です。

ライブラリが非公開だとしても、プロジェクト内の共通部といった多数の人に利用される場合、 同様に再利用性を高めておかないと、いざ型が合わない時に手間取ります。 インターフェースの変更依頼が手間だと

ArrayList arrayList = new ArrayList(vector);
hoge(arryList);

といった可変長配列の詰め替えを行うようなコードが蔓延するかもしれません。 このあたりはインターフェースの変更のコストの大きさというパラメータで論じることができるでしょう。

まとめると、

  • 具象型が単一であるならデメリットが小さい
  • 再利用しない前提であるならデメリットが小さい
  • インターフェースの変更コストが小さいならデメリットが小さい

ということになります。

多様な具象型を用いるし、メソッドは再利用するし、インターフェースの変更コストが高いならば、 インターフェースは抽象的にしておくにこしたことはないでしょう。

投稿日時 : 2008年3月9日 10:09
コメント
  • # re: 具象型でインターフェースを書いた場合のデメリット
    かつのり
    Posted @ 2008/03/09 14:40
    例えがコレクションだから微妙な反応を示す人が多いかもしれないですけど、
    最近のトレンドではDIコンテナなんかを使ったりすると、
    インターフェイスと実装が1:1だったりしますよね。

    インターフェイス:Hogeで実装:HogeImplがあったりすると、
    仮引数にHogeImplなんてはやらないでしょう。
    Hogeで受け取って渡す側はHogeImplだったり、一時的なモックであるHogeMockを渡したりしますよね。

    .NETとJavaでは文化が違うので、こういう書き方って微妙な感じなのかもしれませんが、
    Javaではこういうのが当たり前すぎて、逆になぜ嫌がるんだろうと思ったりしますし、
    そもそもオブジェクト指向的な設計では当たり前じゃないの?とも思ったりします。
  • # re: 具象型でインターフェースを書いた場合のデメリット
    NyaRuRu
    Posted @ 2008/03/10 1:18
    >多様な具象型を用いるし、メソッドは再利用するし、
    >インターフェースの変更コストが高いならば、
    >インターフェースは抽象的にしておくにこしたことはないでしょう。

    Generics も含めるとどうでしょう?
    .NET と Java だと,Generic 型引数 T に対する印象が違うかもしれませんが.

    Generics メソッドでは,引数の具象型 T に対して適切な戻り値の型 X(T) を決められるのですが,これもある種の抽象と言えないでしょうか?

    .NET の Genrics が絡むメソッド定義で,例えば次の 3 つを考えてみます.

    IList<T> Hoge<T>(IList<T> source) where T:ISerializable {...}

    IList<ISerializable> Hoge<T>(IList<T> source) where T:ISerializable {...}

    IList<ISerializable> Hoge(IList<ISerializable> source) {...}

    メソッドチェインでの挟み込みやすさという意味では一番上のものが最も使いやすくなります.もちろん常に一番上の形で書く必要はないのですが.

    複数の interface を実装する具象型を扱うことを考えれば,Generics 経由で interface を取り扱った方がよいということはないですかね?
  • # re: 具象型でインターフェースを書いた場合のデメリット
    凪瀬
    Posted @ 2008/03/10 10:27
    IList<T> Hoge<T>(IList<T> source) where T:ISerializable {...}
    はJavaだと
    <T extends Serializable> List<T> hoge(List<T> source) {...}
    となりますが、
    具象型ではなくList型という抽象型を用い、ただし、そのList型に格納される型は定めない。
    ただし、引数に渡すListに格納される型と、戻り値のListに格納される型は同じと定義している。

    これも抽象のひとつの形でありますから、「インターフェースは抽象的にしておく」の一環ですね。
    ジェネリクスを使ったから継承型は具象型でいいという話ではなく、
    (<T extends Serializable> ArrayList<T> hoge(ArrayList<T> source) {...} とはしない)
    継承階層を用いた抽象化、ジェネリクスを用いた抽象化含めて、より間口を広くとっておくべきです。

    このあたりは本稿では主張が弱いのですが、前回の
    「具象型でジェネリクスの型パラメータを取ると具象型独自のメソッド呼び出しができてしまったり、という問題が起こることは型のときの話に同じ」
    の下りは正にNyaRuRuさんの指摘するジェネリクスでの抽象化を意識しての発言です。

    私の頭の中では、継承階層とジェネリクスが混然一体となって抽象化を考えていますね。
  • # re: 具象型でインターフェースを書いた場合のデメリット
    凪瀬
    Posted @ 2008/03/10 10:35
    > 複数の interface を実装する具象型を扱うことを考えれば,Generics 経由で interface を取り扱った方がよいということはないですかね?

    ケースバイケースですね。
    ジェネリクスは集合論に近く、条件を限定した型の集合であればなんでもよい、という考え方で
    木構造を前提とするオブジェクト指向のポリモフィズムより扱いやすい時もあります。

    そして、その集合を規定するのは継承階層に基づいているんですよね。
    このあたりがOOPの設計で頭を使うポイントになっています。

    抽象化の柔軟性という観点からすれば集合論となるジェネリクスのほうが分があるでしょうか。
  • # re: ????????????????????????
    hydroxychloroquine meaning
    Posted @ 2021/08/07 1:01
    who makes chloroquine phosphate https://chloroquineorigin.com/# hydrachloroquine
タイトル
名前
Url
コメント