上个周末花了些时间将《Pro Git》(Git高手进阶之必读书籍,严重推荐^_^)快速地浏览了一遍,在感叹于Git强大的同时,也见识到了Git的复杂。可以肯定的是Git学习曲线远没有学习Subversion那样平坦。比如,Subversion工作目录下的文件只有三种状态:Untracked、Modified和Committed(即Unmodified);而以Git本地工作目录下则有四种状态:Untracked、Staged、Modified和Committed(即Unmodified)。虽然只多出了一种状态,但感觉其复杂度又上了一个台阶。

Git在这里只是一个引子,我真正要说的还是设计模式,只不过这个模式对应的例子实现与Git的一个命令相关罢了。这个命令就是Git status。Git status可以根据当前工作目录下文件的不同状态输出不同的提示信息,例如,对于工作目录中处于"未跟踪"状态的文件foo.txt,Git会输出下面信息:
$ git status
# On branch master
#
# Untracked files:
#   (use "git add [file]…" to include in what will be committed)
#
#    foo.txt
nothing added to commit but untracked files present (use "git add" to track)

而对于工作目录下处于已修改(modified),但未缓存(unstaged)的文件foo.txt,它的输出就会变成:
$ git status
# On branch master
# Changed but not updated:
#   (use "git add [file]…" to update what will be committed)
#   (use "git checkout — [file]…" to discard changes in working directory)
#
#    modified:   foo.txt
#
no changes added to commit (use "git add" and/or "git commit -a")

好了,假如你是负责实现这个功能的C程序员,你会如何来实现它呢?是这样吗:
void git_status(const struct file_t *file) {
    switch(file->status) {
        case UNTRACKED:
            …

        case STAGED:
            …

        case MODIFIED:
            …

        case COMMITED:
            …

        default:
            …
    }
}

对于众多设计模式的忠实粉丝来说,这样的实现势必会"犯众怒":怎么可以有switch…case呢,怎么可以让git_status与file_t的内部状态值耦合在一起呢?经验告诉我们:遇到问题,找模式!这次的题目似乎给了我们很直观的提示:我们应该用State模式来改造git_status的实现。

首先抽出接口file_state_t。

/* file_state.h */
struct file_state_t {
    void (*file_state_func)(struct file_state_t *this, const char *filename, void *arg);
};

接下来,我们给出各位文件状态的实现,包括untracked_file_state、modified_file_state、committed_file_state以及staged_file_state,为了节省篇幅这里谨以untracked_file_state为例:

/* untracked_file_state.h */
struct file_state_t* untracked_file_state_instance();
void untracked_file_state_destroy();

/* untracked_file_state.c */
struct untracked_file_state_t {
    struct file_state_t fs;
    /* other fields here… */
};

static struct untracked_file_state_t *_untracked_file_state = NULL;

static void dump_untracked_file_state(struct file_state_t *this, const char *filename, void *arg) {
    printf("# Untracked files:\n"
            "#   (use \"git add [file]…\" to include in what will be committed)\n"
            "#\n"
            "#    %s\n"
            "nothing added to commit but untracked files present (use \"git add\" to track)\n",
            filename);
}

struct file_state_t* untracked_file_state_instance() {
    if (!_untracked_file_state) {
        _untracked_file_state = (struct untracked_file_state_t*)malloc(sizeof(*_untracked_file_state));
        if (!_untracked_file_state) return NULL;

        memset(_untracked_file_state, 0, sizeof(*_untracked_file_state));
        _untracked_file_state->fs.file_state_func = dump_untracked_file_state;
    }

    return (struct file_state_t*)_untracked_file_state;
}

void untracked_file_state_destroy() {
    if (_untracked_file_state)
        free(_untracked_file_state);
    _untracked_file_state =  NULL;
}

untracked_file_state_t对象的创建方式采用了类似Singleton模式的手法,减少了频繁创建销毁带来的消耗,在后面使用这个state对象时我们会看得更加清楚。其他几个file_state_t接口的实现大同小异,不同的是dump_xx_file_state的实现。

最后,将各个State对象用于模拟Git场景中,我们来看看效果:

/* main.c */
struct file_t {
    char filename[PATH_MAX];
    struct file_state_t *state;
};

static struct file_t* file_new(const char *filename) {
    struct file_t *f = (struct file_t*)malloc(sizeof(*f));
    if (!f) return NULL;

    memset(f, 0, sizeof(*f));
    strcpy(f->filename, filename);

    /* 文件的初始状态: Untracked */
    f->state = untracked_file_state_instance();
    if (!f->state) {
        free(f);
        return NULL;
    }

    return f;
}

static void file_status(struct file_t *f) {
    f->state->file_state_func(f->state, f->filename, NULL);
}

static void file_add(struct file_t *f) {
    f->state = staged_file_state_instance();
}

static void file_commit(struct file_t *f) {
    f->state = committed_file_state_instance();
}

static void file_modified(struct file_t *f) {
    f->state = modified_file_state_instance();
}

int main(int argc, const char *argv[])
{
    struct file_t *f = file_new("foo.txt");
    file_status(f);

    file_add(f);
    file_status(f);

    file_commit(f);
    file_status(f);

    file_modified(f);
    file_status(f);

    return 0;
}

这个程序的输出结果与预期完全一致。没有了switch…case,没有了实现耦合,这下很多模式Fans怒火可以消消了。不过State模式的这种实现缺点也很明显,那就是一旦状态众多,对应的file_state_t接口实现的数量也就随着增多,从实现角度来看,代码似乎有些散。

从例子中我们可以看出这种State模式的实现是一种行为驱动的状态迁移,这种状态迁移是由State对象的使用者在上下文完成的。

用C语言亲手实现了多个模式后(IteratorObserverStrategyChain of ResponsibilityTransaction),愈来愈觉得其内在的思维方式是一致的。因此以后面对问题也大可不必拘泥于某一种模式,而是要融会贯通,以无招胜有招,路子对了,一切也就水到渠成了。

© 2011, bigwhite. 版权所有.

Related posts:

  1. Transaction模式的C实现
  2. Observer模式的C实现
  3. Strategy模式的C实现
  4. Chain of Responsibility模式的C实现
  5. C单元测试之使用cmockery