作者:gfree.wind@gmail.com博客:blog.focus-linux.net linuxfocus.blog.chinaunix.net
今天学习工厂模式。
工厂模式一共有三种:简单工厂模式,工厂方法模式,抽象工厂模式。无论是哪种工厂模式,都是为了将创建对象的代码封装起来,因为实例化对象的代码是很可能变化的。这再次凸显了,设计模式的思想,封装变化。
简单工厂模式:其实并不是一个真正的设计模式,而更像一种编程习惯或者技巧。一个简单的实现基本上就是一个switch... case...即可。因此简单工厂模式不是我们关心的重点。
工厂方法模式:定义了一个创建对象的接口,但由于子类决定要实例化的类是哪一个。工厂方法让类把实例化推迟到子类。
抽象工厂模式:提供一个接口,用于创建相关或依赖对象的家族,而不需要明确指定具体类。
从定义中基本上已经看出工厂方法模式和抽象工厂模式的区别。抽象工厂方法,用于创建一组相关的对象,而工厂方法模式用于创建一个对象。一般情况下,抽象工厂方法中创建一组对象中的一个对象时,都会利用工厂模式来创建该对象。那么,工厂方法是使用继承,把对象的创建委托给子类,子类实现工厂方法来创建对象。抽象工厂使用对象组合,每个对象由工厂方法实现。
从上面的定义上看,抽象工厂模式依赖于工厂方法模式,工厂方法模式依赖于继承。那么作为面向过程的C语言,如何实现工厂方法模式呢?
在Linux的内核代码中,尽管使用的是C语言实现,但是却有不少object的概念。下面以socket为例,来体现工厂模式的应用。
创建socket的系统调用原型:
- int socket(int domain, int type, int protocol);
通过传入不同的domain,type,和protocol来创建不同的socket。
创建socket的函数为__sock_create,位于kernel的源码文件net/socket.c。
- static int __sock_create(struct net *net, int family, int type, int protocol,
-
struct socket **res, int kern)
-
{
-
int err;
-
struct socket *sock;
-
const struct net_proto_family *pf;
-
-
/*
-
* Check protocol is in range
-
*/
-
if (family < 0 || family >= NPROTO)
-
return -EAFNOSUPPORT;
-
if (type < 0 || type >= SOCK_MAX)
-
return -EINVAL;
-
-
/* Compatibility.
-
-
This uglymoron is moved from INET layer to here to avoid
-
deadlock in module load.
-
*/
-
if (family == PF_INET && type == SOCK_PACKET) {
-
static int warned;
-
if (!warned) {
-
warned = 1;
-
printk(KERN_INFO "%s uses obsolete (PF_INET,SOCK_PACKET)\n",
-
current->comm);
-
}
-
family = PF_PACKET;
-
}
-
-
err = security_socket_create(family, type, protocol, kern);
-
if (err)
-
return err;
-
-
/*
-
* Allocate the socket and allow the family to set things up. if
-
* the protocol is 0, the family is instructed to select an appropriate
-
* default.
-
*/
-
sock = sock_alloc();
-
if (!sock) {
-
if (net_ratelimit())
-
printk(KERN_WARNING "socket: no more sockets\n");
-
return -ENFILE; /* Not exactly a match, but its the
-
closest posix thing */
-
}
-
-
sock->type = type;
-
-
#ifdef CONFIG_MODULES
-
/* Attempt to load a protocol module if the find failed.
-
*
-
* 12/09/1996 Marcin: this makes REALLY only sense, if the user
-
* requested real, full-featured networking support upon configuration.
-
* Otherwise module support will
-
*/
-
if (net_families[family] == NULL)
-
request_module("net-pf-%d", family);
-
#endif
-
-
rcu_read_lock();
-
pf = rcu_dereference(net_families[family]);
-
err = -EAFNOSUPPORT;
-
if (!pf)
-
goto out_release;
-
-
/*
-
* We will call the ->create function, that possibly is in a loadable
-
* module, so we have to bump that loadable module refcnt first.
-
*/
-
if (!try_module_get(pf->owner))
-
goto out_release;
-
-
/* Now protected by module ref count */
-
rcu_read_unlock();
-
-
err = pf->create(net, sock, protocol, kern);
-
if (err < 0)
-
goto out_module_put;
-
-
/*
-
* Now to bump the refcnt of the [loadable] module that owns this
-
* socket at sock_release time we decrement its refcnt.
-
*/
-
if (!try_module_get(sock->ops->owner))
-
goto out_module_busy;
-
-
/*
-
* Now that we're done with the ->create function, the [loadable]
-
* module can have its refcnt decremented
-
*/
-
module_put(pf->owner);
-
err = security_socket_post_create(sock, family, type, protocol, kern);
-
if (err)
-
goto out_sock_release;
-
*res = sock;
-
-
return 0;
-
-
out_module_busy:
-
err = -EAFNOSUPPORT;
-
out_module_put:
-
sock->ops = NULL;
-
module_put(pf->owner);
-
out_sock_release:
-
sock_release(sock);
-
return err;
-
-
out_release:
-
rcu_read_unlock();
-
goto out_sock_release;
-
}
关于这段代码的分析,可以参加我之前的拙作。这里就不分析代码了。
在这段代码中,如果是针对不同的参数,而创建不同的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 设计模式》
阅读(9364) | 评论(1) | 转发(6) |