天天看點

閑談裝飾模式——基于UI動畫架構1. 裝飾模式2. 架構中的應用3. 結語

  本文讨論UI動畫架構中應用過的一個設計模式:“裝飾模式”。為什麼是“應用過的”,而不是“正在用的”?原因是裝飾模式在UI動畫架構中的應用是一個失敗的嘗試,有那麼點牽強,或者說“為了模式而模式”。裝飾模式在架構中并沒有存在多久,就被優化掉,用組合模式替代了。

1. 裝飾模式

  裝飾模式的定義:

動态的給一個對象添加一些額外的職責。就增加功能來說,裝飾模式比生成派生類更靈活。

  類圖:

閑談裝飾模式——基于UI動畫架構1. 裝飾模式2. 架構中的應用3. 結語

  Component:定義了業務類提供的功能接口;

  ConcreteComponent:實作具體的功能,這個具體類就是被裝飾的原始對象;

  Decorator:所有裝飾類的基類,其接口需要和Component的接口一緻(從Component派生來保證這一點),并持有一個到Component對象的句柄,這個對象就是被裝飾的對象;

  ConcreteDecoratorX:具體的裝飾對象,實作具體要向被裝飾對象添加/删除的功能(在被裝飾對象之前/之後添加/删除功能)。

  除了原始的ConcreteComponent對象可以被裝飾,其它被裝飾過的對象也可以再被裝飾,因為Decorator持有的是Component對象句柄,而Decorator對象同時也是Component對象,是以一個展現給客戶的Component對象通常像一個洋蔥一樣,層層嵌套裝飾:

閑談裝飾模式——基于UI動畫架構1. 裝飾模式2. 架構中的應用3. 結語

  裝飾模式從一個對象外部給對象增加功能,就是通過“組合”而不是通過“繼承”來擴充功能。不同的裝飾器之間盡量獨立(沒有耦合關系),這樣就可以随意改變組合順序,複用性高,更為靈活。

2. 架構中的應用

  架構中的政策類體系的初版是使用裝飾模式來實作政策組的。首先定義了一個裝飾政策類:

class DecoratorStrategy : public IStrategy
{
public:
    explicit DecoratorStrategy(IStrategy* ps) : strategy(ps) {}
    virtual IStrategy* Clone() const;

private:
    IStrategy*    strategy;
};
           

該類從IStrategy類派生,并持有一個IStrategy句柄。從政策集合中挑一些基本的政策(即不裝飾任何其它的政策)直接從IStrategy類派生,比如FrameStrategy就是一個基本的政策,這些類就是ConcreteComponent:

class FrameStrategy : public IStrategy
{
private:
    mutable int  interval;  // 兩次播放之間的間隔毫秒數
    long         lastTick;  // 逝去的時間
};
           

其它的政策類都從DecoratorStrategy派生:

// 淡入淡出政策
class FadeStrategy : public DecoratorStrategy
{
public:
    explicit FadeStrategy(IStrategy* ps) : DecoratorStrategy(ps) {}

private:
    const float  iniAlpha;      // alpha初始值
    float        delta;         // 幀與幀之間的alpha變化的百分比
    const float  minAlpha;      // alpha的下限
    const float  maxAlpha;      // alpha的上限
};

// 縮放政策
class ScaleStrategy : public DecoratorStrategy
{
public:
    explicit ScaleStrategy(IStrategy* ps, float dx = 0, float dy = 0, int s = 0)
                          : DecoratorStrategy(ps), fdx(dx), fdy(dy), steps(s)
    {
    }

private:
    float   fdx;    // x軸縮放步長(比例)
    float   fdy;    // y軸縮放步長(比例)
    int     steps;  // 步數
};

           

假如,有這樣的配置:

<strategys id = "123" desc = "視窗慢慢向上飄起,慢慢縮小,并慢慢消失" >
    <strategy id = "frame" param = "30" /> <!-- 30ms一幀 -->
    <strategy id = "move" param = "vertical;10;-20" /> <!-- 從起始位置向上移動10次,每次20像素 -->
    <strategy id = "scale" param = "1;0.8;0.8;6" /> <!-- 尺寸縮小10次,每次長寬均縮小0.8 -->
    <strategy id = "fade" param = "1;0.8" /> <!-- alpha從1.0起每次淡化0.8 -->
</strategys>
           

解析過程是這樣的:

void AnimationParser::BeginStrategy(const AttriMap& attributes)
{
    IStrategy* & ps = strategys.back(); // 取出前一個政策對象
    IStrategy* p = nullptr;
    if (attributes["id"] == "frame"):
        p = new FrameStrategy(ps, attributes["param"]); // 裝飾前面的政策對象
    else if (attributes["id"] == "move"):
        p = new MoveStrategy(ps, attributes["param"]); // 裝飾前面的政策對象
    else if (attributes["id"] == "scale")
        p = new ScaleStrategy(ps, attributes["param"]); // 裝飾前面的政策對象
    else if (attributes["id"] == "fade")
        p = new FadeStrategy(ps, attributes["param"]); // 裝飾前面的政策對象
    ...
    else
        throw ("invalid strategy type!") + attributes["id"]);
    
    ps = p; // 替換
}
           

經過解析,上面的動畫配置生成這樣一個層層嵌套的對象:

閑談裝飾模式——基于UI動畫架構1. 裝飾模式2. 架構中的應用3. 結語

在執行時,在被裝飾的對象執行的基礎上,附加自己的功能:

bool FadeStrategy::Run(Window& aniWnd)
{
    DecoratorStrategy::Run(aniWnd); // 被裝飾的對象執行它的功能
	
    // 以下執行自己的功能
    float alpha = aniWnd.getAlpha();
    if ((delta < 1.0 && alpha >= minAlpha) || (delta > 1.0 && alpha < maxAlpha))
    {
        float xalpha = alpha * delta;
        aniWnd.setAlpha(xalpha > 1.0f ? 1.0f : xalpha);
    }
    return true;
}
           

執行順序是這樣的:執行FrameStrategy → 執行MoveStrategy → 執行ScaleStrategy → 執行FadeStrategy。

  用裝飾模式實作的政策體系,看上去也挺不錯的。但随後就遇到了諸多不便。

  ✘ 結構僵化

  根據動畫配置規則,政策(們)是一個天然的樹形結構,“組合模式”的結構對樹形結構的模拟比較自然,而“裝飾模式”的結構就顯得有點僵硬。組合模式中在組合節點上通過索引在子對象清單(數組)中直接定位到子對象,而在裝飾模式中,則不存在這樣的清單,需要像剝洋蔥似的,一層層向裡層推進,并用一個計數器來标記層次,直到找到目标節點。在實際應用中,你會覺得不順暢,有種别别扭扭的感覺。

  ✘ 效率低

  從上面的代碼執行個體中可以看出,一個政策組中最先執行的政策位于“洋蔥”對象的最裡層,需要一層層向裡傳遞調用,另外,如果一個政策的控制還沒有完成,退出時,得另外标記,以便下一幀能找到它繼續執行。在實作時,就不隻是别扭了,而是覺得處處受限,需要做不少無謂的簿記工作,以完成正确的控制流程。

3. 結語

  UI動畫架構在初期采用裝飾模式,屬于設計的缺陷,很快就回歸到“組合模式”。本系列單列一篇讨論這個模式,主要目的是加深對裝飾模式的了解,從失敗的設計中吸取可借鑒的經驗。裝飾模式的用場主要有:

  • 如果需要在不影響其它對象的情況下,以動态透明的方式給對象添加職責,可以使用裝飾模式,這幾乎是裝飾模式的主要應用場景;
  • 有明顯的需要在使用一個對象之前&之後都要附加一些功能的情況,比如前置條件/後置條件的檢查。

如果沒有以上特征,則不适宜用裝飾模式。以動畫架構中的政策體系類比,本來是一棵樹,卻偏要将所有樹葉嵌進樹枝,再将樹枝折進樹幹,最後把樹幹塞到樹根裡面,生生揉成一個“洋蔥”,用這樣的設計架構,有如自縛手腳,寸步難行!

繼續閱讀