2014年2月7日星期五

Dart 數種語法特色組成完美 Proxy   [+/-]

Ticore's Blog

Civilizing Web Programming with Dart
看到 Dart 實作的代理物件 Proxy 方式
僅僅只需要少量的程式碼
就能夠很容易地做出自己的 Proxy
完全不需要繼承任何特殊的類別
這真的非常神奇~!

甚至能夠 Mimic 對象物件介面通過型別檢查
代理對象程式碼提示
與其它原始物件做二元運算子混和操作
仔細研究之後,發現它包含了許多原本已知 Dart 語法特色

mirror
即是反射 reflection 功能,Dart 提供了多種反射方式
InstanceMirror, TypeMirror, MethodMirror... 等
在這裡用到 InstanceMirror 配合 noSuchMethod Invocation
委任呼叫到反射對象上

noSuchMethod
只要物件宣告 noSuchMethod,任何對物件存取呼叫未宣告的動作
都會被轉向到此方法,得到一個 Invocation 物件
能知道呼叫對象名稱、參數等各種資訊

class as interface
類別即是介面,這乍看之下有點雞肋的特色
其實非常有用,除了 Mock Class 之外
在 Proxy 功能上也提供了很大幫助
因為通常要代理的對象物件不見得會有抽離介面
但是在 Dart 類別即是介面
Proxy 類別可以很容易實作代理對象介面
通過需要型別檢查的地方

overloading operator
Dart 是少數提供過載運算子的動態語言
在這個強大語法特色加持下
許多運算子呼叫都能被當作函式一樣被委任
Proxy 也能代理運算子動作

annotation
類似 Metadata
Dart 額外提供了一個 proxy object
只要類別標註 @proxy annotation
語法分析器就會知道該類別是一個 Proxy
進而忽略相關未宣告方法的錯誤

以下是 Dart Proxy 例子:

import "dart:mirrors";

// 作為被代理的對象類別
class Point {
  int x, y;
  Point(this.x, this.y);
  Point operator +(a) => new Point(x + a.x, y + a.y);
  Point operator -(a) => new Point(x - a.x, y - a.y);
  toString() => "Point($x , $y)";
}


// 尚未實作代理對象介面的 Proxy
// @proxy 表示是 Proxy 物件,進行介面沒有的操作不會產生警告
@proxy
class Proxy {
  InstanceMirror targetMirror;
  Proxy(target) : targetMirror = reflect(target);
  noSuchMethod(Invocation inv) => targetMirror.delegate(inv);
  toString() => targetMirror.reflectee.toString();
}


// 已經實作代理對象介面的 Proxy,能通過型別檢查
class PointProxy extends Proxy implements Point {
  PointProxy(target) : super(target);
  
  // noSuchMethod 表示已經隱含實作一切介面所需東西,不會產生未實作警告
  noSuchMethod(Invocation inv) => super.noSuchMethod(inv);
}


void main() {

  print(new Proxy(new Point(0, 0))); // Point(0 , 0)
  print(new Proxy(new Point(2, 0)) + new Point(0, 1)); // Point(2 , 1)
  Point p1 = new PointProxy(new Point(2, 0));
  print(p1 + new Point(0, 1));  // Point(2 , 1)
  
}
Read more...

2013年12月25日星期三

Dart 常數 Immutable 物件上附加 Mutable 成員   [+/-]

Ticore's Blog

承前一篇 Dart 類別只要一用到常數建構子,所有的成員欄位都必須要 const
就算成員是 Map or List 內部資料也全部都要 const (transitively immutable)
萬一之後突然需要 mutable member 會變得很麻煩

我測試了許多寫法都很難跳脫 const
後來想到一種方式將常數物件自身當作 key,配合利用另一個 model 容器存取資料

/*
 * 在常數 Immutable 物件上附加 Mutable 成員
 */

class Model {
  static final map = new Map();
  static get(obj) => map.putIfAbsent(obj, () => {});
}



class ImmutableObj {
  final int i;
  const ImmutableObj(this.i);
  
  String get name => Model.get(this)['name'];
  set name(value) => Model.get(this)['name'] = value;
}





void main() {
  var c1 = const ImmutableObj(1);
  c1.name = "Const Obj 1";
  print(c1.name); // Const Obj 1
  
  var c2 = const ImmutableObj(2);
  func(); // null
  c2.name = "Const Obj 2";
  func(); // Const Obj 2
}

void func() {
  print(const ImmutableObj(2).name);
}

本來想再進一步將 Mutable Part 寫成 Mixin
結果執行過不了~~ 只能等看看之後會不會修改
Issue 9745: Make mixins forward const constructors.

Simon 介紹也可以用 Expando 來做

class ImmutableObj {
  final int i;
  const ImmutableObj(this.i);
}


class ExpObj {
  static Expando exp = new Expando("ExpObj");
  
  factory ExpObj(obj) {
    if (exp[obj] == null) {
      exp[obj] = new ExpObj._();
    }
    return exp[obj];
  }
  
  ExpObj._();
  String name;
}



void main() {
  var c1 = const ImmutableObj(1);
  new ExpObj(c1).name = "Const Obj 1";
  print(new ExpObj(c1).name); // Const Obj 1
  
  var c2 = const ImmutableObj(2);
  func(); // null
  new ExpObj(c2).name = "Const Obj 2";
  func(); // Const Obj 2
}

void func() {
  print(new ExpObj(const ImmutableObj(2)).name);
}

2013/12/26
又想到可以將外部 Mutable Model 合併為單一 Class 的寫法

/*
 * 在常數 Immutable 物件上附加 Mutable 成員
 */

class ImmutableCls {
  
  // immutable part
  final i;
  const ImmutableCls(this.i);
  
  // mutable part
  static final _map_ = {};
  get mutable => _map_.putIfAbsent(this, () => {});
  get name => mutable["name"];
  set name (value) => mutable["name"] = value;
  
}



void main () {
  
  print(const ImmutableCls(1) == const ImmutableCls(1));
  print(const ImmutableCls(1).name); // null
  const ImmutableCls(1).name = "Const Obj 1";
  print(const ImmutableCls(1).name); // Const Obj 1
  
}
Read more...

2013年12月23日星期一

Dart Singleton by factory and const Construcctor   [+/-]

Ticore's Blog

Dart 語言建構子部分包含很多特色
甚至連 factory pattern 都內建在語言中
可是卻沒有提供 singleton pattern
當然可以像傳統 OO 語言方式自行實作

Dart singleton by factory:

class Singleton {
  factory Singleton() => _ins_;
  const Singleton._internal_();
  static Singleton _ins_ = new Singleton._internal_();
}

void main() {
  print(new Singleton() == new Singleton()); // true
  print(identical(new Singleton() , new Singleton())); // true
}

不過想到 Dart 語言提供了常數建構子 (const constructor) 保證參數一樣可得到相同實體的特色
或許可以用來實作 singleton pattern
但常數建構子無法限制外部使用 new 實體化
故還需要配合限制私有存取以及 factory 來達成 singleton pattern

class Singleton {
  factory Singleton() =>
    const Singleton._internal_();
  const Singleton._internal_();
}

void main() {
  print(new Singleton() == new Singleton()); // true
  print(identical(new Singleton() , new Singleton())); // true
}

Dart 語法真的非常簡潔漂亮,短短幾行程式碼包含許多設計模式在裡面
factory pattern、constant constructor、named constructor
稍加變化組合就能產生出新的模式

不過後來又發現這種寫法會遇到一個問題是...
用了常數建構子等於所有成員欄位都必須是 final
沒有地方可以放可變動資料了
本來想要嘗試放到 function scope 去
在 scope 內宣告 getter/setter 指定給成員
結果目前 dart 所有 function 都視為 non-constant
不能指定給 const var

相關連結:
Dart – implementing the Singleton pattern with factory constructors

Read more...

2013年7月4日星期四

用 FlashCC 開發 Dart HTML5 Game   [+/-]

這次要跟大家分享新的工具,分別是 Adobe Flash CC, Google Dart 以及 ToolKit for Dart
藉由這三套工具,稍微改變一下開發流程
就可以將以前 Flash Game 開發經驗帶到 HTML5 Game 上面來
能夠支援更多的發布平台

Flash CC 是最新一代的 Flash IDE,啟動速度變得更快且支援 64bit 架構
主要定位仍然是多媒體整合與互動設計工具
唯一不同是開發語言可以有更多的選擇
除了原生的 AS3 之外,Flash CS6 多了 CreateJS ToolKit 支援 JS 開發
而 Flash CC 則是更多了 Dart ToolKit 可以支援 Dart 語言開發


Dart 語言特色非常非常多,這裡無法一一描述
簡單說,它是新一代結構化 Web 開發語言
效能更好,更容易開發、除錯


當然也可以用來做遊戲,所以有人仿 Flash AS3 API 寫了一套遊戲框架
StageXL for Dart
讓不熟悉 Dart 的 Flash 開發者也可以快速轉移到 Dart HTML5 Game 開發

安裝好 Flash CC, Dart Editor, Dart ToolKit 工具之後就可以開始了
我是拿了七年前做過 Flash AS2 水果盤小遊戲來改寫看看

由於 Flash CC 已經不支援舊的 AS2 開發
一打開舊的 fla 就會看到警告,不過反正這部分要改用 Dart 寫了
當初做的水果盤包含九個 Slot,Slot 本身則是做成循環無接縫的影格動畫加上遮罩
這樣可以減少程式開發的負擔,不用一直算座標 


打開 ToolKit for Dart 面板,按下 Publish 把整個 fla 發布為 Dart
會發現在原本的 fla 旁邊多了一個目錄 FruitSlot-dart
這就已經是一個標準的 Dart Project
理論上會包含原本 fla 內舞台、元件庫等素材結構
但是不包含 AS 程式邏輯

接下來啟動 Dart Editor,選擇 File > Open Existing Folder
打開剛剛輸出的 Folder
Dart Editor 就會開始自動下載需要的類別庫 StageXL 等等
下載完成後,打開 web > index.html
按下 Ctrl + R 執行測試剛剛輸出的專案

可以看到 Flash 內原本的點陣圖、影格動畫、遮罩都能夠正常轉為 Dart HTML5
並且在瀏覽器上表現出來了!
這原本是在開發 HTML5 Game 最麻煩的部分
Flash CC + Dart ToolKit 提供了非常好的支援
而且原本 Flash 開發者使用經驗也能得以延續

剩下部分只有程式邏輯控制而已了
這牽涉到 Dart 新的語言,超出本篇文章的篇幅
之後有機會再做介紹
最後完成的成品如下:
Dart FruitSlot
Project 下載位置
Read more...

2013年6月7日星期五

AngularJS Directive Scope Note   [+/-]

Ticore's Blog

AngularJS 可以允許自行定義新的 directive
能以四種不同型態使用 element, attribute, class, comment
其中 attribute, class 方式,可以在同一個 element 上宣告多個 directive
每個 directive 又有各自的 scope 屬性設定

乍看之下好像 directive scope 似乎是獨立的
但是事實上~~
同一個 element 上宣告多個 directive
無論怎樣設定 scope 屬性皆共用同一個 $scope

差別僅在於:

  • 皆共用 parent scope (scope: false)
  • 皆共用 new scope (scope: true)
  • 皆共用 isolate scope (scope: {})

多組 scope 設定優先順序:isolate scope > new scope > parent scope

也因此,directive priority 處理順序也有一些限制在
要求建立 isolate scope 之後不可以再出現要求 isolate scope 或是 new scope
否則會得到

Error: Multiple directives [xxx, xxx] asking for isolated scope on: ...

以下是簡單的測試程式:

<!DOCTYPE HTML>
<html ng-app id="ng-app">
<head>
    <meta charset="UTF-8">
    <title>AngularJS App</title>
    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.5/angular.min.js"></script>
    <script>
        var app = angular.module("ng");

        var genNgDir = function(name, priority, scope) {
            app.directive(name, function factory() {
                return {
                    priority: priority,
                    scope: scope,
                    controller: function($scope) {
                        console.log(name, $scope);
                    }
                };
            });
        };

        genNgDir("myDir01", 0, {});
        genNgDir("myDir02", 1, true);
        genNgDir("myDir03", 2, false);

    </script>
</head>
<body>
    <p my-dir01 my-dir02 my-dir03>Content</p>
</body>
</html>

JSBin Edit

Read more...

2013年5月22日星期三

AngularJS Nested App   [+/-]

Ticore's Blog

AngularJS 提供 controller, directive, filter,... 等功能可以利用 module 來管理
但是即使如此,只要在同一個 app 內所有載入的 module 都會在同一個名稱空間內
東西變得複雜時候,很容易遇到名稱空間不足與衝突的問題
網路上也有人提出一些解法

Delegating Nested Directive Behavior To Parent Directive In AngularJS

不過那前提是上層 $scope.delegateDirectiveLinking
必須要了解下層所有可能會出現的 delegate directive
而且寫法有點複雜~

我想到另一種方式是在主要的 ng-app 下啟動 sub-app
兩個 app 之間名稱空間是完全獨立的
當然連帶 $scope 繼承關係也會被隔絕
這一點可以另外再想辦法做資料傳遞同步
程式碼如下:

<!DOCTYPE HTML>
<html ng-app="MainApp">
<head>
    <meta charset="UTF-8">
    <title>Nested App</title>
    <script src="../libs/angular-1.1.4.js"></script>
    <script>
        
        // 宣告 subApp directive
        (function() {
            var ngApp = angular.module("ng");
            var subAppDirective = function() {
                return {
                    restrict: "CA",
                    terminal: true,
                    link: function(scope, iElement, iAttrs, controllers) {
                        angular.bootstrap(iElement.children(), [iAttrs.subApp]);
                    }
                };
            };
            ngApp.directive("subApp", subAppDirective);
        })();


        // 宣告 MainApp module
        (function() {
            var mainApp = angular.module("MainApp", []);
            var mainCtrlFn = function($scope) {
                $scope.name = "MainApp";
                console.log("MainApp.MainCtrl();");
            };
            mainApp.controller("MainCtrl", mainCtrlFn);
        })();


        // 宣告 ChildApp module
        (function() {
            var subApp = angular.module("ChildApp", []);
            var mainCtrlFn = function($scope) {
                $scope.name = "ChildApp";
                console.log("ChildApp.MainCtrl();");
            };
            subApp.controller("MainCtrl", mainCtrlFn);

            var logDirective = function() {
                return function(scope, iElement, iAttrs) {
                    console.log("log:", iAttrs.log);
                };
            };
            subApp.directive("log", logDirective);
        })();

    </script>
</head>
<body ng-controller="MainCtrl">
    {{ name }}
    <div sub-app="ChildApp">
        <div ng-controller="MainCtrl" log="Hello!">
            {{ name }}
        </div>
    </div>
</body>
</html>

Nested App - JS Bin

Read more...