指向性メモ::2005-02-25

ページ情報
制作日
2005-02-25T08:06:22+09:00
最終更新日
2005-02-25T21:52:43+09:00

JavaScriptでデザインパターンその4

Created:
2005-02-25T08:06:22+09:00

Composite。

例えば自分のサイトのディレクトリ構造を考える。

このデータ構造をOOPっぽく表してみよう。

まず最初に考えるべき事は、「ディレクトリ([dir])とファイルってほとんど同じものだよね」と言うことだ。例えばプロパティを考えるとしたら「名前」や「ファイルサイズ(ディレクトリの場合は中身の合計)」など、基本的なものは同一である。むしろ唯一の違いは「ディレクトリはファイルを中身に持つ」と言うことだけだ。ってことは、共通部分を括り出せば効率的なコーディングが可能になるだろう。

ところで、上のディレクトリツリーは木構造になっているけども、こういった木構造のデータは他にも沢山ある。例えば組織図やBBSのスレッド、DOM等々。中身(ファイル)と入れ物(ディレクトリ)を同じものとして扱うことにより、こういった木構造を効率的に作っていくのがCompositeパターンだ。

// まずは共通部分を括りだしたクラスを作る
function Item() {
    this.name; // 名前

    // 名前を返すメソッド
    function Item.prototype.getName() {
        return this.name;
    }

    // サイズを返すメソッド(ファイルとディレクトリでは微妙に違うのでAbstractにしておく)
    function Item.prototype.getSize() {
        // abstract
    }
}

// ファイル用のクラス
function File(newName, newSize) {
    this.name = newName;
    this.size = newSize; // サイズ保持用の変数

    // サイズを返すメソッドを実装
    function File.prototype.getSize() {
        return this.size;
    }
}
File.prototype = new Item() // Item クラスを継承


// ディレクトリ用のクラス
function Directory(newName) {
    this.name = newName;
    this.children = new Array() // 中身を保持するための配列

    // サイズを返すメソッドを実装
    function Directory.prototype.getSize() {
        var total = 0;
        for (var i = 0; i < this.children.length; i++) {
            total += this.children[i].getSize();
        }
        return total;
    }

    // 新しい中身を追加するメソッド
    function Directory.prototype.add(item) {
        this.children.push(item);
    }

    // 中身を返すメソッド。面倒なのでIteratorパターンは使わない
    function Directory.prototype.getChildren() {
        return this.children;
    }
}
Directory.prototype = new Item() // Item クラスを継承


// デモコード
var root = new Directory("root");
root.add(new File("index.html", 2));
root.add(new File("style.css", 3));

var about = new Directory("about");
about.add(new File("index.html", 3));
root.add(about);

var blog = new Directory("blog");
blog.add(new File("index.html", 4));
blog.add(new File("latest.rss", 3));
root.add(blog);

var b2005 = new Directory("2005");
b2005.add(new File("01.html", 8));
b2005.add(new File("02.html", 5));
blog.add(b2005);

// rootディレクトリの中身とサイズの関係を出力してみる。
var rootChildren = root.getChildren();
for (var i = 0; i < rootChildren.length; i++) {
    print(rootChildren[i].getName() + " -- " + rootChildren[i].getSize() + "KB"); // print は適当に定義すべし
}

// Result:
// index.html -- 2KB
// style.css -- 3KB
// about -- 3KB
// blog -- 20KB

名前を返してくれるgetName()メソッドはItemクラスで定義されているため、FileDirectoryクラスで定義し直す必要が無く、とても効率的だ。さらに、上のデモコードで強調されている部分、print(rootChildren[i].getName() + " -- " + rootChildren[i].getSize() + "KB");を見て欲しい。print関数が「rootChildren[i]FileクラスかDirectoryクラスか」に無関心なのが分かるだろうか。この2つのクラスは同一視されているため、getName()getSize()というメソッドさえ分かっていれば、データを扱う側は違いを意識せずに扱うことが出来るのだ。この「同一視」による単純化がCompositeパターンの1番の肝になっている。

Javaの場合は型についても適度に無関心になる必要性があるため、継承が必須となるが、JavaScriptの場合はメソッド名さえ統一しておけば、Itemクラスの継承は必ずしも必要ではない(例えばgetName()もAbstractにしてしまった場合、結局全てのメソッドを定義し直す事になる)。getName()のような同じコードがある場合は継承し、無ければメソッド名を統一するだけも問題ないだろう。

ところで、なんだかこのシリーズは微妙に反響があるようなのだけども、読んで理解できるのか心配になってきた。「なぜこのパターンを使うのか」という説明は真面目にしてるつもりなのだけども、「Abstract」とかJavaScriptには無い概念を使っていたりして、本当に初めての人にはわかりにくいかも知れない。ある程度他の言語でOOPの知識がある人用かなあ。正直なところ言語レベルの話として、Javaほどにはデザインパターンが有効活用できない面もあるし、やっぱりOOPの勉強をするならJavaが良いと思う次第。あ、そうだ、「PHP5でデザインパターン」でもやろうか。かなり需要ありそうな予感。

Comments
0
Trackbacks
0
PermaLink
http://yudai.arielworks.com/memo/2005/02/25/080622

PHP5でデザインパターン

Created:
2005-02-25T21:52:43+09:00

JavaScriptばっかり書いてるとPHPを忘れそうなので、既に解説した分をPHP5で書いてみた。

Iterator。楽をしようと思ったら言語組み込みのinterfaceを実装するべし。

<?php
class BookShelf implements IteratorAggregate {
    private $books = array();

    public function __construct($bookList) {
        $this->books = $bookList;
    } 

    public function getIterator() {
        return new BookShelfIterator($this->books);
    }
}

class BookShelfIterator implements Iterator {
    private $books = array();

    public function __construct($target) {
        $this->books = $target;
    }

   public function rewind() {
       reset($this->books);
   }

   public function current() {
       $book = current($this->books);
       return $book;
   }

   public function key() {
       $key = key($this->books);
       return $key;
   }

   public function next() {
       $book = next($this->books);
       return $book;
   }

   public function valid() {
       $isExisting = $this->current() !== FALSE; // return TRUE or FALSE
       return $isExisting;
   }
}

class Demo {
    public static function main() {
        $shelf = new BookShelf(array("空ノ鐘の響く惑星で", "半分の月がのぼる空", "君の嘘、伝説の君"));
        // 超ラクチンなイタレーション
        // IteratorAggregate を実装しているので foreach を使うと自動的にIteratorの生成から何からしてくれる
        foreach ($shelf as $key => $title) {
            print $key . ". 『" . $title . "』\n";
        }
    }
}
Demo::main();

// Result:
// 0. 『空ノ鐘の響く惑星で』
// 1. 『半分の月がのぼる空』
// 2. 『君の嘘、伝説の君』 
?>

デモコードに書いたとおり、foreachで回せるようになるので連想配列では特に便利だ。

次、Factory。

<?php
class Factory {

    public function __construct() {
    }

    public function create() {
        return new Item();
    }
}

class Item {
    private $name = "";

    public function __construct () {
    }

    public function setName($newName) {
        $this->name = $newName;
    }
}


class Demo {
    public static function main () {
        $fact = new Factory();
        $item1 = $fact->create();
        $item1->setName("Item1");
        $item2 = $fact->create();
        $item2->setName("Item2");
    }
}
Demo::main();
?>

考えてみれば、ホントはFactoryItemabstractにして、実装したクラスは別に用意した方が良かったのかも。

Singleton。PHP5にはアクセス制限があるので言語レベルでの同一性が保証される。

<?php
class Earth {
    private static $instance = NULL;

    public static function getInstance() {
        if (self::$instance == NULL) {
            self::$instance = new Earth(); // クラス内なのでコンストラクタを呼び出せる
        }
        return self::$instance;
    }


    private $population = 2;

    private function __construct() {
        // コンストラクタが private なのでクラス外で new されるとエラーになる
    }

    public function increasePopulation() {
        $this->population = $this->population * 2; 
    }

    public function getPopulation() {
        return $this->population;
    }
}

class Demo {
    public static function main() {
        $e1 = Earth::getInstance();
        print "1st test: " . $e1->getPopulation() . "(e1)\n";

        $e2 = Earth::getInstance();
        $e2->increasePopulation();
        print "2nd test: " . $e2->getPopulation() . "(e2)\n";

        print "3rd test: " . $e1->getPopulation() . "(e1)\n";

        // 試しに new してみる
        print "Additional test: ";
        $e3 = new Earth();
    }
}
Demo::main();

// Result:
//  1st test: 2(e1)
//  2nd test: 4(e2)
//  3rd test: 4(e1)
//  Additional test: 
//  Fatal error: Call to private Earth::__construct() form context 'Demo'...
?>

コンストラクタがprivateになっているため、クラス外部でnewしてインスタンスを作ろうとすると必ずエラーになる。これにより、Earthクラスのインスタンスを得るにはstaticgetInstance()メソッドを用いなければならない。

長くなってきたので1度分割しよう。それにしても、クラスベースの言語だと楽だわぁ。メソッド呼び出しが->なのが少し面倒だけれど。

Comments
0
Trackbacks
0
PermaLink
http://yudai.arielworks.com/memo/2005/02/25/215243
連絡先、リンク、転載や複製などについては『サイト案内』をご覧ください。Powered by HIMMEL

I ♥ Validator