• 为什么要引入输入子系统?

在前面我们写了一些简单的字符设备的驱动程序,我们是怎么样打开一个设备并操作的呢?

一般都是在执行应用程序时,open一个特定的设备文件,如:/dev/buttons

 .....
int main(int argc, char **argv)
{
unsigned char key_val;
int ret;
fd = open("/dev/buttons", O_RDWR); //默认为阻塞操作
if (fd < )
{
printf("can't open!\n");
return -;
}
......

但是实际上,一般的应用程序不会去打开这样设备文件“/dev/buttons”。一般打开的都是系统原有的文件,如“ dev/tty* ” ,还有可能是不需要打开什么tty,
而是直接“scanf()”就去获得了按键的输入。

以前我们写一些输入设备(键盘、鼠标等)的驱动都是采用字符设备、混杂设备处理的。问题由此而来,Linux开源社区的大神们看到了这大量输入设备如此分散不堪,有木有可以实现一种机制,可以对分散的、不同类别的输入设备进行统一的驱动,所以才出现了输入子系统。

输入子系统引入的好处:

(1)统一了物理形态各异的相似的输入设备的处理功能。例如,各种鼠标,不论PS/2、USB、还是蓝牙,都被同样处理。

(2)提供了用于分发输入报告给用户应用程序的简单的事件(event)接口。你的驱动不必创建、管理/dev节点以及相关的访问方法。因此它能够很方便的调用输入API以发送鼠标移动、键盘按键,或触摸事件给用户空间。X windows这样的应用程序能够无缝地运行于输入子系统提供的event接口之上。

(3)抽取出了输入驱动的通用部分,简化了驱动,并提供了一致性。例如,输入子系统提供了一个底层驱动(成为serio)的集合,支持对串口和键盘控制器等硬件输入的访问
  详见《精通Linux设备驱动程序开发》


1.Linux输入子系统框架

linux输入子系统(linux input subsystem)从上到下由三层实现,分别为:

  输入子系统事件处理层(EventHandler)

  输入子系统核心层(InputCore)

  输入子系统设备驱动层(input driver)

1.输入子系统设备驱动层:主要实现对硬件设备的读写访问,中断设置,并把硬件产生的事件转换为核心层定义的规范提交给事件处理层。

2.核心层:承上启下。为驱动层提供输入设备注册与操作接口,如:input_register_device;通知事件处理层对事件进行处理;在/Proc下产生相应的设备信息。

    设备驱动层只要关心如何驱动硬件并获得硬件数据(例如按下的按键数据),然后调用核心层提供的接口,核心层会自动把数据提交给事件处理层。

3.事件处理层:是用户编程的接口(设备节点),并处理驱动层提交的数据处理。

    (Linux中在用户空间将所有的设备都当做文件来处理,由于在一般的驱动程序中都有提供fops接口,以及在/dev下生成相应的设备文件nod,这些操作在输入子系统中由事件处理层完成)

输入子系统中有两个类型的驱动,当我们要为一个输入设备(如触摸屏)的编写驱动的时候,我们是要编写两个驱动:输入设备驱动和输入事件驱动?

答案是否定的。在子系统中,事件驱动是标准的,对所有的输入类都是可以用的,所以你更可能的是实现输入设备驱动而不是输入事件驱动。你的设备可以利用一个已经存在的,合适的输入事件驱动通过输入核心和用户应用程序接口。

输入设备都各有不同,那么输入子系统也就只能实现他们的共性,差异性则由设备驱动来实现。差异性又体现在哪里?

最直观体现在设备功能上的不同。对于驱动编写者来说,在设备驱动中就只要使用输入子系统提供的工具(也就是函数)来完成这些“差异”就行了,其他的则是输入子系统的工作。这个思想不仅存在于输入子系统,其他子系统也是一样(比如:usb子系统、video子系统等)

先分析核心层的代码 Input.c

以下转载自Linux之输入子系统分析(详解)

Input.c

在最后有一下两段:

subsys_initcall(input_init);   //修饰入口函数

module_exit(input_exit);     //修饰出口函数

可知,子系统是作为一个模块存在的。

1.先来分析入口函数input_init

 static int __init input_init(void)
{
int err; err = class_register(&input_class); //注册类,放在/sys/class
if (err) {
printk(KERN_ERR "input: unable to register input_dev class\n");
return err;
} err = input_proc_init(); //在/proc下建立相关文件
if (err)
goto fail1; err = register_chrdev(INPUT_MAJOR, "input", &input_fops); //注册驱动
if (err) {
printk(KERN_ERR "input: unable to register char major %d", INPUT_MAJOR);
goto fail2;
} return ; fail2: input_proc_exit();
fail1: class_unregister(&input_class);
return err;
}

(1)上面第5行,class_register(&input_class),是在/sys/class中创建一个input类,input_class类结构如下:

struct class input_class = {
.name = "input",
.release = input_dev_release,
.uevent = input_dev_uevent,
};

如下图,我们启动内核,再启动一个input子系统的驱动后,也可以看到创建了个"input"类 :

疑问:为什么在此只创建了类,却没有使用class_dev_create()函数在类下面创建驱动设备?

  当注册了input子系统的驱动后,才会有设备驱动,此处代码没有驱动。以下会详细解释。

(2)上面第15行通过register_chrdev创建驱动设备,其中变量INPUT_MAJOR =13,所以创建了一个主设备为13的"input"设备。

然后我们来看看它的操作结构体input_fops,如下

static const struct file_operations input_fops = {
.owner = THIS_MODULE,
.open = input_open_file,
};

只有一个.open函数,比如当我们挂载一个新的input驱动,则内核便会调用该.open函数,接下来分析该.open函数

2.进入input_open_file函数(drivers/input/input.c)

 static int input_open_file(struct inode *inode, struct file *file)
{
struct input_handler *handler = input_table[iminor(inode) >> ]; // (1)
const struct file_operations *old_fops, *new_fops = NULL;
int err; if (!handler || !(new_fops = fops_get(handler->fops))) //(2)
return -ENODEV; if (!new_fops->open) {
fops_put(new_fops);
return -ENODEV;
} old_fops = file->f_op;
file->f_op = new_fops; //(3) err = new_fops->open(inode, file); //(4)
if (err) {
fops_put(file->f_op);
file->f_op = fops_get(old_fops);
} fops_put(old_fops); return err;
}

(1)第3行中,其中iminor (inode)函数调用了MINOR(inode->i_rdev);读取子设备号,然后将子设备除以32,找到新挂载的input驱动的数组号,然后放在input_handler 驱动处理函数handler中

(2)第7行中,若handler有值,说明挂载有这个驱动,就将handler结构体里的成员file_operations * fops赋到新的file_operations *old_fops里面

(3)第16行中, 再将新的file_operations *old_fops赋到file-> file_operations  *f_op里, 此时input子系统的file_operations就等于新挂载的input驱动的file_operations结构体,实现一个偷天换日的效果.

(4)第18行中,然后调用新挂载的input驱动的*old_fops里面的成员.open函数

3.上面代码的input_table[]数组在初始时是没有值的,

所以我们来看看input_table数组里面的数据又是在哪个函数里被赋值

在input.c函数(drivers/input/input.c)中搜索input_table,找到它在input_register_handler()函数中被赋值,代码如下:

int input_register_handler(struct input_handler *handler)
{
... ...
input_table[handler->minor >> ] = handler; //input_table[]被赋值
... ...
list_add_tail(&handler->node, &input_handler_list); //然后将这个input_handler放到input_handler_list链表中
... ...

 list_for_each_entry(dev, &input_dev_list, node) //对于每一个input_dev,调用input_attach_handler
   input_attach_handler(dev, handler);  //根据input_handler的id_table判断能否支持这个input_dev

}

就是将驱动处理程序input_handler注册到input_table[]中,然后放在input_handler_list链表中,后面会讲这个链表

4.继续来搜索input_register_handler,看看这个函数被谁来调用

如下图所示,有evdev.c(事件设备),tsdev.c(触摸屏设备),joydev.c(joystick操作杆设备),keyboard.c(键盘设备),mousedev.c(鼠标设备) 这5个内核自带的设备处理函数注册到input子系统中

以evdev.c为例,它在evdev_ini()函数中注册:

static int __init evdev_init(void)
{
return input_register_handler(&evdev_handler); //注册
}

5.我们来看看这个evdev_handler变量是什么结构体,:

 static struct input_handler evdev_handler = {
.event = evdev_event,
.connect = evdev_connect, //(4)
.disconnect = evdev_disconnect,
.fops = &evdev_fops, //(1)
.minor = EVDEV_MINOR_BASE, //(2)
.name = "evdev",
.id_table = evdev_ids, //(3)
};

就是我们之前看的input_handler驱动处理结构体

(1) 第5行中.fops:文件操作结构体,其中evdev_fops函数就是自己的写的操作函数,然后赋到.fops中

(2)第6行中 .minor:用来存放次设备号

其中EVDEV_MINOR_BASE=64, 然后调用input_register_handler(&evdev_handler)后,由于EVDEV_MINOR_BASE/32=2,所以存到input_table[2]中

所以当open打开这个input设备,就会进入 input_open_file()函数,执行evdev_handler-> evdev_fops -> .open函数,如下所示:

 static const struct file_operations evdev_fops = {
.owner = THIS_MODULE,
.read = evdev_read,
.write = evdev_write,
.poll = evdev_poll,
.open = evdev_open,
.release = evdev_release,
.unlocked_ioctl = evdev_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = evdev_ioctl_compat,
#endif
.fasync = evdev_fasync,
.flush = evdev_flush
};

(3)第8行中.id_table : 表示能支持哪些输入设备,比如某个驱动设备的input_dev->的id和某个input_handler的id_table相匹配,就会调用.connect连接函数,如下图

(4)第3行中.connect:连接函数,将设备input_dev和某个input_handler建立连接,如下图

6.我们先来看看上图的input_register_device()函数,如何创建驱动设备的

搜索input_register_device,发现内核自己就已经注册了很多驱动设备

 6.1然后进入input_register_device()函数,代码如下:

 int input_register_device(struct input_dev *dev)   //*dev:要注册的驱动设备
{
... ...
list_add_tail(&dev->node, &input_dev_list); //(1)放入链表中
... ...
list_for_each_entry(handler, &input_handler_list, node) //(2)
  input_attach_handler(dev, handler);
... ...
}

(1)第4行中,将要注册的input_dev驱动设备放在input_dev_list链表中

(2)第6行中,其中input_handler_list在前面讲过,就是存放每个input_handle驱动处理结构体,

然后list_for_each_entry()函数会将每个input_handle从链表中取出,放到handler中

最后会调用input_attach_handler()函数,将每个input_handle的id_table进行判断,若两者支持便进行连接。

  6.2然后我们在回过头来看注册input_handler的input_register_handler()函数,如下图所示

在input.c中

 int input_register_handler(struct input_handler *handler)
{
struct input_dev *dev; INIT_LIST_HEAD(&handler->h_list); if (handler->fops != NULL) {
if (input_table[handler->minor >> ])
return -EBUSY; input_table[handler->minor >> ] = handler; //放入数组
} list_add_tail(&handler->node, &input_handler_list); //放入链表
    
list_for_each_entry(dev, &input_dev_list, node)  //对于每一个input_dev,调用input_attach_handler
input_attach_handler(dev, handler);

input_wakeup_procfs_readers();
return ;

所以,不管新添加input_dev还是input_handler,都会进入input_attach_handler()判断两者id是否有支持, 若两者支持便进行连接

  6.3我们来看看input_attach_handler()如何实现匹配两者id的:

 static int input_attach_handler(struct input_dev *dev, struct input_handler *handler)
{
... ...
id = input_match_device(handler->id_table, dev); //匹配两者 if (!id) //若不匹配,return退出
return -ENODEV; error = handler->connect(handler, dev, id); //调用input_handler ->connect函数建立连接
... ... }

根据input_handler的id_table判断能否支持这个input_dev
如果能支持,则调用input_handler的connect函数建立“连接”

7.以evdev.c(事件驱动) 的evdev_handler->connect函数来分析是怎样建立连接的,如下:

 static struct input_handler evdev_handler = {
.event = evdev_event,
.connect = evdev_connect,
.disconnect = evdev_disconnect,
.fops = &evdev_fops,
.minor = EVDEV_MINOR_BASE,
.name = "evdev",
.id_table = evdev_ids,
};

  7.1 evdev_handler的.connect函数是evdev_connect(),代码如下:

 static int evdev_connect(struct input_handler *handler, struct input_dev *dev, const struct input_device_id *id)
{
... ...
for (minor = ; minor < EVDEV_MINORS && evdev_table[minor]; minor++); //查找驱动设备的子设备号
if (minor == EVDEV_MINORS) { // EVDEV_MINORS=32,所以该事件下的驱动设备最多存32个,
printk(KERN_ERR "evdev: no more free evdev devices\n");
return -ENFILE; //没找到驱动设备
}
... ...
evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL); //分配一个input_handle全局结构体(没有r)
... ...
evdev->handle.dev = dev; //指向参数input_dev驱动设备
evdev->handle.name = evdev->name;
evdev->handle.handler = handler; //指向参数 input_handler驱动处理结构体
evdev->handle.private = evdev;
sprintf(evdev->name, "event%d", minor); //(1)保存驱动设备名字, event%d
... ...
devt = MKDEV(INPUT_MAJOR, EVDEV_MINOR_BASE + minor), //(2) 将主设备号和次设备号转换成dev_t类型
cdev = class_device_create(&input_class, &dev->cdev, devt,dev->cdev.dev, evdev->name);
// (3)在input类下创建驱动设备 ... ...
error = input_register_handle(&evdev->handle); //(4)注册这个input_handle结构体 ... ...
}

(1) 第16行中,是在保存驱动设备名字,名为event%d, 比如下图(键盘驱动)event1: 因为没有设置子设备号,默认从小到大排列,其中event0是表示这个input子系统,所以这个键盘驱动名字就是event1

(2)第18行中,是在保存驱动设备的主次设备号,其中主设备号INPUT_MAJOR=13,因为EVDEV_MINOR_BASE=64,所以此设备号=64+驱动程序本事子设备号, 比如下图(键盘驱动)event1:  主次设备号就是13,65

(3)在之前在2小结里就分析了input_class类结构,所以第19行中,会在/sys/class/input类下创建驱动设备event%d,比如下图(键盘驱动)event1:

(4)最终会进入input_register_handle()函数来注册,代码在下面

 

 7.2 input_register_handle()函数如下:

 int input_register_handle(struct input_handle *handle)
{
struct input_handler *handler = handle->handler; //handler= input_handler驱动处理结构体 list_add_tail(&handle->d_node, &handle->dev->h_list); //(1)
list_add_tail(&handle->h_node, &handler->h_list); // (2) if (handler->start)
handler->start(handle);
return ;
}

(1)在第5行中, 因为handle->dev指向input_dev驱动设备,所以就是将handle->d_node放入到input_dev驱动设备的h_list链表中,

即input_dev驱动设备的h_list链表就指向handle->d_node

(2) 在第6行中, 同样, input_handler驱动处理结构体的h_list也指向了handle->h_node

最终如下图所示:

两者的.h_list都指向了同一个handle结构体,然后通过.h_list 来找到handle的成员.dev和handler,便能找到对方,便建立了连接

8.建立了连接后,又如何读取evdev.c(事件驱动) 的evdev_handler->.fops->.read函数?

事件驱动的.read函数是evdev_read()函数,我们来分析下:

 static ssize_t evdev_read(struct file *file, char __user *      buffer, size_t count, loff_t *ppos)
{
... ...
/*判断应用层要读取的数据是否正确*/
if (count < evdev_event_size())
return -EINVAL; /*在非阻塞操作情况下,若client->head == client->tail|| evdev->exist时(没有数据),则return返回*/
if (client->head == client->tail && evdev->exist && (file->f_flags & O_NONBLOCK))
return -EAGAIN; /*若client->head == client->tail|| evdev->exist时(没有数据),等待中断进入睡眠状态 */
retval = wait_event_interruptible(evdev->wait,client->head != client->tail || !evdev->exist); ... ... //上传数据 }

9.若read函数进入了休眠状态,又是谁来唤醒?

我们搜索这个evdev->wait这个等待队列变量,找到evdev_event函数里唤醒:

static void evdev_event(struct input_handle *handle, unsigned int type, unsigned int code, int value)
{
... ...
wake_up_interruptible(&evdev->wait); //有事件触发,便唤醒等待中断
}

其中evdev_event()是evdev.c(事件驱动) 的evdev_handler->.event成员,如下所示:

 static struct input_handler evdev_handler = {
.event = evdev_event,
.connect = evdev_connect,
.disconnect = evdev_disconnect,
.fops = &evdev_fops,
.minor = EVDEV_MINOR_BASE,
.name = "evdev",
.id_table = evdev_ids,
};

当有事件发生了,比如对于按键驱动,当有按键按下时,就会进入.event函数中处理事件

10.分析下,是谁调用evdev_event()这个.event事件驱动函数

猜测:硬件相关的代码(input_dev那层)--中断处理函数

来看看内核 gpio_keys_isr()函数代码例子就知道了 (driver/input/keyboard/gpio_key.c)

 static irqreturn_t gpio_keys_isr(int irq, void *dev_id)
{
/*获取按键值,赋到state里*/
... ... /*上报事件*/
input_event(input, type, button->code, !!state);
input_sync(input); //同步信号通知,表示事件发送完毕
}

显然就是通过input_event()来调用.event事件函数,我们来看看:

 void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value)
{
struct input_handle *handle;
... ... /* 通过input_dev ->h_list链表找到input_handle驱动处理结构体*/
list_for_each_entry(handle, &dev->h_list, d_node)
if (handle->open) //如果input_handle之前open 过,那么这个就是我们的驱动处理结构体
handle->handler->event(handle, type, code, value); //调用evdev_event()的.event事件函数 }

若之前驱动input_dev和处理input_handler已经通过input_handler 的.connect函数建立起了连接,那么就调用evdev_event()的.event事件函数,如下图所示:

11.本节总结分析:

1.注册输入子系统,进入input_init():

1)创建主设备号为13的"input"字符设备

err = register_chrdev(INPUT_MAJOR, "input", &input_fops);

2.open打开驱动,进入input_open_file():

1)更新设备的file_oprations

file->f_op=fops_get(handler->fops);

2)执行file_oprations->open函数

err = new_fops->open(inode, file);

3.注册input_handler,进入input_register_handler():

1)添加到input_table[]处理数组中

input_table[handler->minor >> ] = handler;

2)添加到input_handler_list链表中

list_add_tail(&handler->node, &input_handler_list);

3)判断input_dev的id,是否有支持这个驱动的设备

list_for_each_entry(dev, &input_dev_list, node)   //遍历查找input_dev_list链表里所有input_dev

 input_attach_handler(dev, handler);             //判断两者id,若两者支持便进行连接。

4.注册input_dev,进入input_register_device():

1)放在input_dev_list链表中

list_add_tail(&dev->node, &input_dev_list);

2)判断input_handler的id,是否有支持这个设备的驱动

list_for_each_entry(handler, &input_handler_list, node)  //遍历查找input_handler_list链表里所有input_handler
input_attach_handler(dev, handler); //判断两者id,若两者支持便进行连接。

5.判断input_handlerinput_devid,进入input_attach_handler():

1)匹配两者id,

input_match_device(handler->id_table, dev);        //匹配input_handler和dev的id,不成功退出函数

2)匹配成功调用input_handler ->connect

handler->connect(handler, dev, id);              //建立连接

6.建立input_handlerinput_dev的连接,进入input_handler->connect():

1)创建全局结构体,通过input_handle结构体连接双方

evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);    //创建两者连接的input_handle全局结构体
list_add_tail(&handle->d_node, &handle->dev->h_list); //连接input_dev->h_list
list_add_tail(&handle->h_node, &handler->h_list); // 连接input_handle->h_list

7.有事件发生时,比如按键中断,在中断函数中需要进入input_event()上报事件:

1)找到驱动处理结构体,然后执行input_handler->event()

list_for_each_entry(handle, &dev->h_list, d_node)     // 通过input_dev ->h_list链表找到input_handle驱动处理结构体
if (handle->open) //如果input_handle之前open 过,那么这个就是我们的驱动处理结构体(有可能一个驱动设备在不同情况下有不同的驱动处理方式)
handle->handler->event(handle, type, code, value); //调用evdev_event()的.event事件函数

7.Linux 输入子系统分析的更多相关文章

  1. Linux输入子系统框架分析(1)

    在Linux下的输入设备键盘.触摸屏.鼠标等都能够用输入子系统来实现驱动.输入子系统分为三层,核心层和设备驱动层.事件层.核心层和事件层由Linux输入子系统本身实现,设备驱动层由我们实现.我们在设备 ...

  2. linux input输入子系统分析《四》:input子系统整体流程全面分析

    1      input输入子系统整体流程 本节分析input子系统在内核中的实现,包括输入子系统(Input Core),事件处理层(Event Handler)和设备驱动层.由于上节代码讲解了设备 ...

  3. Linux输入子系统(Input Subsystem)

    Linux输入子系统(Input Subsystem) http://blog.csdn.net/lbmygf/article/details/7360084 input子系统分析  http://b ...

  4. Linux input子系统分析

    输入输出是用户和产品交互的手段,因此输入驱动开发在Linux驱动开发中很常见.同时,input子系统的分层架构思想在Linux驱动设计中极具代表性和先进性,因此对Linux input子系统进行深入分 ...

  5. Linux输入子系统详解

    input输入子系统框架  linux输入子系统(linux input subsystem)从上到下由三层实现,分别为:输入子系统事件处理层(EventHandler).输入子系统核心层(Input ...

  6. linux输入子系统

    linux输入子系统(linux input subsystem)从上到下由三层实现,分别为:输入子系统事件处理层(EventHandler).输入子系统核心层(InputCore)和输入子系统设备驱 ...

  7. linux输入子系统简述【转】

    本文转载自:http://blog.csdn.net/xubin341719/article/details/7678035 1,linux输入子系统简述 其实驱动这部分大多还是转载别人的,linux ...

  8. Linux输入子系统 转载

    NQian 记录成长~ 首页 新随笔 联系 订阅 管理 随笔 - 305  文章 - 0  评论 - 254 12.Linux之输入子系统分析(详解)   在此节之前,我们学的都是简单的字符驱动,涉及 ...

  9. linux输入子系统(input subsystem)之evdev.c事件处理过程

    1.代码 input_subsys.drv.c 在linux输入子系统(input subsystem)之按键输入和LED控制的基础上有小改动,input_subsys_test.c不变. input ...

随机推荐

  1. 一个美丽的java烟花程序

    <img src="http://img.blog.csdn.net/20150625104525974?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi ...

  2. CoAP与物联网系统

    CoAP简单介绍 引自维基百科上的介绍,用的是谷歌翻译... 受约束的应用协议(COAP)是一种软件协议旨在以很easy的电子设备.使他们能够在互联网上进行交互式通信中使用. 它特别针对小型低功率传感 ...

  3. CXF实战之在Tomcat中公布Web Service(二)

    服务接口及实现类请參考WebService框架CXF实战(一) 创建Maven Web项目,在pom.xml中加入CXF和Spring Web的引用,因为CXFServlet须要Spring Web的 ...

  4. lightoj--1008--Fibsieve`s Fantabulous Birthday(水题)

    Fibsieve`s Fantabulous Birthday Time Limit: 500MS   Memory Limit: 32768KB   64bit IO Format: %lld &a ...

  5. decimal.ToString("#0.00")与decimal.ToString("#.##")的区别

    decimal decTemp = 2.1m; Console.WriteLine(decTemp.ToString("#0.00")); //输出2.10 Console.Wri ...

  6. Sub Thread to update main Thread (UI)

    Sub Thread to update main Thread (UI) main Thread :   A  has Hander.HandleMessage() to process the & ...

  7. stat---显示文件的状态信息

    stat命令用于显示文件的状态信息.stat命令的输出信息比ls命令的输出信息要更详细. 语法 stat(选项)(参数) 选项 -L:支持符号连接: -f:显示文件系统状态而非文件状态: -t:以简洁 ...

  8. 紫书 例题 9-14 UVa 1218 (树形dp)

    这道题有个初始值设成1e9, 然后这个值是要加很多次的,然后就会溢出变成负数, 然后就一直WA, 找这个bug找了一个小时--以后不能随便这样设那么大, 要考虑会不会加很多次然后溢出. 讲一下思路. ...

  9. IDEA 官方教程

    https://www.jetbrains.com/help/idea/discover-intellij-idea.html#UserInterface

  10. 设计模式之Build(生成者模式)

    一.生成器模式的定义: 生成器模式也称为建造者模式.生成器模式的意图在于将一个复杂的构建与其表示相分离,使得同样的构建过程可以创建不同的表示(GoF).在软件设计中,有时候面临着一个非常复杂的对象的创 ...