ラベル Typescript の投稿を表示しています。 すべての投稿を表示
ラベル Typescript の投稿を表示しています。 すべての投稿を表示

2013年1月13日日曜日

第三回 Typescriptでデザインパターン: Composite Pattern

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
第三回はComposite Patternを実装してみます。

Composite Patternとは


Composite Patternは木構造を伴う再帰的なデータ構造を表すことができます。同じインタフェースを持つ枝と葉が、再帰的な木構造を実現するんですねー。ファイルとディレクトリの関係がまさにComposite Patternです。
Composite パターン Wikipedia

Composite Patternを実装するには


TypescriptでのComposite Patternの実装はinterfaceがあれば実現出来ます。登場人物はComponentとLeafとCompositeです。各要素をクラス図にしてみました。LeafとCompositeはComponentインタフェースを実現しています。CompositeはComponentを集約しています。



Component


LeafとCompositeで共通となるインタフェースです。
木構造を実現する為の機能と、Leaf、Compositeが提供する機能を定義します。

Leaf


木の末端である葉を表すクラスです。

Composite


木の枝を表すクラスです。内部に葉か枝を複数持ちます。Componentの内、主に子要素を操作する機能を実現します。


Composite Patternの実装


何を作るか


ファイルとディレクトリを模した構造を作ってみます。共通のインタフェースであるComponentとファイルを表すItemクラス、ディレクトリを表すDirectoryクラスで構成されています。木構造のrootにDirectoryクラスを配置し、addメソッドでItemかDirectoryを追加して利用します。

Componentが子要素を持つDirectoryクラスか、葉であるItemかを判定する為のisItemメソッドを用意しました。このメソッドによってDirectory,Itemを判定し、再帰処理に利用します。

Componentインタフェースには子要素を操作するメソッドがいくつかあります。Itemクラスではそれらを利用しないので、呼び出した場合は例外を送出する様にしました。例外以外にも無効な値を返却するという方法も考えられます。

実装


//Composite Pattern

//component
interface Component{
  add(child:Component):bool;
  remove(child:Component):bool;
  getChildren():Component[];
  getName():string;
  isItem():bool;
}
//leaf
class Item implements Component {
  constructor(private name: string){

  }
  add(child:Component):bool{
    throw new Error("this object is Item.");
  }
  remove(child:Component):bool{
    throw new Error("this object is Item.");
  }
  getChildren():Component[]{
    throw new Error("this object is Item.");
  }
  getName():string{
    return this.name;
  }
  isItem():bool{
    return true;
  }
}
//composite
class Directory implements Component{
  items:Component[];
  constructor(private name: string){
    this.items = new Array();
  }
  add(child:Component):bool{
    this.items.push(child);
    return true;
  }
  remove(child:Component):bool{
    for (var i : number = this.items.length - 1; i >= 0; i--) {
      if(this.items[i].getName() === child.getName()){
        this.items.splice(i, 1);
        i++;
      }
    }
    return true;
  }
  getChildren():Component[]{
    return this.items;
  }
  getName():string{
    return this.name;
  }
  isItem():bool{
    return false;
  }
}

ソースはこちら
https://github.com/sys1yagi/gof_design_pattern_implemented_in_Typescript/tree/decorator_pattern/src/03.Composite%20Pattern

動作


Composite Patternを用いた木構造でファイルとディレクトリを構成し、再帰的に木を走査して全要素を表示するサンプルです。子要素はpaddingを付けるようにしています。簡単に階層構造を表現出来ます。



Composite Patternの何がおいしいか


Composite PatternはComponentを再帰的に集約する点が拡張性を高めています。今回はConpositeとItemの二つの構成でしたが、ConpositeやItemの種類を増やしたりする事も容易に出来ます。DSLを作る為のInterpreter PatternもComposite Patternの一種です。

終わりに


Webではパンくずメニューや、サイドバーなどのメニューなどで使えると思います。その他にはフォームの入力チェックなんかにも使えるんじゃないかなーと思います。

次回はDecorator Patternを実装してみたいと思います。

2013年1月10日木曜日

第二回 Typescriptでデザインパターン: Bridge Pattern

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
第二回はBridge Patternを実装してみます。

Bridge Patternとは


Bridge Patternは機能と実装を分け、それらを「橋渡し」する事で設計の柔軟性を確保する為のパターンです。
Bridge パターン Wikipedia

Bridge Patternを実装するには


TypescriptでのBridge Patternの実装はinterfaceと抽象クラスが必要となります。Typescriptでは抽象クラスがありません。今回は以下の様な実装にして擬似的に抽象クラス的なものを実現しています。
class Pattern{
  //略
  draw(canvas:HTMLCanvasElement){
    throw new Error("not yet implemented.");
  }
}

Patternをインスタンス化してdrawを呼ぶと死にます。この実装の場合実行時にエラーを吐く事になるので、コンパイル時に型チェックなどをするTypescriptの恩恵を受けられません。

Bridge Patternの実装


何を作るか


図形を描画するShapeと、Shapeをルールにしたがって動かすPatternを作ってみます。
今回Shapeは四角形と円にしました。
Patternは左右移動とランダム移動を作りました。

ソースはこちら
https://gist.github.com/4494073

動作は以下。


Bridgeパターンの何がおいしいか


この図形描画の機能に、図形を追加したい場合どうすればよいでしょうか?簡単ですね。Shapeをimplementsしたクラスを作れば良いです。Pattern側には何も影響を与えません。Pattern側も同様に、動きのバリエーションを増やす時、Shapeの事を考えなくて済みます。「橋渡し」の仕組みによって互いに疎である事を実現していますね。

終わりに


今回もTypescriptのクラス、インタフェースの仕組みによって自然と設計、実装が出来ました。可読性の面でも、jsにコンパイルされた方はprototypeとか__extendsとか色々余計なキーワードが出てきてややこしく感じます。Typescriptの方はclassの定義や継承関係などもはっきり書かれていて簡潔なため読みやすいです。

また、仕様を変更した時にコンパイルエラーによって事前にエラーを何度か発見する事が出来ました。昔はalertやconsole.logやらChomeのDeveloper Toolsなどで実行しながら挙動を追う事をしていました。Typescriptならコンパイルの際に色々と検査してくれるので凄く助かります。

次回はComposite Patternを実装してみたいと思います。

2013年1月6日日曜日

第一回 Typescriptでデザインパターン: Adapter Pattern

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク


Typescriptでデザインパターンをやる。
どうせなのでGoF網羅しよう。interpreter patternはちょっと無理かもしれませんが。

Adapter Patternとは?


Adapter パターン
Adapter パターンを用いると、既存のクラスに対して修正を加えることなく、インタフェースを変更することができる。Adapter パターンを実現するための手法として継承を利用した手法と委譲を利用した手法が存在する。

だそうです。

Adapter Patternを実装するには?


Adapter Patternはinterfaceがあればやれるはず、Typescriptにinterfaceはあるか?

ある。
interface Logger{
 log(msg: string): void;
}

こういう感じで書ける。interfaceはtscでコンパイルする時にだけ使われる。
interfaceをimplementsしているclassがintefaceを満たすかチェックしたり、interfaceを取り扱っている部分での型チェックなどを行う。コンパイル後のjsにはinterfaceは出力されない。javascriptにはinterfaceに準ずる機能はないので当然かもしれない。

このintefaceを使えばAdapter Patternを実装する事が出来そうだ。

実装


javascriptでログ出力を行う方法はパッと以下の3つが考えられる。
  • アラートで出す
  • コンソールに出す
  • ログ用要素に出す


  • それぞれで出力の仕方が異なるのでAdapter Patternを使ってログ出力のインタフェースを統一する。

    実装は以下。intefaceとしてLoggerを定義し、それぞれの出力方法のAdapterを作る。ぞれぞれのLoggerはLogAdapterFactoryを通して受け取る。LogAdapterFactoryにはLOGGER_TYPEを渡す。Typescriptにenumはないので擬似でLOGGER_TYPEというのを作った。内部的には唯のstringなのでちょっとあれだけど知らん。



    使う


    Loggerを使ってみる。LogAdapterFactory.createLogger(LOGGER_TYPE.DISPLAY);のLOGGER_TYPEを変えればそれぞれの出力に切り替わる。



    終わりに


    javascriptでこれを実現するコードを書くのは別に難しくない、けど、今までそういう事をしようとした場合「js的にこうでいいかなぁ?」と毎回悩んでいた。Typescriptだとこの辺りをすっ飛ばして設計に専念しやすいと感じる。

    Typescriptええで

    2012年12月23日日曜日

    Typescriptでライフゲーム

    このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

    jsdo.itで動くのがこれ。ソースは下


    あんまりぽくない気もするけど書きやすかった感がある。
    /**
    * ライフゲームの世界。ボード
    */
    class World{
     interval: number = 10;
     canvas: HTMLCanvasElement;
     ctx: CanvasRenderingContext2D;
     cells: Cell[][];
     constructor(public width:number, public height:number){
      this.canvas = document.createElement("canvas");
      this.canvas.width=width*Cell.cellSize;
      this.canvas.height=height*Cell.cellSize;
      this.ctx = this.canvas.getContext("2d");
      document.body.appendChild(this.canvas);
      this.cells = new Array(width);
      for(var i = 0; i < width; i++){
       this.cells[i] = new Array(height);
       for(var j = 0; j < height; j++){
        var mo = Math.floor( Math.random()*10)%2;
        this.cells[i][j] = new Cell(mo==0);
       }
      }
     }
     timeGoseBy(){
      for(var i = 0; i < this.width; i++){
       for(var j = 0; j < this.height; j++){
        this.cells[i][j].draw(this.ctx, i, j);
       }
      }
      for(var i = 0; i < this.width; i++){
       for(var j = 0; j < this.height; j++){
        this.cells[i][j].nextGeneration(i, j, this);
       }
      }
      this.start();
     } 
     start(){
      var self = this;
      setTimeout(function(){self.timeGoseBy();}, 50);
     }
    }
    /**
    * 細胞
    */
    class Cell{
     static cellSize: number = 4;
     public now: bool = false;
     public next: bool = false;
     constructor (next: bool){
      this.next = next;
      this.now = next;
     };
     leftUp(x:number, y:number, world:World){
      if(x-1 < 0){
       x = world.width;
      }
      if(y-1 < 0){
       y = world.height;
      }
      return world.cells[x-1][y-1].now ? 1 : 0;
     }
     left(x:number, y:number, world:World){
      if(x-1 < 0){
       x = world.width;
      }
      return world.cells[x-1][y].now ? 1 : 0;
     }
     leftDown(x:number, y:number, world:World){
      if(x-1 < 0){
       x = world.width;
      }
      if(y+1 >= world.height){
       y = -1;
      }
      return world.cells[x-1][y+1].now ? 1 : 0;
     }
     rightUp(x:number, y:number, world:World){
      if(x+1 >= world.width){
       x = -1;
      }
      if(y-1 < 0){
       y = world.height;
      }
      return world.cells[x+1][y-1].now ? 1 : 0;
     }
     right(x:number, y:number, world:World){
      if(x+1 >= world.width){
       x = -1;
      }
      return world.cells[x+1][y].now ? 1 : 0;
     }
     rightDown(x:number, y:number, world:World){
      if(x+1 >= world.width){
       x = -1;
      }
      if(y+1 >= world.height){
       y = -1;
      }
      return world.cells[x+1][y+1].now ? 1 : 0;
     }
     up(x:number, y:number, world:World){
      if(y-1 < 0){
       y = world.height;
      }
      return world.cells[x][y-1].now ? 1 : 0;
     }
     down(x:number, y:number, world:World){
      if(y+1 >= world.height){
       y = -1;
      }
      return world.cells[x][y+1].now ? 1 : 0;
     }
     nextGeneration(x:number, y:number, world:World){
      var aroundLivingCount = 0;
      aroundLivingCount += this.rightUp(x, y, world);
      aroundLivingCount += this.right(x, y, world);
      aroundLivingCount += this.rightDown(x, y, world);
      aroundLivingCount += this.leftUp(x, y, world);
      aroundLivingCount += this.left(x, y, world);
      aroundLivingCount += this.leftDown(x, y, world);
      aroundLivingCount += this.up(x, y, world);
      aroundLivingCount += this.down(x, y, world);
      switch(aroundLivingCount){
       case 0:
       case 1:
       case 4:
       case 5:
       case 6:
       case 7:
       case 8:
        this.next = false;
        break;
       case 2:
        break;
       case 3:
        this.next = true;
        break;
      }
     }
     draw(ctx: CanvasRenderingContext2D, x: number, y: number){
      this.now = this.next;
      if(this.next){
       ctx.fillStyle = "#000000";
      }
      else{
       ctx.fillStyle = "#ffffff";
      }
      ctx.fillRect(x*Cell.cellSize, y*Cell.cellSize, Cell.cellSize, Cell.cellSize);
     }
    
    }
    window.onload=function(){
     var w = new World(100, 100);
     w.timeGoseBy();
    }