2009年12月5日 星期六

Stack-Based Class Design

我學過一招,也在前幾天施展給Hsu跟阿伯看,所以在這裡撰文分享一下。

(靠~招式快用完了,沒神秘感了)

事情是這樣的

我們遇到一個問題,得在程式內去更改current directory才能解決,所以在程式內加了幾行如List 1。
List 1 - Original
    char oldCurrentDirectory[BUFSIZE];
    GetCurrentDirectoryA(BUFSIZE, oldCurrentDirectory);
    SetCurrentDirectoryA(rootPath.c_str());
    
 if(!m_hMod) {
  m_hMod = LoadLibraryA(m_sName.c_str());
    }
    if(!m_hMod) {
  return NULL;
    }

    GETINSTANCEFUNC CreatePluginInstance = (GETINSTANCEFUNC) ( GetProcAddress( m_hMod, "CreatePluginInstance" ) );
    if(!CreatePluginInstance) return NULL;

    _PluginInstance* pInstance =  (CreatePluginInstance(rootPath.c_str()));
    if(!pInstance) return NULL;

    m_pInstanceVector.push_back(pInstance);

    return pInstance;
  1. Line 2: 先記下原本 的目錄以利還原。 (身為一個有道德的Programmer這是應該的)
  2. Line 3: 更改目前目錄成我們要的路徑。(簡單啦~)
到這裡我再繼續往下看,傻眼!我要在哪加入還原目錄的code?! 好多地方都有return,只加在最後一定會有漏洞!
這裡通常會遇到幾個內心的掙扎
  1. 管他的,反正沒還原搞不好也沒差,出問題再說,也搞不好不會找到我身上(這段code)。 <-- 破壞者
  2. 那我就copy paste在每個return的地方就好。 <--  50%苦力 +  50%  破壞者
  3. 寫個class(如List 2)來解決好了。 <-- OO魔人 James
List 2 - Stack-Based Class
class StChangeCurrentDirectory
{
public:
    StChangeCurrentDirectory(const char *newPath) {
        GetCurrentDirectoryA(BUFSIZE, _oldCurrentDirectory);
        SetCurrentDirectoryA(newPath);
    }
    ~StChangeCurrentDirectory() {
        SetCurrentDirectoryA(_oldCurrentDirectory);
    }
private:
    StChangeCurrentDirectory(const StChangeCurrentDirectory&);
    StChangeCurrentDirectory& operator=(const StChangeCurrentDirectory&);
    char _oldCurrentDirectory[BUFSIZE];
};
這個class在constructor裡紀錄了目前目錄在它的member並更新的目前目錄。
在destructor裡還原目前目錄。所以程式可以改寫成List 3這樣。
List 3 - Using Stack-Based Class
 StChangeCurrentDirectory changeToPluginRoot(rootPath.c_str());
 if(!m_hMod) {
  m_hMod = LoadLibraryA(m_sName.c_str());
    }
    if(!m_hMod) {
  return NULL;
    }

    GETINSTANCEFUNC CreatePluginInstance = (GETINSTANCEFUNC) ( GetProcAddress( m_hMod, "CreatePluginInstance" ) );
    if(!CreatePluginInstance) return NULL;

    _PluginInstance* pInstance =  (CreatePluginInstance(rootPath.c_str()));
    if(!pInstance) return NULL;

    m_pInstanceVector.push_back(pInstance);

    return pInstance;
這個設計利用了C++ constructor/destructor 與 stack variable的特性。在changeToPluginRoot離開它的scope時自然會還原目前目錄的設定。
這樣的設計好處有
  1. 不會忘記還原。
  2. 可重複使用。
這樣的設計其實可以套用在很多地方,例如: GDI+裡Graphics::BeginContainer與Graphics::EndContainer等類似的routine。
另外一方面,這段程式原先的寫法(指的是到處都有return)我本人就不推崇,很多缺點(本文就帶到其一),有空再另文分析。

沒有留言:

張貼留言