[pureMVC] Javascript version – bình mới rượu cũ

Cách đây vài năm, khi Flash/Flex development còn phổ biến thì pureMVC được xem như là một trong những framework mẫu mực dành cho công nghệ này.  Nếu bạn từng là một flash/flex developer, bạn chắc hẳn đã từng sử dụng hoặc nghe về framework này. Bản thân pureMVC – như tên của nó làm một framework thuần túy theo pattern MVC nên nó được port sang rất nhiều các ngôn ngữ phổ biến như Java, C#, Python … Javascript cũng không ngoại lệ.

Một số thống kê gần đây cho thấy pureMVC không nằm trong danh sách các framework javascript phổ biến như thống kê dưới đây chẳng hạn.

Theo quan điểm cá nhân của tôi thì bản thân pureMVC không thật sự hỗ trợ tốt cho HTML (tương tác với DOM object, hỗ trợ HTML template, partial …). Tuy được port sang nhiều ngôn ngữ nhưng nhóm tác giả pureMVC lại không cập nhật và phát hành thường xuyên, có lẽ vì ít người sử dụng nên hầu như không có nhiều feedback cũng như bug report.PureMVC là một framework gắn liền với Flash /ActionScript nên các lập trình viên HTML/JS cũng ít quan tâm đến framework này hơn. Bản thân tôi khi chuyển sang HTML5/JS cũng đã chọn lựa AngularJS, BackboneJS, KnockoutJS cho các project của mình hơn là pureMVC.

Điểm hấp dẫn của pureMVC chính là tính “thuần” MVC pattern của nó – bạn có thể dùng pureMVC để kết hợp với các framework Javascript chưa có MVC như CreateJS, KinectJS để từ đó tạo ra các project có cấu trúc tốt và dễ bảo trì.  Bạn có thể tham khảo source từ puremvc.github.io  hoặc từ app-seed project mà tôi đang thực hiện.

Tagged with: , , , , ,
Posted in Design Pattern, HTML5/JS/CSS, MVC, PureMVC, Unit Testing

[Android] ADB is unabled to connect my China phone – HiSense U950 (aka HiSense F1)

That was crazy time !

I just bought a cheap android phone for my incoming projects. Yes, budget was my very first issue that I could not bought expensive phone such as Samsung Galaxy S3, or Note 2 (just my dream) . So I decided to buy a cheap phone – Hisense F1, which price is arround 110 usd. My Android development experience takes me to this site, then download the USB driver. But no look, the link is just … broken, and it seems about the smart TV product, not smartphone !!!

A guy from tinhte.vn just posted some stuff rooting this phone, so I could found the link to download its driver.  So I thought just downloading driver, then everything would work well as I did with my HTC Cha Cha or Motorola Droid before.  Opening the adb and check connection

C:\Android\android-sdk\platform-tools>adb kill-server
C:\Android\android-sdk\platform-tools>adb start-server
* daemon not running. starting it now on port 5037 *
OK
* daemon started successfully *
C:\Android\android-sdk\platform-tools>adb devices
List of devices attached

The list was just empty !!!

I know the most problems that ADB could not see the device is driver compatible, but Windows shows me that driver has been installed correctly ! Dammit …   So I came back to Android development site, read carefully about “Hardware devices” and noticed on USB Vendor ID. Just remembering that the last time I installed driver for HTC Cha Cha,  adb_usb.ini file (C:\Users\<my user name>\.android\adb_usb.ini ) has been changed for some reason, and it contains USB Vendor ID list.

# ANDROID 3RD PARTY USB VENDOR ID LIST -- DO NOT EDIT.
# USE 'android update adb' TO GENERATE.
# 1 USB VENDOR ID PER LINE.
0x0502
0x05C6
0x0BB4
0x0FCE
0x1004
0x18D1
0x19D2
0x22B8

Yes, I did not contain the vendor ID for Hisence phones !

So I just added “0x109b” at the ending of file and restarted the ADB

C:\Android\android-sdk\platform-tools>adb kill-server
C:\Android\android-sdk\platform-tools>adb devices
* daemon not running. starting it now on port 5037 *
OK
* daemon started successfully *
List of devices attached 
u950 device

Now, ADB can see my device …

 

 

Tagged with: , , ,
Posted in Android, Ice Cream Sandwich 4.0

[Sencha Ext JS 4] Unit Testing với Jasmine

Jasmine là một BDD (Behavior Driven Development) test framework có thể tích hợp vào nhiều môi trường khác nhau. Trong bài viết trước, tôi có nhắc đến Jasmine cùng với khái niệm về TDD. Thực tế thì BDD là một bản mở rộng của TDD mà thôi. Bạn có thể xem thảo luận về BDD và TTD tại đây. Về Unit testing, bạn có thể tìm hiểu qua rất nhiều các bài viết hoặc ebook nên tôi không đi vào quá chi tiết việc trình bày các khái niệm về unit testing. Quan điểm của tôi về unit testing như sau:

  • Unit testing để làm rõ và kiểm tra tính đúng đắn của specs
  • Unit testing cho thấy khả năng chia nhỏ các feature mà bạn đang hiện thực nó
  • Unit testing không thích hợp cho test UI, tốt nhất là áp dụng unit testing cho logic của ứng dụng. Bạn đặt logic của ứng dụng ở đâu (Model ? View ? ) thì hãy áp dụng unit testing ở đó.

Với ExtJS, hướng dẫn về unit testing được trình bày tương đối đơn giản và ngắn gọn, bạn nên đọc qua trước khi bắt đầu viết unit testing.  Bài viết sử dụng ứng dụng FlickrSearch  mà tôi đã trình bày từ trước, bạn có thể tìm hiều qua ứng dụng FlickrSearch. Phần trình bày về Jasmine sẽ tâp trung vào việc viết test cho 2 module Search Photo và màn hình Login. Trường hợp màn hình login là một thử nghiệm vì tôi không nghĩ mình sẽ viết unit testing cho phần UI như quan điểm trình bày ở trên.

Cài đặt Jasmine

Tôi sử dụng phiên bản Jasmine 1.3.1 - là phiên bản mới nhất tại thời điểm này. Bạn có thể thấy workspace của tôi khác một chút so với các ví dụ của ExtJS. Đơn giản là tôi thực hiện unit testing cho chính source code mà tôi đang viết.

jasmine workspace

Những module được đánh dấu là những phần thuộc về unit test. Như vậy bạn cần phải copy jasmine 1.3.1 vào project của mình. Folder specs là nơi đặt các test-case, app-test.js và run-test/html là các file để khởi động ứng dụng test.

app-test.js

File này khởi tạo môi trường chạy của jasmine cũng như khởi tạo các report cho các test run

Ext.Loader.setConfig({enabled:true});
Ext.require('Ext.app.Application');

var Application = null;
Ext.onReady(function() {
      Application = Ext.create('Ext.app.Application', {
      name: 'FlickrSearch',
      appFolder: 'app',
      controllers: ['ScreensController'],
      launch: function() {
         var htmlReporter = new jasmine.HtmlReporter();
         jasmine.getEnv().addReporter(htmlReporter);
         jasmine.getEnv().execute();
      }
 });
});

Test suite cho PhotoStore

Một test-suite bao gồm nhiều test-case, trong trưởng hợp này tôi tạo test-suite cho PhotoStore và định nghĩa một số test-case trong đó.

describe("Photo", function() {
    var controller = null;
    var store = null;
    beforeEach(function() {
        if (!controller) {
           controller = Application.getController("ScreensController");
        }
        if (!store) {
           store = controller.getStore('PhotoStore');
        }
   });
});

beforeEach (cùng với afterEach) là các hàm global – những hàm này sẽ được gọi trước (beforeEach) và sau (afterEach) mỗi test-case. Các test-case có cùng điều kiện khởi tạo và kết thúc có thể sử dụng các hàm này để khời tạo cũng như kết thúc test-case. Mục đích là để tránh lập lại các đoạn code dành cho khời tạo, kết thúc …  Trong trường hợp trên, mỗi test-case cần khởi tạo lại store và controller, nếu đặt các đoạn code này trong từng test-case sẽ dẫn đến việc các đoạn code này bị lập lại – bạn sẽ vi phạm nguyên tắc DRY – Don’t Repeat Yourself

Một số test-case cho PhotoStore

Kiểm tra việc khởi tạo controller

it("controller must be defined",function(){
 expect(controller).toBeDefined();
 });

Kiểm tra việc khởi tạo store

it("Store must be definded", function(){
 expect(store).toBeDefined();
 });

PhotoStore không phải là 1 store autoLoad, store này được kích hoạt thông qua việc gọi hàm load nên trong trường hợp này, thuộc tính autoLoad phải là false.

it("Store should not auto load",function(){
 expect(store.autoLoad).toEqual(false);
 });

test-case dưới đây minh họa việc test bất đồng bộ – store thực hiện một request tới server và chờ server response về . Giả định là request này sẽ phải thực hiện trong vòng 4 giây và nếu quá thời gian này, test -case sẽ fail

it("sets up latency configuration and receives latency", function() {
 runs(function() {
 store.load({params: { text: 'red' }});
 }, "an asynchronous method");
waitsFor(function() {
 return store.isLoading() == false;
 }, "store loading complete", 4000);
 });

Sau khi thực hiện request đến server và có response trong vòng 4 giây, kiểm tra kết quả store có dữ liệu hay không

it("Store should have photos", function() {
 expect(store.getCount()).toBeGreaterThan(0);
 });

Unit-test với UI

Theo quan điểm của tôi, UI có thể quá phức tạp để thực hiện unit-test. Với UI, nên sử dụng các phương pháp automation test sẽ có hiệu quả hơn ? Bạn có thể tham khảo thêm test-suite “authentication” mà tôi dùng để test UI. Kết quả chạy có thể thấy như sau

jasmine

Tagged with: ,
Posted in HTML5/JS/CSS, Jasmine, Sencha Ext JS 4, tiếng Việt, Web & AIR

[Sencha ExtJS 4] Xây dựng ứng dụng Flickr Search

Trong bài viết trước tôi đã trích dẫn một phần nhỏ của ứng dụng Flickr Search mà tôi đang viết demo. Từ loạt vbài này, tôi sẽ cố gắng trình giới thiệu từng bước để có thể xây dựng một ứng dụng Flickr Search tương đối hoàn chỉnh trên nền ExtJS 4.2 . Loạt bài viết này cũng không trình bày cách thức cài đặt môi trường làm việc với ExtJS, cũng như đi quá sâu vào việc trình bày xây dựng 1 ứng dụng MVC từ con số 0. Bạn cần phải nắm về MVC pattern, Javascript để có thể theo dõi loại bài này hoặc có thể để lại comment cho bất kỳ mục nào mà bạn không hiểu hoặc chưa rõ. Nhiều bạn từng hỏi tôi về IDE /application server ?  Tôi đang sử dụng WebStorm 5.0 để coding và NodeJS để deploy ứng dụng của mình. Bạn có thể tải trước mã nguồn của ứng dụng tại đây.

 

Ứng dụng này sẽ gồm những gì ?

Màn hình login – hiện tại nó không có tác dụng gì ngoài minh họa cho việc quản lý nhiều màn hình khác nhau.

login

Màn hình PhotoSearch – màn hình này sẽ bao gồm 1 toolbar, bạn sẽ gõ từ khóa tìm kiếm ở đây

search

Ngoài ra, khi bạn click lên hình thumbnail, một cửa sổ popup hiện ra cho phép bạn xem hình ở kích thước lớn hơn

popup

Bạn cần xây dựng những lớp nào ?

Trước hết, hãy xem qua screenshot project workspace của tôi

project

 Hãy chia ứng dụng của bạn ra các package tương ứng với M – Model , V – View , C – Controller. Dễ dàng thấy 3 màn hình mà tôi trình bày ở trên nằm lần lượt trong package view.  Trong project này tôi cũng sử dụng unit testing với Jasmine 1.3.1, tuy nhiên tôi không có ý định xây dựng ứng dụng này theo phương pháp TDD. TDD với ExtJS sẽ được trình bày trong một bài viết khác cùng với kỹ thuật Automation. Bạn có thể tham khảo file app-test.js, các file trong package specs cũng như chạy thử run-test.html.

Khởi tạo ứng dụng – app.js

Ext.Loader.setConfig({enabled:true});
Ext.application({
     requires: ['Ext.container.Viewport'],
     name: 'FlickrSearch',
     appFolder: 'app',
     controllers: ['ScreensController'],
     launch: function() {
         var viewport = Ext.create('Ext.container.Viewport', {
         layout: 'card',
         items: [
               {
                 xtype: 'authentication'
               },
               {
                 xtype: 'photosearch'
               }
         ]
      });
    this.on({
           signin: function() {
                 viewport.getLayout().setActiveItem(1);
          },
          signout: function() {
                viewport.getLayout().setActiveItem(0);
          }
    });
   }
});

Bạn cần quan tâm đến layout: ‘card’ - layout này có thể quản lý nhiều view (hoặc component)  - các view hoặc component sẽ có kích thước fit với ViewPort và tại mỗi thời điểm chỉ có 1 view hoặc component được hiển thị. Để chuyển qua view khác, gọi setActiveItem và truyền vào index thích hợp. Ứng dụng hiện tại có 2 view – authentication (index = 0) và photosearch (index = 1)

signin signout là 2 sự kiện được gửi từ ScreensController, application bằt 2 sự kiện này để thực hiện việc chuyển đổi giữa các màn hình

ScreensController

Controller này hoạt động theo đúng như tên của nó – handle hầu hết các action giữa các màn hình. Nếu bạn nhìn vào các code của các View, bạn sẽ không thấy các phần logic, truy cập store …ScreensController đã làm việc này. Theo quan điểm của tôi, view thuần túy chỉ định nghĩa phần giao diện, phần logic của nó nên chuyển sang cho controller hoặc model.

Tất cả các view do ScreensController quản lý cần được khai báo trong mảng views

 

 views:['AuthenticationView', 'PhotoSearchView', 'PhotoView'],

ScreensController giữ tham chiếu đến các view thông qua khai báo các tham chiếu đến từng view trong mảng refs

refs: [
        { ref: 'authentication', selector: 'authentication'},
        { ref: 'photosearch', selector: 'photosearch'}
 ],

Sau khi khai báo, ExtJS sẽ tự sinh ra getter cho view tương ứng – getter dựa trên selector với ký tự đầu tiên được viết hoa cùng với từ khóa get được thêm vào ở phía trước . Ví dụ, để tham chiếu đến photosearch, ta sẽ gọi

view = this.getPhotosearch();

từ đây, bạn có thể truy cập đến các hàm, biến được định nghĩa trong photosearch

Ở trên, tôi có trình bày trong app của chúng ta đang handle 2 sự kiện là sigin và signout. 2 sự kiện này được khởi động trong controller, thực tế thì đến từ 2 handler của các action trong các view do ScreensController quản lý

 signinHandler: function() {
    this.application.fireEvent("signin");
 },
 signoutHandler: function() {
    this.application.fireEvent("signout");
 },

PhotoModel và PhotoStore

Tham khảo đến bài viết trước để có cái nhìn tổng quan hơn về 2 class này

 

PhotoSearchView

Chú ý đến XTemplate được sử dụng trong view này. Tôi đã mất 1 tiếng để debug khi không thể render được các item từ XTemplate vì chưa khai báo itemSelector ( itemSelector: ‘div.thumb-wrap’, ). Với các phiên bản cũ của ExtJS thì itemSelector không bắt buộc (hoặc không có ?) nhưng với ExtJS 4.2 thì đây là thuộc tính bắt buộc. Tốt nhất khi sử dụng 1 component, class của ExtJS, bạn nên tham khảo document – mục config để xem các trường nào là bắt buộc.

Một số điểm cần chú ý

  • các view được khai báo trong controller sẽ được khởi tạo sẵn. Tôi bẫy sự kiện active của photosearch để xóa các kết quả search cũ khi màn hình này được active
  • alias được dùng để truy cập view từ controller
  • Ext.widget sẽ tạo ra instance mới – đừng hiểu nhầm đó là cách tham chiếu đến view.

 

Kết luận

ExtJS là một framework rất tốt để xây dựng các ứng dụng Rich Internet, đồng thời cũng là đối thủ nguy hiểm của Flex. Nếu bạn theo dõi blog của tôi từ trước, bạn có thể dễ dàng thấy tôi đang sử dụng ExtJS để thay thế Flex :)

 

 

 

Tagged with: , , , ,
Posted in HTML5/JS/CSS, Sencha Ext JS 4, tiếng Việt

[Sencha ExtJS 4] Truy cập Flickr Search API

Nếu bạn bắt đầu tiếp cận với ExtJS 4 và tham khảo bài viết của Evan Trimboli về việc truy cập Flickr Search API thì có thể bạn sẽ gặp 1 chút trở ngại với ExtJS 4. Blog này viết cho các phiên bản cũ của ExtJS – có thể là 3.x nên không còn đúng khi viết trên nền Ext JS 4.0

Tôi không tìm cách port lại các class Proxy cũng như JSON Reader mà chỉ đưa ra một cách tiếp cận khác, đó là hiện thực lớp PhotoStore để thực hiện việc truy cập Flickr Search API.

Ext.define('FlickrSearch.store.PhotoStore', {
          extend: 'Ext.data.Store',
          model: 'FlickrSearch.model.PhotoModel',
          proxy: {
                type: "jsonp",
                url: "http://api.flickr.com/services/rest/",
                callbackKey: 'jsoncallback',
                actionMethods: {
                    read: 'POST'
                },
           extraParams: {
               api_key: '7bcd4d0a48fc6fe0e86ec660f95541a9',
               method: 'flickr.photos.search',
               format:'json',
               nojsoncallback:'1'
           },
          reader: {
              type: 'json',
              root: 'photos.photo',
              totalProperty: 'photos.total',
              id: 'id'
          }
      },
});


// Model
Ext.define('FlickrSearch.model.PhotoModel',{
            extend: 'Ext.data.Model',
            fields:[
                    { name:'id'},
                    { name:'owner'},
                    { name:'secret'},
                    { name:'server'},
                    { name: 'farm', type: 'int' },
                    { name:'title'},
                    { name: 'ispublic', type: 'boolean' },
                    { name: 'isfriend', type: 'boolean' },
                    { name: 'isfamily', type: 'boolean' }
       ]});

 

Để thực hiện search 1 term với Flickr API,

doSearch: function(term) {
       Ext.getCmp('images-grid').store.load({params: { text: term }});
 }

Nhận đinh: 4.2.0 có thể nói là phiên bản update toàn diện của Ext JS và việc tương thích với ExtJS 3.x là rất thấp.

Tagged with: , , , , , ,
Posted in HTML5/JS/CSS, Sencha Ext JS 4, tiếng Việt, Web & AIR

ANT & Flex Application: đôi khi Flash Builder không phải là tất cả ;)

Tôi có dịp trao đổi về chuyên môn với 1 số flex developer mà tôi gặp trong quá trình làm việc. Và thật bất ngờ khi phần lớn họ đều phụ thuộc quá nhiều vào Flex (Flash) Builder cho việc build ứng dụng và không mấy hào hứng với các công cụ build khác như mxmlc, hfcd … có vẻ các công cụ không mấy trực quan này không phải là lựa chọn hàng đầu cho các lập trình viên flex, hoặc cũng có thể do tính chất các project mà họ đang làm không thực sự phải dùng đến các công cụ build khác ngoài tính năng build đã có sẵn trong Flex (Flash Builder). Thực sự tôi cũng không có đủ kinh nghiệm để giải thích một cách bài bản khi nào nên dùng các công cụ khác ngoài Flex (Flash) Builder. Nhưng với project thực tế mà tôi đang làm việc, một automation tester cần chỉ cần build application hàng ngày thì việc mở Flash (Flex) Builder chỉ để build thì chả mấy khả quan tí nào.

Kịch bản mà tôi cần giải quyết đó là xây dựng một cơ chế tự động để có thể tự động check-out, merge source code, sau đó build và tự deploy lên server. Công cụ automation mà tôi đang sử dụng là RIATest (sẽ có một bài viết về mảng RIATest – FlexMonkey sau khi tôi hoàn thành việc đánh giá và so sánh 2 công cụ này).  Hệ thống tự động cần build 1 phiên bản application thông thường và một phiên bản tích hợp với các automation library, automation agent và deploy lên 2 server khác nhau. Tôi không có nhiều kinh nghiệm với ANT, ngay cả sau khi đã tốn khá nhiều thời gian với MAVEN để giải quyết một số trục trặc trong quá trình build tự động. Nhưng cá nhân tôi lại thấy cách tiếp cận của ANT dễ dàng hơn rất nhiều so với MAVEN.

Trước hết, nội dung của file build.xml

<?xml version="1.0" encoding="UTF-8"?>
<project name="webviewer" default="riatest" basedir=".">
	<property file="settings.properties" />
	<!-- path to the flex task libraries. -->
	<path id="flextasks.classpath">
		<fileset dir="${FLEX_HOME}/ant/lib">
			<include name="*.jar" />
		</fileset>
	</path>

	<typedef resource="flexTasks.tasks" classpathref="flextasks.classpath" />
	<target name="riatest" depends="riatest.compile.web,deploy.web" />

	<target name="clean">
		<echo message="Cleaning Build Space" />
		<delete dir="${base.dir}/${build.dir}" />
	</target>

	<target name="deploy.web">
		<copy file="${swfFile}" tofile="${swfFileDomain}" />
	</target>

	<target name="riatest.compile.web" depends="clean">
		<echo message="Compiling Project with RIATest" />
		<mxmlc file="${projectFile}" output="${swfFile}" locale="en_US">
			<load-config filename="${FLEX_HOME}/frameworks/flex-config-riatest.xml" />
			<source-path path-element="${FLEX_HOME}/frameworks" />
			<source-path path-element="${resources.dir}" />
			<source-path path-element="${locales.dir}/{locale}" />
			<source-path path-element="${dev.dir}" />
			<static-link-runtime-shared-libraries />
			<compiler.library-path dir="${FLEX_HOME}/frameworks" append="true">
				<include name="libs/*" />
			</compiler.library-path>
			<compiler.include-libraries dir="${libs.dir}" append="true">
				<include name="cairngorm-2.2.1.swc" />
			</compiler.include-libraries>
			<compiler.include-libraries dir="${libs.dir}" append="true">
				<include name="flexspy-1.2.swc" />
			</compiler.include-libraries>
			<compiler.include-libraries dir="${automationlibs.dir}" append="true">
				<include name="automation_dmv.swc" />
				<include name="automation.swc" />
				<include name="automation_agent.swc" />
				<include name="automation_spark.swc" />
				<include name="riatestagent.swc" />
			</compiler.include-libraries>
			<theme dir="${FLEX_HOME}/frameworks/themes/Halo">
				<include name="halo.swc" />
			</theme>
		</mxmlc>
	</target>
</project>

File settings.properties

flex.path = C:/sdks/

flex.sdkVersion= 4.1.0
flex.sdkPath= ${flex.path}/${flex.sdkVersion}
FLEX_HOME= ${flex.sdkPath}

contentText = [This value will be overwritten by Flash Builder in the output app.xml]

base.dir =..
build.dir = ../bin-release

dev.dir = ../main/flex
resources.dir = ../main/resources/
locales.dir = ../main/locales/
automationlibs.dir = ../flexlib
libs.dir = ../libs

domain1.dir = C:/development/glassfish3/glassfish/domains/domain1/applications/enterprise-app/core-web_war
app.name = flex_application

## mxml or as
app.type = mxml

swfFile=${build.dir}/${app.name}.swf
swcDelegateFile=${build.dir}/${app.name}.swc
swfFileDomain = ${domain1.dir}/${app.name}.swf
projectFile=${dev.dir}/org/catapult//app/webview/${app.name}.${app.type}

swfViewerFile=${build.dir}/${appviewer.name}.swf
projectViewerFile=${dev.dir}/${appviewer.name}.${app.type}

Lưu ý:  trong file build.xml, dòng lệnh <load-config filename=”${FLEX_HOME}/frameworks/flex-config-riatest.xml” /> để load file cấu hình của flex SDK cho mxmlc. Do tôi có một số config riêng như cần include thêm các file AS (delegation class, các file AS được kế thừa từ các lớp xxxImpl) và có cả license Flex SDK. Nếu bạn không sử dụng các automation library hoặc include thêm các file AS khác thì nên thay đổi thành <load-config filename=”${FLEX_HOME}/frameworks/flex-config.xml” />

Tagged with: , , , , , , , , , , , ,
Posted in Flex, Action Script 3.0, Flash, Flash develop, Web & AIR, AIR, Cairngorm, tiếng Việt, Flash Builder, Android, Flash Player 10.x, RIATest, FlexMonkey

[Flex] ClassFactory và ItemRenderer

Trước khi trình bày về ClassFactory và ItemRenderer, chúng ta nên điểm qua về mẫu thiết kế (design pattern) Factory. Mẫu thiết kế Factory được dùng để giải quyết vấn đề tạo ra một đối tượng mà không thể xác định trước chính xác loại (class type) đối tượng cụ thể. Bằng cách định nghĩa một phương thức cho việc tạo đối tượng, và các lớp con thừa kế có thể nạp chồng (override ?)  để chỉ rõ đối tượng nào sẽ được tạo.

Một ví dụ thực tế của Factory chính là trình duyệt ảnh như ACDSee chẳng hạn. Bạn có 1 list các hình ảnh, tuy nhiên mỗi hình ảnh có thể là 1 đối tượng của JPEGRenderer hoặc PNGRenderer, nghĩa là thuôc các lớp hoàn toàn khác nhau. Và khi render list các hình ảnh này thì ACDSee cần phải xác định lớp nào – JPEGRenderer hoặc PNGRenderer sẽ được chọn.  Một FactoryClass sẽ đóng vai trò chọn ra class instance thich hợp.

Bài viết này chi gói gọn trong phạm vi trình bày cách sử dụng ClassFactory và ItemRenderer hơn là đi sâu vào phân tích tính chất của mẫu thiết kế Factory. Hơn nữa mẫu thiết kế này được phân tích rất chi tiết trong tài liệu ActionScript 3.0 Design Patterns.

ClassFactory

Flex ClassFactory cho phép tạo ra một instance của lớp bất kỳ với các thuộc tính giống nhau. Để khởi tạo một đối tượng của FactoryClass thì điều đầu tiên là bạn phải chỉ rõ đối tượng lớp nào cần được khởi tạo bằng cách truyền class vào hàm dựng của ClassFactory

var animalHasTailRenderer:ClassFactory = new ClassFactory(AnimalRenderer);
animalHasTailRenderer.properties = { hasTail: true };

Trong đoạn mã trên,  để tạo ra một class instance của animalHasTailRenderer thì Flex sẽ gọi phương thức newInstance() của đối tượng factory và thiết lập thuộc tính hasTail = true thông qua việc gán setter properties.

ItemRenderers

Khi tiếp cận với DataGrid, List … bạn sẽ va chạm rất nhiều với ItemRenderer . Người tiền nhiệm của ItemRenderer chính là cellRenderer AS2 – nếu bạn đã có kinh nghiệm sử dụng các component của Flash.

Ngữ cảnh sử dụng ItemRenderer thường được dùng với DataGrid – bạn cần đặt các checkbox hay combobox vào từng dòng của DataGrid chẳng hạn, hay mỗi dòng sẽ là một component khác nhau.  Bạn có thể xem một ví dụ điển hình của việc sử dụng itemRenderer như đoạn mã mxml dưới đây

<mx:DataGrid id="myGrid" dataProvider="{customerXMLList}">
     <mx:columns>
          <mx:DataGridColumn dataField="Company" itemRenderer="org.catapult.renderers.CompanyRenderer"/>
          <mx:DataGridColumn dataField="Contact" itemRenderer="org.catapult.renderers.ContactRenderer"/>
     </mx:columns>
</mx:DataGrid>

Một ItemRenderer có thể được tao từ mã ActionScript thuần túy hay mã MXML, tuy nhiên phải được implement từ IFactory. Lý do là bạn phải cài đặt phương thức newInstance để ClassFactory có thể gọi được.

ActionScript

package org.catapult.renderers {
     import mx.containers.VBox;
     import mx.core.IFactory;

     public class CompanyRenderer extends VBox implements IFactory {       
           public function newInstance():* {
                return new CompanyRenderer();
           }
     }
}

MXML

<?xml version="1.0" encoding="utf-8"?>
<mx:VBox xmlns:mx="http://www.adobe.com/2006/mxml" implements="mx.core.IFactory">
     <mx:Script>
           <![CDATA[
                public function newInstance():* {
                       return new CompanyRenderer();
                }
            ]]>
    </mx:Script>
</mx:VBox>

Bản chất của việc gán itemRenderer trong đoạn mã trên là việc tạo ra một class instance kiểu object factory.

Tham khảo:

1. ActionScript 3.0 Abstract Factory Design Pattern: Multiple Products and Factories

Tagged with: , , , , , , , , ,
Posted in Action Script 3.0, AIR, custom components, Design Pattern, Factory, Flash Builder, Flash develop, Flex, Web & AIR
Follow

Get every new post delivered to your Inbox.