设计模式的书相信很多人都看过。对于设计模式这样一种方法,相信不同的人有不同的理解。
C语言和设计模式(开篇)
关于软件设计方面的书很多,比如《 重构》,比如《 设计模式》。至于软件开发方式,那就更多了,什么极限编程、精益方法、敏捷方法。随着时间的推移,很多的方法又会被重新提出来。
其实,就我个人看来,不管什么方法都离不开人。一个人写不出二叉树,你怎么让他写?敏捷吗?你写一行,我写一行。还是迭代?写三行,删掉两行,再写三行。项目的成功是偶然的,但是项目的失败却有很多原因,管理混乱、需求混乱、设计低劣、代码质量差、测试不到位等等。就软件企业而言,没有比优秀的文化和出色的企业人才更重要的了。
从软件设计层面来说,一般来说主要包括三个方面:
(1)软件的设计受众,是小孩子、老人、女性,还是专业人士等等;
(2)软件的基本设计原则,以人为本、模块分离、层次清晰、简约至上、适用为先、抽象基本业务等等;
(3)软件编写模式,比如装饰模式、责任链、单件模式等等。
从某种意义上说,设计思想构成了软件的主题。软件原则是我们在开发中的必须遵循的准绳。软件编写模式是开发过程中的重要经验总结。灵活运用设计模式,一方面利于我们编写高质量的代码,另一方面也方便我们对代码进行维护。毕竟对于广大的软件开发者来说,软件的维护时间要比软件编写的时间要多得多。编写过程中,难免要有新的需求,要和别的模块打交道,要对已有的代码进行复用,那么这时候设计模式就派上了用场。我们讨论的主题其实就是设计模式。
讲到设计模式,人们首先想到的语言就是c#或者是java,最不济也是c++,一般来说没有人会考虑到c语言。其实,我认为设计模式就是一种基本思想,过度美化或者神化其实没有必要。其实阅读过linux kernel的朋友都知道,linux虽然自身支持很多的文件系统,但是linux自身很好地把这些系统的基本操作都抽象出来了,成为了基本的虚拟文件系统。
举个例子来说,现在让你写一个音乐播放器,但是要支持的文件格式很多,什么ogg,wav,mp3啊,统统要支持。这时候,你会怎么编写呢?如果用C++语言,你可能会这么写。
class music_file
{
HANDLE hFile;
public:
void music_file() {
}
virtual ~music_file() {
}
virtual void read_file() {
}
virtual void play() {
}
virtual void stop() {
}
virtual void back() {
}
virtual void front() {
}
virtual void up() {
}
virtual void down() {
}
};
其实,你想想看,如果用C语言能够完成相同的抽象操作,那不是效果一样的吗?
typedef struct _music_file
{
HANDLE hFile;
void (*read_file)(struct _music_file* pMusicFile);
void (*play)(struct _music_file* pMusicFile);
void (*stop)(struct _music_file* pMusicFile);
void (*back)(struct _music_file* pMusicFile);
void (*front)(struct _music_file* pMusicFile);
void (*down)(struct _music_file* pMusicFile);
void (*up)(struct _music_file* pMusicFile);
}music_file;
当然,上面的例子比较简单,但是也能说明一些问题。写这篇文章的目的一是希望和朋友们共同学习模式的相关内容,另一方面也希望朋友们能够活学活用,既不要迷信权威,也不要妄自菲薄。只要付出努力,付出汗水,肯定会有收获的。有些大环境你改变不了,那就从改变自己开始。万丈高楼平地起,一步一个脚印才能真真实实学到东西。如果盲目崇拜,言必google、微软、apple,那么除了带来几个唾沫星,还能有什么受用呢?无非白费了口舌而已。
C语言和设计模式(之单件模式)
有过面试经验的朋友,或者对设计模式有点熟悉的朋友,都会对单件模式不陌生。对很多面试官而言,单件模式更是他们面试的保留项目。其实,我倒认为,单件模式算不上什么设计模式。最多也就是个技巧。
单件模式要是用C++写,一般这么写。
#include <string.h>
#include <assert.h>
class object
{
public:
static class object* pObject;
static object* create_new_object()
{
if(NULL != pObject)
return pObject;
pObject = new object();
assert(NULL != pObject);
return pObject;
}
private:
object() {
}
~object() {
}
};
class object* object::pObject = NULL;
单件模式的技巧就在于类的构造函数是一个私有的函数。但是类的构造函数又是必须创建的?怎么办呢?那就只有动用static函数了。我们看到static里面调用了构造函数,就是这么简单。
int main(int argc, char* argv[])
{
object* pGlobal = object::create_new_object();
return 1;
}
上面说了C++语言的编写方法,那C语言怎么写?其实也简单。大家也可以试一试。
typedef struct _DATA
{
void* pData;
}DATA;
void* get_data()
{
static DATA* pData = NULL;
if(NULL != pData)
return pData;
pData = (DATA*)malloc(sizeof(DATA));
assert(NULL != pData);
return (void*)pData;
}
C语言和设计模式(之原型模式)
原型模式本质上说就是对当前数据进行复制。就像变戏法一样,一个鸽子变成了两个鸽子,两个鸽子变成了三个鸽子,就这么一直变下去。在变的过程中,我们不需要考虑具体的数据类型。为什么呢?因为不同的数据有自己的复制类型,而且每个复制函数都是虚函数。
用C++怎么编写呢,那就是先写一个基类,再编写一个子类。就是这么简单。
class data
{
public:
data () {
}
virtual ~data() {
}
virtual class data* copy() = 0;
};
class data_A : public data
{
public:
data_A() {
}
~data_A() {
}
class data* copy()
{
return new data_A();
}
};
class data_B : public data
{
public:
data_B() {
}
~data_B() {
}
class data* copy()
{
return new data_B();
}
};
那怎么使用呢?其实只要一个通用的调用接口就可以了。
class data* clone(class data* pData)
{
return pData->copy();
}
就这么简单的一个技巧,对C来说,当然也不是什么难事。
typedef struct _DATA
{
struct _DATA* (*copy) (struct _DATA* pData);
}DATA;
假设也有这么一个类型data_A,
DATA data_A = {
data_copy_A};
既然上面用到了这个函数,所以我们也要定义啊。
struct _DATA* data_copy_A(struct _DATA* pData)
{
DATA* pResult = (DATA*)malloc(sizeof(DATA));
assert(NULL != pResult);
memmove(pResult, pData, sizeof(DATA));
return pResult;
};
使用上呢,当然也不含糊。
struct _DATA* clone(struct _DATA* pData)
{
return pData->copy(pData);
};
C语言和设计模式(之组合模式)
组合模式听说去很玄乎,其实也并不复杂。为什么?大家可以先想一下数据结构里面的二叉树是怎么回事。为什么就是这么一个简单的二叉树节点既可能是叶节点,也可能是父节点?
typedef struct _NODE
{
void* pData;
struct _NODE* left;
struct _NODE* right;
}NODE;
那什么时候是叶子节点,其实就是left、right为NULL的时候。那么如果它们不是NULL呢,那么很明显此时它们已经是父节点了。那么,我们的这个组合模式是怎么一个情况呢?
typedef struct _Object
{
struct _Object** ppObject;
int number;
void (*operate)(struct _Object* pObject);
}Object;
就是这么一个简单的数据结构,是怎么实现子节点和父节点的差别呢。比如说,现在我们需要对一个父节点的operate进行操作,此时的operate函数应该怎么操作呢?
void operate_of_parent(struct _Object* pObject)
{
int index;
assert(NULL != pObject);
assert(NULL != pObject->ppObject && 0 != pObject->number);
for(index = 0; index < pObject->number; index ++)
{
pObject->ppObject[index]->operate(pObject->ppObject[index]);
}
}
当然,有了parent的operate,也有child的operate。至于是什么操作,那就看自己是怎么操作的了。
void operate_of_child(struct _Object* pObject)
{
assert(NULL != pObject);
printf("child node!\n");
}
父节点也好,子节点也罢,一切的一切都是最后的应用。其实,用户的调用也非常简单,就这么一个简单的函数。
void process(struct Object* pObject)
{
assert(NULL != pObject);
pObject->operate(pObject);
}
C语言和设计模式(之模板模式)
模板对于学习C++的同学,其实并不陌生。函数有模板函数,类也有模板类。那么这个模板模式是个什么情况?我们可以思考一下,模板的本质是什么。比如说,现在我们需要编写一个简单的比较模板函数。
template <typename type>
int compare (type a, type b)
{
return a > b ? 1 : 0;
}
模板函数提示我们,只要比较的逻辑是确定的,那么不管是什么数据类型,都会得到一个相应的结果。固然,这个比较的流程比较简单,即使没有采用模板函数也没有关系。但是,要是需要拆分的步骤很多,那么又该怎么办呢?如果相通了这个问题,那么也就明白了什么是template模式。
比方说,现在我们需要设计一个流程。这个流程有很多小的步骤完成。然而,其中每一个步骤的方法是多种多样的,我们可以很多选择。但是,所有步骤构成的逻辑是唯一的,那么我们该怎么办呢?其实也简单。那就是在基类中除了流程函数外,其他的步骤函数全部设置为virtual函数即可。
class basic
{
public:
void basic() {
}
virtual ~basic() {
}
virtual void step1() {
}
virtual void step2() {
}