Time Flies

fckey's Tech Blog

オブジェクト指向プログラミングのススメ

オブジェクト指向プログラミング導入に良記事が挙がっていたから紹介。

プログラミング勉強中の人にオブジェクト指向とは何なのかを何となく伝えたい話

前の記事はこの辺りを抑えた人が次のステップに進むために書いたものだから合わせて読んだらいいよ☆

紹介記事中では「なぜオブジェクト指向なのか」以下に大切なことが書いてある。

継承はどこで使うべきか

継承はオブジェクトを分類するための手段です。

「同じプロパティやメソッドを持っている」という実装寄りの理由で継承を使うべきではありません。

継承を使うべきかどうかを判断するときは実装ではなく、概念や仕様に着目します。同じ概念を持つクラスをまとめるために継承を使用するとよいです。

特にこういう部分のセンスを養うのがOOPの難点でありキモなんだけど、その要素が存分に詰まっているのがデザインパターンである。

デザインパターンもひと通り理解したという人が実プログラムでどう活かされているのかを学ぶためにオススメなのがこの本。

ソースコードリーディングから学ぶ Javaの設計と実装

ソースコードリーディングから学ぶ Javaの設計と実装

デザインパターンが実際に使用される例と意識するべき事とか

デザインパターン

使ったなら何か特別なモノが出来そうな気がしてくる響きである。

とある仕様の物質演舞~デザインパターン~などと書いてみるとこの世の物理法則すら超越できそうな気もしてくる。

ではデザインパターンを用いれば必ず素晴らしいソフトウェアが出来るのだろうか?

もちろんそうではない。

ソフトウェアの設計を行う上でオブジェクトの関係、再利用、メッセージングなどを考える中で自然と利用される形式。気付いたら頻繁に構築されているオブジェクトの利用方法。結果的に出来上がる構成。デザインパターンとは本来そういったものである。

センス溢れる人ならデザインパターンは学ぶまでもなく自然と構築出来るのだろう。だが、筆者を含め皆が皆オブジェクト指向の申し子とは限らない。プログラミングの幅を広げ引き出しを増やすという意味でもオブジェクト指向に慣れていない人にはデザインパターンを学ぶことを強く勧める。

以下では筆者が学生時代に記述したアプリケーション*1を用いてデザインパターンの実用例を紹介する。コードの一部切り取りでコンテキストが掴みづらいため、今回はデザインパターンは実際に利用機会があるのだと感じられたら充分だ。

紹介するコードはストリーム型Publish/Subscribeシステムの動作イメージをProcessingを用いて描画したものである。github上のプロジェクト

f:id:fckey:20140216014900p:plain

作成したPub/Subシステムのデモ実行時の画像

  • Factory Method

Node生成用のインターフェースを定義した上で処理用ノードか、センサーノードかで生成方法を変更している

public interface NodeFactory {
    
    Node create(Location loc);
    
    Node create(Id id, Location loc);
}

public class ProcessingNodeFactory implements NodeFactory{
    private static  PApplet core;
    private static  IdFactory idFactory;
    
    public ProcessingNodeFactory(PApplet core, IdFactory idFactory) {
        this.core = core;
        this.idFactory = idFactory;
    }
    
    public  Node create(Location loc){
        return create(idFactory.create(loc), loc);
    }
    
    public Node create(Id id, Location loc){
        return new ProcessingNode(core, id, loc);
    }
    
}

public class SensorFactory implements NodeFactory {

    private static  PApplet core;
    private static  IdFactory idFactory;
    public SensorFactory(PApplet core, IdFactory idFactory) {
        this.core = core;
        this.idFactory = idFactory;
    }
    
    public  Node create(Location loc){
        return create(idFactory.create(loc), loc);
    }
    
    public Node create(Id id, Location loc){
        return new SensorNode(core, id, loc);
    }

}
  • Proxy

再利用可能な未使用のデータオブジェクトがある場合はそれを描画し、ない場合には新たにデータオブジェクトを生成する。

if(dataStockList.isEmpty()){
    data = dataFactory.create(srcNode, dstNode);
    dataUsingList.add(data);
}else{
   data = getIdleData();
   data.init(srcNode.getId(), dstNode.getId(), srcNode.getLocation(), dstNode.getLocation(), Math.random() % Const.MAXDATAVALUE);
}
return data;

この他にもこのデモアプリケーションを作成するために意識しているだけでも以下のパターンが使用されている。

冒頭にも述べたが、デザインパターンはあくまでも手段であり目的を達成する上で結果的に構築されるものである。だが、武器は多いほうが戦いやすいように、デザインパターンを知ることでゴールに辿り着くための近道を見つけられる可能性が何倍にも増す...と筆者は信じている。

増補改訂版Java言語で学ぶデザインパターン入門

増補改訂版Java言語で学ぶデザインパターン入門

*1:筆者が始めて書いたJavaコードであり、当初はかなりひどかったためリファレンスされている

オブジェクト指向がよくわからない人への特効薬としてのデザインパターン

オブジェクト指向はなんとなく知ってるがどう書いたら良いかわからないという人はとりあえずデザインパターンの勉強でもしてみたらというお話。

オブジェクト指向のイメージといえば実際の"モノ”を表すようにオブジェクトは作られること、設計の特徴としてカプセル化(encapsulation)や継承(inheritance)、多態性(polymorphism)を利用するといったものが定番だろうか。

このように言うは易しなオブジェクト指向ではあるが、概念を理解したところから実際のコードに落としこむところまでは少し距離があるように感じる。 実際、C言語などの手続き型言語を多少書けるようになった人が本や大学の授業で知識として得たばかりの”オブジェクト指向”を利用しようとしたものの、とりあえずクラスを定義しメソッドを書き、main関数でオブジェクトを一つ生成した後、メソッドたちを順番に手続き的に呼び出す状態に陥った例をみる。 このように、概念としてオブジェクト指向はわかるがどう表現するのか分からない人にはデザインパターンを学んでみる事をオススメしたい。

デザインパターンとは「 GoF (Gang of Four)と呼ばれる人達がよく使用されるプログラムのパターンに名前を与え整理した」ものである。詳しくは本を読むなりWikipediaを読むなりググるなりして欲しい。

以下ではCompositeパターンを用いて、デザインパターンによりオブジェクト指向のエッセンスを上手に利用した設計が可能なことを示す。 Composite パターンとは木構造などの入れ子になったデータ構造において、容器と中身を同一視することで再帰的な処理を容易にするものであり、定番の例はディレクトリとファイルの関係である。

ここでは以下の3つのクラスとmainクラスを用いてJavaによりComposite パターンを説明する。

  • 構造の基となる抽象クラスComponent
  • 容器となるクラスDirectory
  • 実際の中身であるクラスFile

以下のコードで示した通りアルゴリズムは至ってシンプルである。 Fileクラス、Directoryクラス共にComponentクラスを継承することでdisplayPathメソッドを持ち、このメソッドはFide,Directoryでそれぞれ異なる実装がされる。 このComponentクラスの継承が容器(Directory)と中身(File)の同一視を可能とする。 Directoryは自身の中にコンポーネント(File、Directory)をもち、それぞれのコンポーネントのdisplayPathメソッドを呼ぶ。これにより再帰的な処理が行われる。このようにCompositeパターンを用いることでファイル数、ディレクトリ数がいくつになっても処理に変更を加える必要なくそのパスを確認することが出来る。

また、以下のコードではnameのカプセル化やComponentの継承、displayPathの多態性の利用などオブジェクト指向を表現する上で必須の概念が使用されている。それに加え、Directoryクラス内のdisplayPathにおいて自身の処理を他オブジェクトに任せる委譲(delegation)という技術も使用されており、これもオブジェクト指向を書く上でよく使用されるので使えるようになるとよい。

基となるComponent

public abstract class Component {
    public abstract String getName();
    public abstract void displayPath(String prefix);
    public void displayPath(){
        displayPath("");
    }
}

自身の名前情報のみを持つFileクラス

public class File extends Component {
    private String name;
    
    public File(String name){
        this.name = name;
    }
    
    @Override
    public String getName() {
        return name;
    }

    @Override
    public void displayPath(String prefix) {
        System.out.println(prefix+"/"+getName());
    }
}

FileおよびDirecrtoryを保持可能なDirectoryクラス

public class Directory extends Component {
    private String name;
    private List<Component> components = new LinkedList<Component>();
    
    public Directory(String name) {
        this.name = name;
    }
    
    @Override
    public String getName() {
        return name;
    }
    
    public void addComponents(Component component) {
        components.add(component);
    }
    
    @Override
    public void displayPath(String prefix) {
        String currentPath = prefix + "/"+ getName();
        System.out.println(currentPath);
        for (Component component : components) {
            component.displayPath(currentPath);
        }
    }
}

mainクラス

public class CompositePattern {

    public static void main(String[] args) {
        File file1 = new File("file1");
        File file2 = new File("file2");
        File file3 = new File("file3");
        File file4 = new File("file4");
        Directory dir1 = new Directory("dir1");
        dir1.addComponents(file1);
        Directory dir2 = new Directory("dir2");
        dir2.addComponents(file2);
        dir2.addComponents(file3);
        dir1.addComponents(dir2);
        dir1.addComponents(file4);
        
        dir1.displayPath("~");
    }
}
出力結果
~/dir1
~/dir1/file1
~/dir1/dir2
~/dir1/dir2/file2
~/dir1/dir2/file3
~/dir1/file4

(※必要最小限の機能だけ書いたためListのremoveがないとかnameだけのファイルはいらないとか野暮なツッコミはここまで読んだ辛抱強い読者からは来ないと信じている)


おすすめのウェブページ: デザインパターン | TECHSCORE(テックスコア)

GoF本。初心者にはおすすめしない。

オブジェクト指向における再利用のためのデザインパターン

オブジェクト指向における再利用のためのデザインパターン

やっぱこれでしょ。

増補改訂版Java言語で学ぶデザインパターン入門

増補改訂版Java言語で学ぶデザインパターン入門