Chinaunix首页 | 论坛 | 博客
  • 博客访问: 2705164
  • 博文数量: 505
  • 博客积分: 1552
  • 博客等级: 上尉
  • 技术积分: 2514
  • 用 户 组: 普通用户
  • 注册时间: 2007-09-23 18:24
文章分类

全部博文(505)

文章存档

2019年(12)

2018年(15)

2017年(1)

2016年(17)

2015年(14)

2014年(93)

2013年(233)

2012年(108)

2011年(1)

2009年(11)

分类: C/C++

2013-04-18 10:27:16

作者:gfree.wind@gmail.com
博客:blog.focus-linux.net     linuxfocus.blog.chinaunix.net

今天学习工厂模式。

工厂模式一共有三种:简单工厂模式,工厂方法模式,抽象工厂模式。无论是哪种工厂模式,都是为了将创建对象的代码封装起来,因为实例化对象的代码是很可能变化的。这再次凸显了,设计模式的思想,封装变化。

简单工厂模式:其实并不是一个真正的设计模式,而更像一种编程习惯或者技巧。一个简单的实现基本上就是一个switch... case...即可。因此简单工厂模式不是我们关心的重点。


工厂方法模式:定义了一个创建对象的接口,但由于子类决定要实例化的类是哪一个。工厂方法让类把实例化推迟到子类。

抽象工厂模式:提供一个接口,用于创建相关或依赖对象的家族,而不需要明确指定具体类。

从定义中基本上已经看出工厂方法模式和抽象工厂模式的区别。抽象工厂方法,用于创建一组相关的对象,而工厂方法模式用于创建一个对象。一般情况下,抽象工厂方法中创建一组对象中的一个对象时,都会利用工厂模式来创建该对象。那么,工厂方法是使用继承,把对象的创建委托给子类,子类实现工厂方法来创建对象。抽象工厂使用对象组合,每个对象由工厂方法实现。

从上面的定义上看,抽象工厂模式依赖于工厂方法模式,工厂方法模式依赖于继承。那么作为面向过程的C语言,如何实现工厂方法模式呢?

在Linux的内核代码中,尽管使用的是C语言实现,但是却有不少object的概念。下面以socket为例,来体现工厂模式的应用。

创建socket的系统调用原型:
  1. int socket(int domain, int type, int protocol);
通过传入不同的domain,type,和protocol来创建不同的socket。

创建socket的函数为__sock_create,位于kernel的源码文件net/socket.c。
  1. static int __sock_create(struct net *net, int family, int type, int protocol,
  2.              struct socket **res, int kern)
  3. {
  4.     int err;
  5.     struct socket *sock;
  6.     const struct net_proto_family *pf;

  7.     /*
  8.      * Check protocol is in range
  9.      */
  10.     if (family < 0 || family >= NPROTO)
  11.         return -EAFNOSUPPORT;
  12.     if (type < 0 || type >= SOCK_MAX)
  13.         return -EINVAL;

  14.     /* Compatibility.

  15.      This uglymoron is moved from INET layer to here to avoid
  16.      deadlock in module load.
  17.      */
  18.     if (family == PF_INET && type == SOCK_PACKET) {
  19.         static int warned;
  20.         if (!warned) {
  21.             warned = 1;
  22.             printk(KERN_INFO "%s uses obsolete (PF_INET,SOCK_PACKET)\n",
  23.              current->comm);
  24.         }
  25.         family = PF_PACKET;
  26.     }

  27.     err = security_socket_create(family, type, protocol, kern);
  28.     if (err)
  29.         return err;

  30.     /*
  31.      *    Allocate the socket and allow the family to set things up. if
  32.      *    the protocol is 0, the family is instructed to select an appropriate
  33.      *    default.
  34.      */
  35.     sock = sock_alloc();
  36.     if (!sock) {
  37.         if (net_ratelimit())
  38.             printk(KERN_WARNING "socket: no more sockets\n");
  39.         return -ENFILE;    /* Not exactly a match, but its the
  40.                  closest posix thing */
  41.     }

  42.     sock->type = type;

  43. #ifdef CONFIG_MODULES
  44.     /* Attempt to load a protocol module if the find failed.
  45.      *
  46.      * 12/09/1996 Marcin: this makes REALLY only sense, if the user
  47.      * requested real, full-featured networking support upon configuration.
  48.      * Otherwise module support will
  49.      */
  50.     if (net_families[family] == NULL)
  51.         request_module("net-pf-%d", family);
  52. #endif

  53.     rcu_read_lock();
  54.     pf = rcu_dereference(net_families[family]);
  55.     err = -EAFNOSUPPORT;
  56.     if (!pf)
  57.         goto out_release;

  58.     /*
  59.      * We will call the ->create function, that possibly is in a loadable
  60.      * module, so we have to bump that loadable module refcnt first.
  61.      */
  62.     if (!try_module_get(pf->owner))
  63.         goto out_release;

  64.     /* Now protected by module ref count */
  65.     rcu_read_unlock();

  66.     err = pf->create(net, sock, protocol, kern);
  67.     if (err < 0)
  68.         goto out_module_put;

  69.     /*
  70.      * Now to bump the refcnt of the [loadable] module that owns this
  71.      * socket at sock_release time we decrement its refcnt.
  72.      */
  73.     if (!try_module_get(sock->ops->owner))
  74.         goto out_module_busy;

  75.     /*
  76.      * Now that we're done with the ->create function, the [loadable]
  77.      * module can have its refcnt decremented
  78.      */
  79.     module_put(pf->owner);
  80.     err = security_socket_post_create(sock, family, type, protocol, kern);
  81.     if (err)
  82.         goto out_sock_release;
  83.     *res = sock;

  84.     return 0;

  85. out_module_busy:
  86.     err = -EAFNOSUPPORT;
  87. out_module_put:
  88.     sock->ops = NULL;
  89.     module_put(pf->owner);
  90. out_sock_release:
  91.     sock_release(sock);
  92.     return err;

  93. out_release:
  94.     rcu_read_unlock();
  95.     goto out_sock_release;
  96. }
关于这段代码的分析,可以参加我之前的拙作。这里就不分析代码了。

在这段代码中,如果是针对不同的参数,而创建不同的socket,那么就是简单工厂模式。而当前使用的方法是通过调用pf->create(net, sock, protocol, kern)这个回调函数来创建socket。而pf是由
pf = rcu_dereference(net_families[family]);得到的。比如创建AF_INET的socket时,net_families[AF_INET]实际上为inet_family_ops,那么socket的创建函数pf->create,调用的是inet_create。

也就是说socket的创建,有一个统一的接口socket,而具体的socket创建则根据domain,由不同的net_families[family]实现。这只是socket的创建,而socket的操作各种API如read,write,也是使用了类似的工厂模式,在同一个domain下,通过不同的protocol,由inet_protos[proto_hash]来调用针对不同protocol的API。

这个例子,看似不是很贴切,但是实际上的思想,确实如工厂模式相类似。无论是对象的创建,还是操作,都是通过统一接口的封装,而将具体的实现交给具体的socket的实现。这样的设计给使用者带了方便,因为调用者无需关心object的细节,而使用统一的API。如socket编程中,我们可以使用统一的socket,read,write,close来完成socket编程。

这里,也体现了设计模式中的一个关键思想,“针对接口编程,而不是针对实现编程”。

Note:
对于设计模式,我一直认为不要去生搬硬套的去使用设计模式,更无需去记住哪个模式的名字是什么,特性是什么。无论是什么语言,我们只需记住封装变化,保持灵活。设计模式要信手拈来,领会思想,灵活使用。

参考:
1. 《设计模式——可复用面向对象软件的基础》
2. 《Head First 设计模式》

阅读(803) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~