首页 > 程序人生 > 谈谈程序的可扩展性

谈谈程序的可扩展性

光阴如棱,写程序也有好几个年头了。几年来,写过不少程序,也读过不少别人写的代码,各种各样、形形色色的都有。刚开始写程序的人,大都是以完成任务为主,以结果为导向的,很少会注重程序,或者说代码本身。随着写过、读过的程序越来越多,对写程序的人来说,完成任务、实现当前功能不是唯一的目标了,如何能在完成当前任务的同时,考虑一下,如果后面还会有其它的需求的话,我们现在的程序能不能在尽量不修改现有代码的前提下,通过增加一些新的代码,来实现需求、完成任务。这就是所谓程序的“可扩展性”。

为了帮助大家更好的理解,下面看一个例子,来实实在在的体会一下程序的可扩展性。这里首先给出需求:实现一个HtmlParser, 提供解析出页面里所有img链接的功能。

  • 1. 先来看看大家最容易想到的一种实现:
  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    
    class HtmlParser
    {
    public:
        /// ...
     
        const char * GetTag(const char * pos)
        {
            /// TODO parse the tag start from pos
        }
     
        bool ParseImg(std::vector< std::string> & img_links)
        {
            const char * pos = m_page_begin;
            const char * end = m_page_begin + m_page_len;
            while (pos != end)
            {
                if (*pos == '< ')
                {
                    std::string tag(GetTag(pos));
                    if (tag == "img")
                    {
                        /// TODO parse the tag
                    }
                }
                ++pos;
            }
        }
     
        /// ...
     
    private:
        std::string    m_page_url;
        const char *   m_page_content;
        size_t         m_page_len;
    };

    当然,上面的代码,很容易便可以完成前面的需求。但是,这时候又来了新的需求,把页面里引用的Javascript的链接也都解析出来,这时候怎么实现?一个水到渠成的想法就是,再增加一个函数,比如叫ParseJavascript,大体框架跟ParseImg类似。但是,这样又带来的新的问题,如果我即想要Img,又想要Javascript,那就需要遍历两遍页面,效率相当低下。这时候,可能会有人说,那就修改ParseImg函数,使其在遍历过程中,同时支持解析img和javascript。但是,这样子的改法会让人相当蛋疼,一方面改了旧的代码,旧的功能的正确性不能保证,需要重新测试。另一方面,随着新的解析类型支持的越来越多,ParseImg函数本身会越来越大,不仅每种类型的测试会比较因难,代码本身的可维护性也越来越差。这个时候,该怎么办呢?Rework!

  • 2. 接下来看看另外一种实现:
  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    
    class HtmlParser;
     
    class TagHandler
    {
    public:
     
        virtual const char * GetTag() = 0;
     
        virtual bool Parse(HtmlParser * parser) = 0;
    };
     
    class HtmlParser
    {
    public:
        /// ...
     
        bool RegisterHandler(TagHandler * handler)
        {
            m_tag_handles.insert(handler);
        }
     
        const char * GetTag(const char * pos)
        {
            /// TODO parse the tag start from pos
        }
     
        void Parse()
        {
            const char * pos = m_page_begin;
            const char * end = m_page_begin + m_page_len;
            while (pos != end)
            {
                if (*pos == '< ')
                {
                    std::string tag(GetTag(pos));
                    std::set< TagHandler *>::iterator it = m_tag_handles.begin();
                    while (it != m_tag_handles.end())
                    {
                        if ((*it)->GetTag() == tag)
                        {
                            (*it)->Parse(this);
                            break;
                        }
                        ++it;
                    }
                }
                ++pos;
            }
        }
     
        /// ...
    private:
        std::string              m_page_url;
        const char *             m_page_content;
        size_t                   m_page_len;
        std::set< TagHandler *>  m_tag_handles;
    };
     
     
    class ImgHandler : public TagHandler
    {
    public:
     
        virtual const char * GetTag()
        {
            return "img";
        }
     
        virtual bool Parse(HtmlParser * parser)
        {
            /// TODO parse the img link
            /// TODO add to m_img_links
        }
     
    private:
        std::vector< std::string>  m_img_links;
    };

    上面的实现中,用户只要把ImgHandler的实例注册到HtmlParser中去,HtmlParser便可以支持解析Img的链接。如果要支持解析Javascript的链接,用户只需要继承TagHandler,实现一个JavascriptHandler即可。不论要扩展到支持解析哪种类型的tag,都可以按此方法来实现。这里不论要支持的Tag有多少种,都是在一次遍历页面的过程中出结果的,在保证高效率的前提下,实现了灵活的扩展机制。

    通过对比上面两个例子中的实现,我们发现,可扩展性,对程序设计来说是多么的重要。总结一下程序的可扩展性,主要表现在以下几个方面:

  • 1. 能够方便的增加新的功能
  • 2. 增加新的功能,尽量不修改原有的代码
  • 3. 增加新的功能,通过增加新的类/函数来实现
  • 4. 增加新的功能,不造成对程序性能、效率的大的影响
  • 5. 新的代码可以独立进行测试
  • 6. 新的代码可以通过和原有代码简单的“注册”或“组合”机制,无缝实现新的功能
  • 说了这么多,相信大家对程序的可扩展性,已经有了一个基本的认识。那么,在写程序的过程中,具体如何实践呢?这里没有现成的方法,只是把我个人实践过程中总结的一些经验分享一下,希望能起到抛砖引玉的目的。通常,扩展性好的程序,大都是实现一个“框架”,所有的功能单元都可以像“插件”一样,方便地从这个“框架”上“拔插”。这里最为重要的就是“框架”,要想设计出比较好的“框架”,是需要有一定的积累的,这里没有捷径,多读别人写的优秀的代码是一个不错的方法,尤其是一些优秀的开源项目的代码。设计好了“框架”,所有的功能单元都需要插在这个“框架”上才能发挥功能。这里如何把这些功能单元从“框架”上方便的插入、拔出,也是十分关键的。通常的实现方法一是如上面所示的注册机制,通过C++的多态机制,继承实现不同的子类,完成不同的功能单元。另外比较常见的方法是直接注册回调函数。还有一些比较实用的设计模式,比如Composite, Bridge等,都可以用来增加代码的扩展性,感兴趣的朋友可以好好研读一个GOF的设计模式那本书,很不错。
    以上即是本文的全部内容,粗知拙见,希望能对同道中人有所帮助,不胜荣幸!

    分类: 程序人生 标签: , ,
    1. 2011年8月2日09:29 | #1

      谢谢分享

    2. 2011年8月2日16:58 | #2

      看不懂 支持一下

    3. 2011年8月3日13:33 | #3

      最近正好在看libxml2的parser部分哈哈

    4. raycheng
      2011年8月16日00:27 | #4

      小武哥总结得很好呀,良好的抽象和可扩展性对提高效率具有很大的帮助,写这样的程序成就感也大很多。首先得培养这样的意识,平时就严格要求自己,写出通用的代码

    5. 2011年8月16日12:49 | #5

      好久不见!@raycheng

    1. 本文目前尚无任何 trackbacks 和 pingbacks.
    您必须在 登录 后才能发布评论.