2008年04月26日

[Web] 連結分享

最近忙著一個大案子,所以這篇的發佈時間就 Delay 了,而且以後貼網摘的時間也可能會越拉越長...Orz

另外我想說...Oracle 的表格、欄位名稱都要大寫這個限制實在很鳥...不曉得有沒有哪位 Oracle 前輩能指點一二?

PHP

Zend Framework

JavaScript

jQuery

CSS

Database

MySQL

Other

Software

Programming

SCM

Web

Design


Posted by jaceju at 11:36回應(4)連結分享

2008年04月22日

技術...

大師發明技術
廠商包裝技術
高手掌握技術
阿宅沈迷技術


Posted by jaceju at 16:33回應(5)心得感想

2008年04月18日

Bridge 模式的簡單範例

今天跟 tokimeki 討論到 Bridge 模式的例子,這裡我做個簡單的記錄。

假設我們有軟體 (Software) 會透過印表機 (Printer) 做列印輸出,而軟體有文字編輯器 (輸出文字) 與影像編輯器 (輸出影像) ,然後印表機則可分做雷射、噴墨及點陣式。

在設計上,這兩者分別屬於不同的繼承體系,而 Software 又會利用到 Printer ,因此它們形成了 Bridge 關係,如下圖:

Bridge 範例

把幾個討論心得整理如下:

  1. 由於 Printer 抽象類封裝了實作上的不同,因此 Software 的子類別不必瞭解 Printer 的實作,它們只要知道 Printer 提供哪些介面可用。這些子類別會依照自己的需要來組合這些介面,就算用不到也沒關係。

  2. 在 Software 的子類別可以加入屬於自己的特殊方法,因為它們通常會依場合直接被調用,所以這裡並不需要遵守 LSP 原則。

  3. 對 Printer 體系來說,它不需要知道誰來用它;但反過來 Software 體系因為需要 Printer 體系的支援,所以形成了單向耦合。

  4. 對 Software 體系來說,只要它需要新的功能,而且這些功能所需要的實作也在 Printer 抽象類支援的範圍之內,那麼它就可以直接調用 Printer 物件的方法,而不會影響 Printer 體系。

  5. 如果 Software 體系需要的新功能是 Printer 抽象類所沒有的,那麼還是可以在 Printer 抽象類裡加入新的方法,而實作則交給 Printer 的子類別完成。這樣的修改完全不會影響 Software 體系。

更詳細的 Bridge 模式介紹可參考良葛格的「 Design Pattern: Bridge 模式」一文。


Posted by jaceju at 23:37回應(0)程式開發

2008年04月17日

簡單說明「抽象類」與「介面」

繼承抽象類表示類別「是個什麼」,
實作介面表示類別「能做什麼」。


Posted by jaceju at 9:49回應(6)心得感想

2008年04月15日

給 Web 開發者的一句話

不管你要做什麼服務,
正妹
就是最好的 Interface !!!


Posted by jaceju at 14:48回應(0)引用(0)心得感想

2008年04月14日

[PHP] 在 PHP5 中實作 AOP 的概念

這篇積在我電腦裡很久了,一直沒公開...這次趁著要幫我的 Library 加料,順便拿出來分享一下心得。

什麼是 AOP

AOP 全名為 Aspect-Oriented Programming ,基本的觀念可以參考良葛格的 AOP 入門:

這裡我簡單提一下 AOP 的基本想法:

假設當我們呼叫物件的某些方法 (或是業務流程) 之後,會想要把相關的資訊記錄到 log 檔裡,我們也許會這樣寫:

<?php
/**
 * Test
 */
class Test
{
    /**
     * 某個方法
     */
    public function doSomething()
    {
        // 建立 Log 物件
        $logger = new Log();

        // 寫入前置 Log
        $logger->save('before do something.');

        // 主要的動作
        // ...

        // 寫入 Log
        $logger->save('before do something.');
    }
}

可是如果今天這個記錄 log 的這個動作只是臨時的,或是在未來可能會需要再加入不同的動作時 (例如寄信) ,難道我們還要在原有方法的程式碼裡修修改改嗎?有沒有什麼方式能協助我們動態地把記錄的動作插在原有動作之後呢?

AOP 就是從這個角度所延伸出來的一種觀念,它能協助我們在不侵入原有類別程式碼的狀況下,動態地為類別方法新增額外的權責;簡單來說, AOP 主要的目的就是切入類別原有方法執行之前或之後,並安插我們想要執行的動作

註: IT 界似乎很喜歡發明深奧的名詞來詮釋一個簡單的概念,然後像我這樣不學無術的開發者就常被唬得一楞一楞的。

AOP 和 Decorator

先介紹幾篇實作 AOP 的文章:

其實一開始我以為 AOP 和 Decorator 模式在 PHP 上的實作方式是差不多的,不過實際上還有是些許的差別。

一般在 Decorator 模式中,具體類別和 Wrapper 類別都會有個共同的祖先,亦即一個抽象類別或介面,因此所產生出來的物件對 Client 程式來說,其抽象型態可以說是一樣的。

但是在 AOP in PHP 中,我們必須透過一個代理類別來切入原有的類別方法裡,雖然這個代理類別也能夠提供原有類別中的所有方法,但是實際上它卻已經失去了與原有類別所擁有的抽象型態了。

用 PHP 實作 AOP

首先我們來看看還沒有切入任何事件的目標類別:

<?php
/**
 * Test class
 *
 */
class TestClass
{
    /**
     * Method 1
     *
     * @param string $message
     */
    public function method1($message)
    {
        echo "\n", __METHOD__, ":\n", $message, "\n";
    }

    /**
     * Method 2
     *
     * @return int
     */
    public function method2()
    {
        echo "\n", __METHOD__, ":\n";
        return rand(1, 10);
    }

    /**
     * Method 3
     *
     * @throws Exception
     */
    public function method3()
    {
        echo "\n", __METHOD__, ":\n";
        throw new Exception('Test Exception.');
    }
}

這個類別提供了三個方法,其中 method1 和 method2 只是簡單的顯示資料而已,而 method3 則會丟出一個異常。

另外我們需要一個 Log 類別:

<?php
/**
 * Log
 *
 */
class Log
{
    /**
     * log message
     *
     * @param string $message
     */
    public function save($message)
    {
        echo $message, "\n";
    }
}

這個 Log 類別只提供一個 save() 方法,以顯示 log 訊息。

現在我們要完成的目標如下:

  1. 在 method1 執行前呼叫 Log::save() 。

  2. 在 method2 執行後呼叫 Log::save() 。

  3. 在 method3 發生異常時呼叫 Log::save() 。

這裡我用很簡單的方式來做,那就是直接使用一個 Aspect 類別:

<?php
/**
 * Aspect
 *
 */
class Aspect
{
    /**
     * Name of target class
     *
     * @var string
     */
    private $_className = null;

    /**
     * Target object
     *
     * @var object
     */
    private $_target = null;

    /**
     * Event callback
     *
     * @var array
     */
    private $_eventCallbacks = array();

    /**
     * Add object
     *
     * @param object $target
     * @return Aspect
     */
    public static function addObject($target)
    {
        return new Aspect($target);
    }

    /**
     * Contructor
     *
     * @param object $target
     */
    public function __construct($target)
    {
        if (is_object($target)) {
            $this->_target = $target;
            $this->_className = get_class($this->_target);
        }
    }

    /**
     * Register event
     *
     * @param string $eventName
     * @param string $methodName
     * @param callback $callback
     */
    private function _registerEvent($eventName, $methodName, $callback, $args)
    {
        if (!isset($this->_eventCallbacks[$methodName])) {
            $this->_eventCallbacks[$methodName] = array();
        }

        if (!is_callable(array($this->_target, $methodName))) {
            throw new Exception(get_class($this->_target) . '::' . $methodName . ' is not exists.');
        }

        if (is_callable($callback)) {
            $this->_eventCallbacks[$methodName][$eventName] = array($callback, $args);
        } else {
            $callbackName = Aspect::getCallbackName($callback);
            throw new Exception($callbackName . ' is not callable.');
        }
    }

    /**
     * Register 'before' handler
     *
     * @param string $methodName
     * @param callback $callback
     */
    public function before($methodName, $callback, $args = array())
    {
        $this->_registerEvent('before', $methodName, $callback, (array) $args);
    }

    /**
     * Register 'after' handler
     *
     * @param string $methodName
     * @param callback $callback
     */
    public function after($methodName, $callback, $args = array())
    {
        $this->_registerEvent('after', $methodName, $callback, (array) $args);
    }

    /**
     * Register 'on catch exception' handler
     *
     * @param string $methodName
     * @param callback $callback
     */
    public function onCatchException($methodName, $callback, $args = array())
    {
        $this->_registerEvent('onCatchException', $methodName, $callback, (array) $args);
    }

    /**
     * Trigger event
     *
     * @param string $eventName
     */
    private function _trigger($eventName, $methodName, $target)
    {
        if (isset($this->_eventCallbacks[$methodName][$eventName])) {
            list($callback, $args) = $this->_eventCallbacks[$methodName][$eventName];
            $args[] = $target;
            call_user_func_array($callback, $args);
        }
    }

    /**
     * Execute method
     *
     * @param string $methodName
     * @param array $args
     * @return mixed
     */
    public function __call($methodName, $args)
    {
        if (is_callable(array($this->_target, $methodName))) {
            try {
                $this->_trigger('before', $methodName, $this->_target);
                $result = call_user_func_array(array($this->_target, $methodName), $args);
                $this->_trigger('after', $methodName, $this->_target);
                return $result ? $result : null;
            } catch (Exception $e) {
                $this->_trigger('onCatchException', $methodName, $e);
                throw $e;
            }
        } else {
            throw new Exception("Call to undefined method {$this->_className}::$methodName.");
        }
    }

    /**
     * Get name of callback
     *
     * @param callback $callback
     * @return string
     */
    public static function getCallbackName($callback)
    {
        $className  = '';
        $methodName = '';

        if (is_array($callback) && 2 == count($callback)) {
            if (is_object($callback[0])) {
                $className = get_class($callback[0]);
            } else {
                $className = (string) $callback[0];
            }
            $methodName = (string) $callback[1];
        } elseif (is_string($callback)) {
            $methodName = $callback;
        }

        return $className . (($className) ? '::' : '') . $methodName;
    }
}

這個類別有點小長,簡單說明如下:

  1. 我們利用 Aspect::addObject() 方法來指定要被切入的物件; addObject() 方法會回傳一個透明的 Aspect 物件。

  2. 利用 before 、 after 和 onCatchException 三個方法來指定切入的時機,它們會呼叫 _registerEvent() 方法來註冊要執行的回呼函式 (callback) 。

  3. 執行原來被切入物件的方法,這時會觸動 Aspect 的 __call() 方法,並在指定的切入時機呼叫 _trigger() 方法來執行我們所切入的回呼函式。

先來看看還沒有使用 AOP 前,我們對 TestClass 類別的測試:

<?php
require_once 'TestClass.php';

$test = new TestClass();

/* @var $test TestClass */
echo "=======\n";
$test->method1('abc');
echo "=======\n";
echo $test->method2(), "\n";
echo "=======\n";
$test->method3();
echo "=======\n";

/* 執行結果:
=======

TestClass::method1:
abc
=======

TestClass::method2:
2
=======

TestClass::method3:

Exception: Test Exception. in TestClass.php on line 38
*/

接下來我們利用 Aspect 類別來對 TestClass 物件的三個方法切入 Log::save() :

<?php
require_once 'Aspect.php';
require_once 'TestClass.php';
require_once 'Log.php';

$test = Aspect::addObject(new TestClass());
$logger = new Log();

$test->before('method1', array($logger, 'save'), 'Log saved (method1).');
$test->after('method2', array($logger, 'save'), 'Log saved (method2).');
$test->onCatchException('method3', array($logger, 'save'), 'Log saved (method3).');

/* @var $test TestClass */
echo "=======\n";
$test->method1('abc');
echo "=======\n";
echo $test->method2(), "\n";
echo "=======\n";
$test->method3();
echo "=======\n";

/* 執行結果:
=======
Log saved (method1).

TestClass::method1:
abc
=======

TestClass::method2:
Log saved (method2).
8
=======

TestClass::method3:
Log saved (method3).

Exception: Test Exception. in TestClass.php on line 38
*/

結論

我們可以從範例看到, AOP 能幫我們在某類別的方法中插入一些額外的動作,同時又能不破壞原有類別的程式碼。而它與 Decorator 最大的不同是, Decorator 必須用很多小類別來完成相同的動作,但是 AOP 則透過 PHP 的動態特性解決了這個問題。

當然 AOP 也不是萬靈丹,像在本文的實作裡它就不能接觸目標類別的非公開屬性。而之前也跟 Mark 聊了一下,其實 AOP 偏向於程式的整體設計,所以這裡的範例尚不能用於實戰之中,僅僅只是我個人一個概念的實作而已。

供大家參考看看吧。也歡迎一起討論~


Posted by jaceju at 18:29回應(7) PHP

2008年04月13日

[Web] 連結分享

朋友問我這些連結怎麼看得完?我其實沒有全部看完,只是挑重點看。其他的都是瞄個大概,然後記下先;註解也是看懂大概後寫的 (我的英文閱讀能力普普) ,所以有時候可能會錯,請大家原諒。 >_<

PHP

Zend Framework

JavaScript

jQuery

CSS

Internet

Web

Design


Posted by jaceju at 0:22回應(6)連結分享

2008年04月10日

[Web] 連結分享

PHP

Zend Framework

CSS

JavaScript

jQuery

Web

Browser

Design

Other

Software

4月1日(愚人節快樂)


Posted by jaceju at 0:26回應(0)連結分享
 [1]