2011年十月月 发布的文章

提高效率不是口号

当前任何一个组织 — 无论是私企,还是国企,无论是政府还是民间组织,无论是在国内还是在国外 — 都在强调提高效率。但"提高效率"不简单是一句口号,还需要脚踏实地的真正去做。

说到"提高效率",大家首先就会想到工作的行为主体-人!促进人员能力的提升是提升个体工作效率的一个很好的办法。在软件开发领域也有一个公认的事实,那就是一个顶尖程序员的效率可以十倍甚至百倍于一个普通程序员。为此,很多组织都投入巨资引入各种针对个体能力提升的培训和咨询,试图以此提升全体人员的效率。我们不能否认这种方法的效果,但从实际情况来看,似乎效果有限,特别是对组织整体效率的提升效果有限,因为组织内不可能都是由领域顶尖人员组成的,即使接受培训,大部分人员也不会成为顶尖级,其水平还是处于平均线的。这就是摆在我们面前的一个现实,我所在的组织同样也面临着同样的问题,这也促使我进行了一些较为深入的思考。

那么除了个体能力提升之外,我们还应当如何提升组织的整体效率呢?

提高效率,首先要做好"认知",找到方向。即知道什么工作决策和行为是有利于提高效率的,什么样的决策和行为是无助于提高效率或是导致无用功。以现阶段比较火的物流快递行业为例,科学地选择送货路线无疑是提高整体效率的一个好方法,即能节省时间,还能节省交通燃油成本,缩短送货时间,提高服务质量。如果你作为快递公司的老总,你的决策应该是:建立一套高智能的选路系统,以帮助每个快递员快速完成送货任务。在软件开发领域,人们一直以来都在做着探索,努力地瞄准正确的方向前行。特别是近些年来,软件开发借鉴其他传统产业的生产经验,试图通过消除开发过程中的浪费来提升整体开发效率。什么是软件开发领域的浪费呢?广义的讲,对开发团队而言,不能卖钱的工作都是浪费;对于客户而言,不能给客户带来价值的工作都是浪费。传统行业(比如汽车制造业)用事实证明,浪费现象只能尽量减少,但无法完全杜绝。但浪费是否是完全没有意义的呢?有些"浪费“对组织的发展壮大还是有裨益的,比如建立企业内部知识库等。OK,我们似乎找到了软件领域提升组织整体效率的方向,那就是要尽量减少那些既不能给客户带来直接价值,也无法给我们自身带来经济利益,对组织的发展壮大也无多大贡献的浪费。

提高效率,还要了解个体的行为特征。我们应该了解所在行业从业人员的行为效率曲线。理想的情况是让大家始终都能处于自己的高效率区间。俗话说,术业有专攻。快递员在挨家串户送货时效率是最高的;软件开发人员在设计、编码和调试时效率是最高的;文案人员在编写文档时的效率是最高的,等等。如果让每个快递员自己优化送货路线、让每个软件开发人员自己做集成构建,环境搭建、调试和测试、让文案人员自己负责后期的装订和印刷,那低效率就不可避免。说白了就是让大家都去做且一直在做自己最擅长的事情,这样才能尽量发挥出个体的效率,才有益于组织整体效率的提升。

有了上面两个方面作为前提,我们就可以得到一种切实可行的提升组织整体效率的方法,那就是推进组织内部服务的建设。什么是"内部服务"?简单来说,就是组织内部一部分人员的工作就是为其他人提供服务,至于服务的内容因行业的不同而不同。"内部服务"可以从全局角度主动地推动个体效率提升。从个体行为上来看,内部服务可以剥离大多数个体所不擅长的事情,让大多数个体长期处在高效率区间,后期将这些大家不擅长的事情以服务或基础设施的形式科学地且用户界面友好地提供给大家,让大家可以高效地或自动化地使用。这就是"内部服务"的原理所在。注意,与在培训和咨询等过程中,组织内人员需主动学习和主动提高效率不同,通过内部服务的建设和提升,组织内人员是在不知不觉中"被提升了效率",也避免了因人员能力参差不齐,效果因人而异的问题,这是其最大魅力。

个人觉得实施内部服务建设有以下几个关键要素:
- 细分工作,识别出基础服务。即找出可以作为服务的工作内容。还是以软件开发领域为例,系统管理小组和版本构建小组的工作内容就可以作为提供给其他人的基础服务。

- 按工作内容调整团队组织。即将组织划分为若干基础服务团队,以及其他非提供内部服务的产品团队;明确内部服务团队的职责范围以及与其他团队的接口方式。

- 基于内部服务改造工作流程。将内部服务放入工作流程中,让成果物在各个团队间高效流动,甚至可以在一定程度上简化原有工作流程。

在人员有限的前提下,内部服务团队也可以是虚拟团队,不过这样做就会导致内部服务工作的优先级在工作极其繁忙时被降低,可能会导致浪费现象在后续显现出来。内部服务的概念也是分工细化思想的延伸,也许它并不适合一些人员规模较小的startup,但却适合那些规模已经扩大但效率却没有明显提升的组织。

在软件开发领域,我们很容易识别出很多基础服务,诸如服务器/虚拟化环境支持服务、公共库开发团队、测试工具的开发服务、构建与持续集成服务、自动化测试支持服务、文案服务等。越来越多的自动化框架和工具(比如puppetjenkinsbuildbot等)也让内部服务团队可以不必具有很大规模,而且可以使得服务团队自身的工作也颇为高效。另外内部服务团队还便于将一些致力于消除浪费、提高效率的思想(诸如持续交付DevOps)快速地转化为具体的工作方法和实践。

总之,提高效率是一个极其重要的事,甚至是直接关乎于真金白银的事。一个组织应着眼于全局思考、规划和落实提高效率的具体措施。如果一个组织现在依然将提高效率停留在喊口号的层次上,那若干年后,这个组织剩下的也就仅是那句口号罢了。

Chain of Responsibility模式的C实现

又是一个行为类的模式,似乎这类模式在使用C语言开发的项目中适应性更强,而另外两类模式创建型和结构型则略显不受待见^_^。

Chain of Responsibility模式(中文名:职责链模式)是一个不算复杂的模式。虽不复杂,但用好了同样可以解决大问题。个人觉得其最大的好处就在于可以动态地重组针对一类对象的处理流程。正是得益于这一优势,它才可以在纷繁芜杂的业务领域站稳脚跟。

我们遇到的问题是这样的:有一类消息需要我们的系统处理,消息在系统入口处需经过种种业务层面上的校验,只有通过所有校验的消息才被允许进入到我们的系统中并被视为合法的消息。针对来自不同企业的消息,系统在入口处的校验规则是不同的,对于信用度较高的企业,系统实施的校验较少;而对于信用度不高的企业或新签约企业来说,其校验规则就相对多些;随着企业的信用度的变化,系统也会自动地调整对其下发消息的校验规则集。

最初关于这个部分的系统伪码大致是这样的:
int check_msg(corp_info, msg) {
    if (corp_info->need_check_source) {
        if (FAILED == check_source(msg))
            return xx;
    }

    if (corp_info->need_check_destination) {
        if (FAILED == check_destination(msg))
            return xx;
    }

    if (corp_info->need_check_priority) {
        if (FAILED == check_priority(msg))
            return xx;
    }

    if (corp_info->need_check_content) {
        if (FAILED == check_content(msg))
            return xx;
    }

    return 0;
}

在check_msg外部,系统根据企业的信用度设置corp_info中的多个check feature开关,诸如need_check_source、need_check_content等,从而使得check_msg内部可以根据企业的不同feature开关情况,对企业发送的消息实施不同的校验规则。

这里消息校验的请求者与消息校验的处理者具有一定的耦合,另外check_msg中满眼的if语句也让我们的神经为之紧绷!于是我们尝试移除if,尝试降低请求者和执行者之间的耦合。在《设计模式》中我们找到了Chain of Responsibility模式,我们决定试试!

我们首先定义了handler_t接口:

struct handler_t {
    void (*set_successor)(struct handler_t *this, struct handler_t *successor);
    struct handler_t* (*get_successor)(struct handler_t *this);
    int (*handle_request)(struct handler_t *this, void *obj, void *args);
    int type; /* handler类型 */
};

接下来,我们根据例子的需要逐个定义该接口的实现:source_checker、destination_checker、priority_checker和content_checker。以source_checker为例:

/* source_checker.h */
struct handler_t* source_checker_new();
void source_checker_destroy(struct handler_t **h);

/* source_checker.c */
struct source_checker_t {
    struct handler_t h;
    struct handler_t *successor;
};

static void _set_successor(struct handler_t *this, struct handler_t *successor) {
    struct source_checker_t *h = (struct source_checker_t*)this;
    h->successor = successor;
}

static struct handler_t* _get_successor(struct handler_t *this) {
    struct source_checker_t *h = (struct source_checker_t*)this;
    return h->successor;
}

static int _handle_request(struct handler_t *this, void *obj, void *args) {
    struct source_checker_t *h = (struct source_checker_t*)this;
    struct msg_t *msg = (struct msg_t*)obj;

    if (校验失败) /* 伪码 */
        return FAILED;
    printf(“[source_checker]: check msg – [%s]\n”, msg->msg_id);

    if (h->successor)
        return (h->successor->handle_request(h->successor, obj, args));

    return SUCCESS;
}

struct handler_t* source_checker_new() {
    struct source_checker_t *h;

    h = (struct source_checker_t*)malloc(sizeof(*h));
    if (!h) return NULL;

    memset(h, 0, sizeof(*h));
    h->h.set_successor = _set_successor;
    h->h.get_successor = _get_successor;
    h->h.handle_request = _handle_request;
    h->h.type = SOURCE_CHECKER;

    return (struct handler_t*)h;
}

void source_checker_destroy(struct handler_t **h) {
    struct source_checker_t *p = (struct source_checker_t*)(*h);

    if (p) free(p);
    (*h) = NULL;
}

destination_checker、priority_checker和content_checker与source_checker的实现类似,关键在于_handle_request的实现不同。

现在我们就可以在初始化阶段为不同企业组装不同的业务校验流程了,假设我们有两家企业A和B,A企业下发的消息需要进行全部业务校验,而B企业下发的消息仅需进行source check和destination check:

/* A企业消息的业务校验链 */
struct handler_t *A_destination_checker = destination_checker_new();
struct handler_t *A_priority_checker = priority_checker_new();
struct handler_t *A_content_checker = content_checker_new();
struct handler_t *A_msg_checker = source_checker_new();

A_msg_checker->set_successor(A_msg_checker, A_destination_checker);
A_destination_checker->set_successor(A_destination_checker, A_priority_checker);
A_priority_checker->set_successor(A_priority_checker, A_content_checker);

/* B企业消息的业务校验链 */
struct handler_t *B_destination_checker = destination_checker_new();
struct handler_t *B_msg_checker = source_checker_new();

B_msg_checker->set_successor(B_msg_checker, B_destination_checker);

我们可以将msg_checker的放入corp_info中,这样check_msg的新实现如下:
int check_msg(corp_info, msg) {
    return corp_info->msg_checker->handle_request(corp_info->msg_checker, (void*)msg, NULL);
}

这样通过A企业下发的消息testAmsg通过check_msg得到的结果是:
[source_checker]: check msg – [testAmsg]
[destination_checker]: check msg – [testAmsg]
[priority_checker]: check msg – [testAmsg]
[content_checker]: check msg – [testAmsg]

而B企业下发的消息testBmsg通过check_msg得到的结果则是:
[source_checker]: check msg – [testBmsg]
[destination_checker]: check msg – [testBmsg]

前面说过动态重组针对某一对象的业务流程是职责链模式一大特点。当某企业信用度发生变化时,该企业对应的checker链也会动态修改。比如当企业A信用度增加时,系统将去除其对应的content check流程,去除过程的实现如下:

struct handler_t *h = A_msg_checker;
struct handler_t *successor = h->get_successor(h);

while (successor) {
    if (successor->type == CONTENT_CHECKER) {
        h->set_successor(h, successor->get_successor(successor));
        break;
    }
   
    h = successor;
    successor = successor->get_successor(successor);
}

重组校验链后,企业A下发的消息testAmsg通过msg_check得到的结果就变成了:
[source_checker]: check msg – [testAmsg]
[destination_checker]: check msg – [testAmsg]
[priority_checker]: check msg – [testAmsg]

也许大家也看到了职责链模式的缺点,那就是每增加一个业务处理对象就要增加一个handler_t的具体实现,如诸多xx_checker,在C语言开发中这至少需要一个头文件与一个源文件。但职责链模式对降低请求者与处理者之间的耦合,以及支持职责链的动态重组方面还是会给你带来很大帮助的。是否使用这种模式,需要你自己根据实际情况权衡利弊后做出选择。

Strategy模式的C实现

与那些复杂的模式相比,Stragegy Pattern(策略模式)是一个相对简单的模式,很直观,也易于理解。 同时它也是我们在开发过程中使用最多的模式之一。

问题是设计模式使用的驱动力,只有当我们遇到问题时,设计模式才会向我们伸出援助之手。这里我也想通过对问题以及解决方法演化的阐述来说明策略模式是如何更好地帮助我们的。我们从问题出发!

Tony最近接到了一个新任务,任务的内容是实现一个通用的平衡二叉树数据结构供其他同事使用。接到这个任务后,他十分欣喜,因为在之前的工作中他一直在用C语言编写繁芜复杂的业务逻辑,这让他感觉很烦躁。今天他终于盼到了一个做公共库的机会。为此Tony连夜拜读了《C语言接口与实现》一书,希望能从书中取点经,做出一个让大家都满意的通用的平衡二叉树。

Tony十分欣赏《C语言接口与实现》中采用的一些原则,比如尽量隐藏细节,只给外部暴露必要的信息等。他也是按照书中的原则来定义平衡二叉树的各个操作接口的。但在定义接口的过程中Tony却遇到了问题,以平衡二叉树的创建和销毁接口为例,Tony最初设计的接口原型是这样的:

/* balanced_binary_tree.h */
… …
struct bb_tree_t;
int bb_tree_create(struct bb_tree_t **tree, …);
int bb_tree_destroy(struct bb_tree_t **tree, …);
… …

/* balanced_binary_tree.c */
struct bb_tree_t {
    …
};
… …

很显然,为了隐藏细节,Tony选择了在接口实现内部为tree分配和释放内存空间,但他不确定应该用哪种内存分配方式。他经过一番思维斗争后最终选择了使用标准库中的malloc和free,因为他坚定地认为多数人都会使用这套标准的内存管理接口,例外的可能性很小。后续的实现过程很顺利,当这套"设计优美"的平衡二叉树公共库被提交给其他同事使用时,Tony感觉十分地开心。

/* balanced_binary_tree.c */
int bb_tree_create(struct bb_tree_t **tree, …) {
    struct bb_tree_t *p = (struct bb_tree_t*)malloc(sizeof(*p));
    …
}

int bb_tree_destroy(struct bb_tree_t **tree, …) {
    free(*tree);
    … …
}

不过墨菲定律告诉我们:事情如果有变坏的可能,不管这种可能性有多小,它总会发生。果然好景不长,这一天一位同事找到了Tony并向他诉苦到:"我们需要在共享内存上使用平衡二叉树,但你提供的bb_tree只能在堆上分配内存,无法被多个进程共享,我们真不知道该怎么办了,不知道你能否修改一下你提供的库,让它也支持在共享内存上分配呢"。Tony是个自尊心很强的人,听到自己提供的公共库有缺陷,他的心中只有一个念头:尽快解决这个问题!

Tony回到座位上开始重新审视自己的设计,他满脑子都是"既要支持标准内存分配接口,又要支持在共享内存上分配"的需求。在考量了十几分钟后,他似乎知道该怎么做了,他在屏幕上敲下了如下代码:

/* balanced_binary_tree.h */
… …
struct bb_tree_t;
enum mem_allocator_flag {
    STD_ALLOCATOR,
    SHM_ALLOCATOR
};
int bb_tree_create(struct bb_tree_t **tree, enum mem_allocator_flag flag, …);
int bb_tree_destroy(struct bb_tree_t **tree, …);
… …

/* balanced_binary_tree.c */
struct bb_tree_t {
    …
    enum mem_allocator_flag flag;
};

int bb_tree_create(struct bb_tree_t **tree, enum mem_allocator_flag flag, …) {
    … …
    struct bb_tree_t *p
    if (flag == STD_ALLOCATOR)
        p = (struct bb_tree_t*)malloc(sizeof(*p));
   
    if (flag == SHM_ALLOCATOR)
        p = (struct bb_tree_t*)shm_malloc(sizeof(*p));

    p->flag = flag;
    … …
}

int bb_tree_destroy(struct bb_tree_t **tree, …) {
    … …
    if ((*tree)->flag == STD_ALLOCATOR)
        free(*tree);
   
    if ((*tree)->flag == SHM_ALLOCATOR)
        shm_free(*tree);
    … …
}

嗯,这样改后就应该支持在共享内存上分配平衡二叉树了。Tony似乎又恢复了信心,表情也不再那么死板严肃了!他伸伸懒腰,感觉有些疲倦,于是他决定趴在办公桌上小憩一会儿。恍惚中一个同事来到了他的跟前,对他说:"Tony,标准内存分配接口使用的内存分配算法效率太低,时间长了又会导致太多的内存碎片,完全不能满足我们的需求,我们希望能在平衡二叉树中使用我们自己实现的一套高性能内存分配接口,你必须尽快做出修改,不然…."。Tony瞬间从梦中惊醒,他努力地回忆了一下刚才梦中的情景,又看了看屏幕上刚刚修改过的代码,心生一丝窃喜,"还好这只是一场梦,否则这份代码又要被人嘲笑了。内存管理的接口有N多种,我总不能每支持一种新分配算法就重新发布一次吧"。Tony又一次陷入了沉思。不过这次沉思显然也没有持续多久,Tony似乎又找到了解决方案,屏幕上的代码发生了变化。

/* balanced_binary_tree.h */
… …
struct bb_tree_t;
int bb_tree_create(struct bb_tree_t **tree,
                   void (*malloc_func)(size_t size),
                   void (*free_func)(void *ptr), …);
int bb_tree_destroy(struct bb_tree_t **tree, …);
… …

/* balanced_binary_tree.c */
struct bb_tree_t {
    …
    void (*malloc_func)(size_t size),
    void (*free_func)(void *ptr)
};

int bb_tree_create(struct bb_tree_t **tree,
                   void (*malloc_func)(size_t size),
                   void (*free_func)(void *ptr), …) {
    struct bb_tree_t *p = (struct bb_tree_t*)malloc_func(sizeof(*p));
    p->malloc_func = malloc_func;
    p->free_func = free_func;
    … …
}

int bb_tree_destroy(struct bb_tree_t **tree, …) {
    (*tree)->free_func(*tree);
    … …
}

这回Tony把使用何种存储分配机制的权力完完全全地交给了库的使用者,这样纵使内存分配机制有千般变化,这里也依旧可以满足。不过这次Tony还是长了个心眼儿,没有马上将库发布给其他同事,而是从优秀代码设计的角度再次对自己的代码做了一次分析。

Tony暂时抛开了bb_tree,而是从所有类似的公共库的设计和实现角度考虑了许久。他发现如果公共库的设计都遵循将细节隐藏,只暴露必要信息的原则的话,势必都会遇到类似的如何在内部进行存储分配的问题。bb_tree使用到了malloc和free接口,但其他公共库很可能还会用到calloc、realloc等接口。一旦遇到这种情况,按照上面的方案就会出现类似下面的函数原型形式:

int xx_create(…,
              void (*malloc_func)(size_t size),
              void (*calloc)(size_t nmemb, size_t size),
              void (*realloc)(void *ptr, size_t size),
              void (*free_func)(void *ptr)…);

Tony意识到这个函数原型参数太多,使用不便,代码味道自然不好!应该重构一下,将变化的事物封装起来。综上来看,目前主要有两点易变的地方:
1) 内存分配接口需支持可替换
2) 不同公共库可能使用不同形式的内存分配接口,有的用符号malloc原型的,有的用符合calloc原型的。

"封装变化!",Tony潜意识里跳出的第一个想法。于是十几分钟过后他的电脑屏幕上就出现了下面这些代码:

/* mem_allocator.h */
struct mem_allocator_t {
    void (*malloc)(struct mem_allocator_t *allocator, size_t size),
    void (*calloc)(struct mem_allocator_t *allocator, size_t nmemb, size_t size),
    void (*realloc)(struct mem_allocator_t *allocator, void *ptr, size_t size),
    void (*free)(struct mem_allocator_t *allocator, void *ptr)
};

/* balanced_binary_tree.h */
#include "mem_allocator.h"
… …
struct bb_tree_t;
int bb_tree_create(struct bb_tree_t **tree,
                   const struct mem_allocator_t *allocator, …);
int bb_tree_destroy(struct bb_tree_t **tree, …);
… …

/* balanced_binary_tree.c */
struct bb_tree_t {
    …
    const struct mem_allocator_t *allocator;
};

int bb_tree_create(struct bb_tree_t **tree,
                   struct mem_allocator_t *allocator, …) {
    struct bb_tree_t *p = (struct bb_tree_t*)(allocator->malloc(allocator, sizeof(*p)));
    p->allocator = allocator;
    … …
}

int bb_tree_destroy(struct bb_tree_t **tree, …) {
    struct mem_allocator_t *allocator = (*tree)->allocator;
    allocator->free(allocator, *tree);
    … …
}

Tony封装出一个接口mem_allocator_t,bb_tree的创建和销毁只依赖于mem_allocator_t。我们可以为bb_tree_create传入不同的mem_allocator_t接口的实现,以支持内存分配机制的更换;另外mem_allocator_t内部封装了所有形式的通用的内存管理原型,可以满足其他公共库实现的需要。

面向对象语言可以通过继承(derive)接口、重写方法(override method)的方式实现一个接口,但在C中,我们只能像下面这样给出mem_allocator_t接口的一个实现 – shm_mem_allocator:

/* shm_mem_allocator.h */
#include "mem_allocator.h"

struct mem_allocator_t* shm_mem_allocator_new();
void shm_mem_allocator_free(struct mem_allocator_t **allocator);

/* shm_mem_allocator.c */

struct shm_mem_allocator_t {
    struct mem_allocator_t allocator;
    … … /* 其他用于实现shm_mem_allocator所需要的字段 */
};

struct mem_allocator_t* shm_mem_allocator_new() {
    struct shm_mem_allocator_t *allocator = (struct shm_mem_allocator_t*)malloc(sizeof(*allocator));
    if (!allocator)
        return NULL;
    memset(allocator, 0, sizeof(*allocator));

    allocator->allocator.malloc = shm_mem_alloc;
    allocator->allocator.calloc = shm_mem_calloc;
    allocator->allocator.realloc = shm_mem_realloc;
    allocator->allocator.free = shm_mem_free;
   
    return (struct mem_allocator_t*)allocator;
}

static void* shm_mem_malloc(struct mem_allocator_t *allocator, size_t size) {
    struct shm_mem_allocator_t *p = (struct shm_mem_allocator_t*)allocator;
    … …
}

static void* shm_mem_calloc(struct mem_allocator_t *allocator, size_t nmemb, size_t size) {
    struct shm_mem_allocator_t *p = (struct shm_mem_allocator_t*)allocator;
    … …
}

static void* shm_mem_realloc(struct mem_allocator_t *allocator, void *ptr, size_t size) {
    struct shm_mem_allocator_t *p = (struct shm_mem_allocator_t*)allocator;
    … …
}

static void shm_mem_free(struct mem_allocator_t *allocator, void *ptr) {
    struct shm_mem_allocator_t *p = (struct shm_mem_allocator_t*)allocator;
    … …
}

void shm_mem_allocator_free(struct mem_allocator_t **allocator) {
    struct shm_mem_allocator_t *p = (struct shm_mem_allocator_t*)(*allocator);
    free(p);
    (*allocator) = NULL;
}

注意shm_mem_allocator本身也涉及到内部实现的内存分配问题,但考虑到其自身的特质:我们无须在共享内存上创建mem_allocator实例对象,也无须为mem_allocator实例对象内部的内存分配算法性能担忧(只是初始化时使用一次),因此可以无须支持多种内存分配接口,利用标准内存分配接口足矣。

下面是将bb_tree与shm_mem_allocator结合在一起使用的代码:
struct bb_tree_t *tree;
ret = bb_tree_create(&tree, shm_mem_allocator_new(), …);
… …

如果你要更换bb_tree内部的内存分配器,则在调用bb_tree_create时换用你自己的mem_allocator_t接口实现即可:
struct bb_tree_t *tree;
ret = bb_tree_create(&tree, your_mem_allocator_new(), …);
… …

Tony终于找到了一份可以让自己满意的方案了。在发布代码库后,他再也没有收到来自同事们的抱怨之声。在接下来的一个星期里,Tony又用同样的设计方案让产品可以支持从多种数据库(Oracle、MySQL、Sqlite3等)读取数据。

后来,Tony在翻阅《设计模式》一书时,发现自己的解决方法与书中描述的“策略模式”甚为类似,简直就是用C语言实现的策略模式,而且这种模式几乎没有什么缺点,除了每增加一种策略(比如新增一种mem_allocator_t接口的实现),就要多维护一种策略的代码,但这样带来的负担与之前相比几乎是可以忽略不计的。




这里是Tony Bai的个人Blog,欢迎访问、订阅和留言!订阅Feed请点击上面图片

如果您觉得这里的文章对您有帮助,请扫描上方二维码进行捐赠,加油后的Tony Bai将会为您呈现更多精彩的文章,谢谢!

如果您喜欢通过微信App浏览本站内容,可以扫描下方二维码,订阅本站官方微信订阅号“iamtonybai”;点击二维码,可直达本人官方微博主页^_^:



本站Powered by Digital Ocean VPS。

选择Digital Ocean VPS主机,即可获得10美元现金充值,可免费使用两个月哟!

著名主机提供商Linode 10$优惠码:linode10,在这里注册即可免费获得。

阿里云推荐码:1WFZ0V立享9折!

View Tony Bai's profile on LinkedIn


文章

评论

  • 正在加载...

分类

标签

归档











更多