C语言和设计模式-工厂方法

C语言和设计模式-工厂方法

目录

C语言和设计模式-工厂方法

1.女娲造人的故事     

2.工厂方法模式的定义

3.工厂方法模式的应用

4 工厂方法模式的扩展

5 最佳实践


1.女娲造人的故事     

      东汉《风俗通》记录了一则神话故事:“开天辟辟,未有人民,女娲搏,黄土作人……”,讲述的内容就是大家非常熟悉的女娲造人的故事。开天辟地之初,大地上并没有生物,只有苍茫大地,纯粹而洁净的自然环境,寂静而又寂寞,于是女娲决定创造一个新物种(即人类)来增加世界的繁荣,怎么制造呢?

      别忘了女娲是神仙,没有办不到的事情,造人的过程是这样的:首先,女娲采集黄土捏成人的形状,然后放到八卦炉中烧制,最后放置到大地上生长,工艺过程是没有错的,但是意外随时都会发生:

      第一次烤泥人,兹兹兹兹,感觉应该熟了,往大地上一放,哇,没烤熟!于是一个白人诞生了!(这也是缺乏经验的最好证明)

      第二次烤泥人,兹兹兹兹兹兹兹兹,上一次没烤熟,这次多烤一会儿,放到世间一看,嘿,熟过头了,于是黑人诞生了!

      第三次烤泥人,兹~~兹~~兹~~,一边烧制一边察看,直到表皮微黄,嘿,真正好,于是黄色人种出现了!

      这个造人过程是比较有意思的,是不是可以通过软件开发来实现这个过程呢?古人云“三人行,必有我师焉”,在面向对象的思维中,万物皆对象,是对象我们就可以通过软件设计来实现。首先对造人过程进行分析,该过程涉及三个对象:女娲、八卦炉、三种不同肤色的人,女娲可以使用场景类Client来表示,八卦炉类似于一个工厂,负责制造生产产品(即人类):三种不同肤色的人,他们都是同一个接口下的不同实现类,都是人嘛,只是肤色、语言不同,对于八卦炉来说都是它生产出的产品。分析完毕,我们就可以画出类图。

  

 

      类图比较简单,AbstractHumanFactory是一个抽象类,定义了一个八卦炉都具有的整体功能,HumanFactory为实现类,完成具体的任务:创建人类;Human接口是人类的总称,其三个实现类分别为三类人种;NvWa类是一个场景类,负责模拟这个场景,执行相关的任务。

      我们定义的每个人种都有两个方法:getColor(获得人的皮肤颜色)和talk(交谈),其源代码下所示。

//抽象的人类角色
typedef struct _Human
{
	//每个人种的皮肤都有相应的颜色
	void (*getColor)();
 
	//人类会说话
	void (*talk)();
}Human;

 

      接口Human是对人类的总称,每个人种都至少具有两个方法,黑色人种、黄色人种、白色人种的代码分别如下所示。

//具体黑色人种
struct _Human BlackHuman  = 
{
	.getColor = getColor1,
	.talk = talk1,
};

void getColor1()
{
	printf("黑色人种的皮肤颜色是黑色的!");
}

void talk1()
{
	printf("黑人会说话,一般人听不懂。");
}

黄色人种:

//具体黄色人种
struct _Human YellowHuman   = 
{
	.getColor = getColor2,
	.talk = talk2,
};

void getColor2()
{
	printf("黄色人种的皮肤颜色是黄色的!");
}

void talk2()
{
	printf("黄色人种会说话,一般说的都是双字节。");
}

白色人种:

      

//具体白色人种
struct _Human WhiteHuman  = 
{
	.getColor = getColor3,
	.talk = talk3,
};

void getColor3()
{
	printf("白色人种的皮肤颜色是白色的!");
}

void talk3()
{
	printf("白色人种会说话,一般都是但是单字节。");
}

      所有的人种定义完毕,下一步就是定义一个八卦炉,然后烧制人类。我们想象一下,女娲最可能给八卦炉下达什么样的生产命令呢?应该是“给我生产出一个黄色人种(YellowHuman类)”,而不会是“给我生产一个会走、会跑、会说话、皮肤是黄色的人种”,因为这样的命令增加了交流的成本,作为一个生产的管理者,只要知道生产什么就可以了,而不需要事物的具体信息。

抽象人类创建工厂

//抽象人类创建工厂
typedef struct _AbstractHumanFactory 
{
  Human* (*createHuman)(Human* pHuman);

}AbstractHumanFactory ;

     

        目前女娲只有一个八卦炉,其实现了生产人类的方法,如下代码所示。

人类创建工厂:

//具体人类创建工厂
struct _AbstractHumanFactory HumanFactory = 
{
	.createHuman = createHuman,
};

Human* createHuman(Human* pHuman)
{
	if(NULL == pHuman)
	{
		return NULL;
	}
		
	//定义一个生产的人种 
	Human *human=(Human*)malloc(sizeof(Human));
	if(NULL == human)
	{
		return NULL;
	}
	 
	//产生一个人种
	memset(human,0,sizeof(human));

	human.getColor = pHuman.getColor;
	human.talk = pHuman.talk;
	

	return human;
}

      人种有了,八卦炉也有了,剩下的工作就是女娲采集黄土,然后命令八卦炉开始生产。

场景女娲

void NvWa
{
	//创建阴阳八卦炉
	AbstractHumanFactory *YinYangLu = &HumanFactory;
	
	//女娲第一次造人,火候不足,创造出白色人种
	Human *whiteHuman = YinYangLu->createHuman(&WhiteHuman);
	if(NULL != whiteHuman)
	{
		whiteHuman->getColor();
		whiteHuman->talk();
		free(whiteHuman);
	}
	
	//女娲第二次造人,火候过足,创造出黑色人种
	Human *blackHuman  = YinYangLu->createHuman(&BlackHuman);
	if(NULL != blackHuman)
	{
		blackHuman->getColor();
		blackHuman->talk();
		free(blackHuman);
	}	
	
	//女娲第三次造人,火候刚刚好!黄色人种
	Human *yellowHuman   = YinYangLu->createHuman(&YellowHuman);
	if(NULL != yellowHuman)
	{
		yellowHuman->getColor();
		yellowHuman->talk();
		free(yellowHuman);
	}	
	
}

      哇,人类的生产过程就展现出来了!这个世界就热闹起来了,黑人、白人、黄人都开始活动了,这也正是我们现在的真实世界。以上就是工厂方法模式(没错,对该部分有疑问,请继续阅读下去)。

2.工厂方法模式的定义

工厂方法模式使用的频率非常高,在我们日常的开发中总能见到它的身影。其定义为:

       Define an interface for creating an object, but let subclasses decide which class to instantiate. Factory Method lets a class defer instantiation to subclasses。(定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。)

2.1工厂方法模式的通用类图:

  

      在工厂方法模式中,抽象产品类Product负责定义产品的共性,实现对事物最抽象定义;Creator为抽象创建类,也就是抽象工厂,具体如何创建产品类是由具体的实现工厂ConcreteCreator完成的。工厂方法模式的变种较多,我们来看一个比较实用的通用源码。

2.2抽象产品类代码如下:

//抽象产品类
typedef struct _Product
{
	//抽象方法
	void (*method)();
 
}Product;

      具体的产品类可以有多个,都继承于抽象产品类。

2.3具体产品类:

//具体产品1
struct _Product ConcreteProduct1  = 
{
	.method = method1,
};

void method1()
{
	printf("产品1");
}

//具体产品2
struct _Product ConcreteProduct2  = 
{
	.method = method2,
};

void method2()
{
	printf("产品2");
}

2.4抽象工厂类负责定义产品对象的产生。

抽象工厂类:

//抽象工厂
typedef struct _Creator 
{
	Product* (*createProduct)(Product* pProduct);
	
}Creator;

  2.5具体如何产生一个产品的对象,是由具体的工厂类实现的。

//具体工厂
struct _Creator ConcreteCreator = 
{
	.createProduct = createProduct,
};

Product* createProduct(Product* pProduct)
{	
	if(NULL == pProduct)
	{
		return NULL;
	}
	
	//定义一个生产的产品 
	Product *productNew=(Product*)malloc(sizeof(Product));
	if(NULL == productNew)
	{
		return NULL;
	}
	 
	//生产一个产品
	memset(productNew,0,sizeof(productNew));
	productNew->method = pProduct->method;


	return productNew;
}

  2.6场景类的调用方法如下所示:

void client
{
	//创建工厂
	Creator *pcreator = &ConcreteCreator;
	Product *product = NULL;
	//生产产品1
	product = pcreator->createProduct(&ConcreteProduct1);
	if(NULL != product)
	{
		product->method();
		free(product);
	}
	
	//生产产品2
	product = pcreator->createProduct(&ConcreteProduct2);
	if(NULL != product)
	{
		product->method();
		free(product);
	}	
	return ;
}

      该通用代码是一个比较实用、易扩展的框架,读者可以根据实际项目需要进行扩展。

3.工厂方法模式的应用

3.1 工厂方法模式的优点

      首先,良好的封装性,代码结构清晰。一个对象创建是有条件约束的,如一个调用者需要一个具体的产品对象,只要知道这个产品的类名(或约束字符串)就可以了,不用知道创建对象的艰辛过程,减少模块间的耦合。

      其次,工厂方法模式的扩展性非常优秀。在增加产品类的情况下,只要适当地修改具体的工厂类或扩展一个工厂类,就可以完成“拥抱变化”。例如在我们的例子中,需要增加一个棕色人种,则只需要增加一个BrownHuman类,工厂类不用任何修改就可完成系统扩展。

      再次,屏蔽产品类。这一特点非常重要,产品类的实现如何变化,调用者都不需要关心,它只需要关心产品的接口,只要接口保持不表,系统中的上层模块就不要发生变化,因为产品类的实例化工作是由工厂类负责,一个产品对象具体由哪一个产品生成是由工厂类决定的。在数据库开发中,大家应该能够深刻体会到工厂方法模式的好处:如果使用JDBC连接数据库,数据库从MySql切换到Oracle,需要改动地方就是切换一下驱动名称(前提条件是SQL语句是标准语句),其他的都不需要修改,这是工厂方法模式灵活性的一个直接案例。

      最后,工厂方法模式是典型的解耦框架。高层模块值需要知道产品的抽象类,其他的实现类都不用关心,符合迪米特原则,我不需要的就不要去交流;也符合依赖倒转原则,只依赖产品类的抽象;当然也符合里氏替换原则,使用产品子类替换产品父类,没问题!

3.2 工厂方法模式的使用场景

      首先,工厂方法模式是new一个对象的替代品,所以在所有需要生成对象的地方都可以使用,但是需要慎重地考虑是否要增加一个工厂类进行管理,增加代码的复杂度。

      其次,需要灵活的、可扩展的框架时,可以考虑采用工厂方法模式。万物皆对象,那万物也就皆产品类,例如需要设计一个连接邮件服务器的框架,有三种网络协议可供选择:POP3、IMAP、HTTP,我们就可以把这三种连接方法作为产品类,定义一个接口如IConnectMail,然后定义对邮件的操作方法,三个具体的产品类(也就是连接方式)进行不同的实现,再定义一个工厂方法,按照不同的传入条件,选择不同的连接方式。如此设计,可以做到完美的扩展,如某些邮件服务器提供了WebService接口,很好,我们只要增加一个产品类就可以了。

      再次,工厂方法模式可以用在异构项目中,例如通过WebService与一个非Java的项目交互,虽然WebService号称是可以做到异构系统的同构化,但是在实际的开发中,还是会碰到很多问题,如类型问题、WSDL文件的支持问题,等等,从WSDL中产生的对象都认为是一个产品,然后由一个具体的工厂类进行管理,减少与外围系统的耦合。

      最后,可以使用在测试驱动开发的框架下,例如,测试一个类A,就需要把与类A有关联关系的类B也同时产生出来,我们可以使用工厂方法模式把类B虚拟出来,避免类A与类B的耦合。目前由于JMock和EasyMock的诞生,该使用场景已经弱化了,读者可以在遇到此种情况时直接考虑使用JMock或EasyMock。

4 工厂方法模式的扩展

      工厂方法模式有很多扩展,而且与其他模式结合使用威力更大,下面将介绍四种扩展。

4.1缩小为简单工厂模式

      我们这样考虑一个问题:一个模块仅需要一个工厂类,没有必要把它产生出来,使用静态的方法就可以了,根据这一要求,我们把上例中的AbstarctHumanFactory修改一下,类图如下图所示。

  

       我们在类图中去掉了AbstractHumanFactory抽象类,同时把createHuman方法设置为静态类型,简化了类的创建过程,变更的源码仅仅是HumanFactory和NvWa类,HumanFactory代码如下所示。

简单工厂模式中的工厂类,在C语言中可以去除HumanFactory实例类,只用static createHuman更容易理解(注意:跟面向对象编程有点差别,我们灵活运用):

static Human* createHuman(Human* pHuman)
{
	if(NULL == pHuman)
	{
		return NULL;
	}
		
	//定义一个生产的人种 
	Human *human=(Human*)malloc(sizeof(Human));
	if(NULL == human)
	{
		return NULL;
	}
	 
	//产生一个人种
	memset(human,0,sizeof(human));

	human.getColor = pHuman.getColor;
	human.talk = pHuman.talk;
	

	return human;
}

      HumanFactory类仅有两个地方变化:去掉继承抽象类,并在createHuman前增加static关键字。工厂类发生变化,也同时引起了调用者NvWa的变化,如下面代码所示。

简单工厂模式中的场景类:

      

void NvWa
{

	//女娲第一次造人,火候不足,创造出白色人种
	Human *whiteHuman = createHuman(&WhiteHuman);
	if(NULL != whiteHuman)
	{
		whiteHuman->getColor();
		whiteHuman->talk();
		free(whiteHuman);
	}
	
	//女娲第二次造人,火候过足,创造出黑色人种
	Human *blackHuman  = createHuman(&BlackHuman);
	if(NULL != blackHuman)
	{
		blackHuman->getColor();
		blackHuman->talk();
		free(blackHuman);
	}	
	
	//女娲第三次造人,火候刚刚好!黄色人种
	Human *yellowHuman   = createHuman(&YellowHuman);
	if(NULL != yellowHuman)
	{
		yellowHuman->getColor();
		yellowHuman->talk();
		free(yellowHuman);
	}	
	
}

运行结果没有发生变化,但是我们的类图变简单了,而且调用者也比较简单,该模式是工厂方法模式的弱化,因为简单,所以被称为简单工厂模式(Simple Factory Pattern),也叫做静态工厂模式。在实际项目中,采用该方法的案例还是比较多的,其缺点是工厂类的扩展比较困难,不符合开闭原则,但它仍然是一个非常实用的设计模式。

4.2升级为多个工厂类

      当我们在做一个比较复杂的项目时,经常会遇到初始化一个对象很耗费精力的情况,所有的产品类都放到一个工厂方法中进行初始化会使代码结构不清晰。例如,一个产品类有5个具体实现,每个实现类的初始化(不仅仅是new,初始化包括new一个对象,并对对象设置一定的初始值)方法都不相同,如果写在一个工厂方法中,势必会导致该方法巨大无比,那怎么办?

      考虑到需要结构清晰,我们就为每个产品定义一个创造者,然后由调用者自己去选择与哪个工厂方法关联。我们还是以女娲造人为例,每个人种都有一个固定的八卦炉,分别造出黑色人种、白色人种、黄色人种,修改后的类图如下所示。

  

 

                                        多个工厂类的类图

      每个人种(具体的产品类)都对应了一个创建者,每个创建者都独立负责创建对应的产品对象,非常符合单一职责原则,按照这种模式我们来看代码变化。

多工厂模式的抽象工厂类:

//抽象人类创建工厂
typedef struct _AbstractHumanFactory 
{
  Human* (*createHuman)();

}AbstractHumanFactory ;

      注意 抽象方法中已经不再需要传递相关参数了,因为每一个具体的工厂都已经非常明确自己的职责:创建自己负责的产品类对象。

黑色人种的创建工厂:

//具体黑色人种工厂
struct _AbstractHumanFactory BlackHumanFactory = 
{
	.createHuman = createBlackHuman,
};

Human* createBlackHuman()
{
	//定义一个生产的人种 
	Human *human=(Human*)malloc(sizeof(Human));
	if(NULL == human)
	{
		return NULL;
	}
	 
	//产生一个人种
	memset(human,0,sizeof(human));

	human.getColor = BlackHuman.getColor;
	human.talk = BlackHuman.talk;

	return human;
}

  黄色人种的创建工厂:

/具体黄色人种工厂
struct _AbstractHumanFactory YellowHumanFactory = 
{
	.createHuman = createYellowHuman,
};

Human* createYellowHuman()
{
	//定义一个生产的人种 
	Human *human=(Human*)malloc(sizeof(Human));
	if(NULL == human)
	{
		return NULL;
	}
	 
	//产生一个人种
	memset(human,0,sizeof(human));

	human.getColor = YellowHuman.getColor;
	human.talk = YellowHuman.talk;

	return human;
}

  白色人种的创建工厂:

  

//具体白色人种工厂
struct _AbstractHumanFactory WhiteHumanFactory = 
{
	.createHuman = createWhiteHuman,
};

Human* createWhiteHuman()
{
	//定义一个生产的人种 
	Human *human=(Human*)malloc(sizeof(Human));
	if(NULL == human)
	{
		return NULL;
	}
	 
	//产生一个人种
	memset(human,0,sizeof(human));

	human.getColor = WhiteHuman.getColor;
	human.talk = WhiteHuman.talk;

	return human;
}

      三个具体的创建工厂都非常简单,但是,如果一个系统比较复杂时工厂类也会相应变复杂。

场景类NvWa:

void NvWa
{
	//女娲第一次造人,火候不足,创造出白色人种
	Human *whiteHuman = WhiteHumanFactory.createHuman();
	if(NULL != whiteHuman)
	{
		whiteHuman->getColor();
		whiteHuman->talk();
		free(whiteHuman);
	}
	
	//女娲第二次造人,火候过足,创造出黑色人种
	Human *blackHuman = BlackHumanFactory.createHuman();
	if(NULL != blackHuman)
	{
		blackHuman->getColor();
		blackHuman->talk();
		free(blackHuman);
	}	
	
	//女娲第三次造人,火候刚刚好!黄色人种
	Human *yellowHuman = YellowHumanFactory.createHuman();
	if(NULL != yellowHuman)
	{
		yellowHuman->getColor();
		yellowHuman->talk();
		free(yellowHuman);
	}	
	
}

      运行结果还是相同。我们回顾一下,每一个产品类都对应了一个创建类,好处就是创建类的职责清晰,而且结构简单,但是给扩展性和维护性带来了一定的影响。为什么这么说呢?如果要扩展一个产品类,就需要建立一个相应的工厂类,这样就增加了扩展的难度。因为工厂类和产品类的数量相同,维护时需要考虑两个对象之间的关系。

      当然,在复杂的应用中一般采用多工厂的方法,然后再增加一个协调类,避免调用者与各个子工厂交流,协调类的作用是封装子工厂类,对高层模块提供统一的访问接口。

4.3替代单例模式

之前讲述了单例模式以及扩展出的多例模式,并且指出了单例和多例的一些缺点,我们是不是可以采用工厂方法模式实现单例模式的功能呢?单例模式的核心要求就是在内存中只有一个对象,通过工厂方法模式也可以只在内存中生产一个对象。类图如图所示。

注:此处工厂方法代替单利模式,使用了java的语言特性,C语言中不适应该编写方法,此处先不做c语言替换,后续再研究;

  

工厂方法模式替代单例模式类图

      非常简单的类图,Singleton定义了一个private的无参构造函数,目的是不允许通过new的方式创建一个对象,。

单例类

public class Singleton {

    //不允许通过new产生一个对象
    private Singleton(){

    }

    public void doSomething(){

    //业务处理
    
    }

}

      Singleton保证不能通过正常的渠道建立一个对象,那SingletonFactory如何建立一个单例对象呢?答案是通过反射方式创建。

负责生成单例的工厂类:

public class SingletonFactory {
 
private static Singleton singleton;
 
static{ 
 
try {
 
Class cl= Class.forName(Singleton.class.getName());
 
//获得无参构造
 
Constructor constructor=cl.getDeclaredConstructor();
 
//设置无参构造是可访问的
 
constructor.setAccessible(true);
 
//产生一个实例对象
 
singleton = (Singleton)constructor.newInstance();
 
} catch (Exception e) {
 
//异常处理
 
}
 
}
 
public static Singleton getSingleton(){
 
return singleton;
 
}
 
}

      通过获得类构造器,然后设置访问权限,生成一个对象,然后提供外部访问,保证内存中的对象唯一。当然,其他类也可以通过反射的方式建立一个单例对象,确实如此,但是一个项目或团队是有章程和规范的,何况已经提供了一个获得单例对象的方法,为什么还要重新创建一个新对象呢?除非是有人作恶。

      以上通过工厂方法模式创建了一个单例对象,该框架可以继续扩展,在一个项目中可以产生一个单例构造器,所有需要产生单例的类都遵循一定的规则(构造方法是private),然后通过扩展该框架,只要输入一个类型就可以获得唯一的一个实例。

4.3延迟初始化

      何为延迟初始化(Lazy initialization)?一个对象被消费完毕后,并不立刻释放,工厂类保持其初始状态,等待再次被使用。延迟初始化是工厂方法模式的一个扩展应用,其通用类图如图所示。

  

 

                                                延迟初始化通用类图

      ProductFactory负责产品类对象的创建工作,并且通过prMap变量(C伪代码中实现使用了链表代替,原理一样的)产生一个缓存,对需要再次被重用的对象保留,Product和ConcreteProduct是一个示例代码, ProductFactory伪代码如下:

延迟加载的工厂类

//伪代码
static list ProductList;

static Product* createProduct(int type)
{	
	Product *product = NULL;
	
	//如果原链表中已经存在,则直接返回该对象;
	product = list_search(ProductList,type);
	if(NULL != product)
	{
		return product;
	}
	
	//如果原链表中不存在,则将对象加入到链表,并返回该地址
	if(1 == type)
	{
		product = list_add(ConcreteProduct1,type);
	}
	else if(2 == type)
	{
		product = list_add(ConcreteProduct2,type);
	}

	return product;
}

 

      代码还比较简单,通过定义一个list容器,容纳所有产生的对象,如果在list容器中已经有的对象,则直接取出返回,如果没有,则根据需要的类型产生一个对象并放入到list容器中,以方便下次调用。

      延迟加载框架是可以扩展的,例如限制某一个产品类的最大实例化数量,可以通过判断list中已有的对象数量来实现,这样的处理是非常有意义的,例如JDBC连接数据库,都会要求设置一个MaxConnections最大连接数量,该数量就是内存中最大实例化的数量。

      延迟加载还可以使用在一个对象初始化比较复杂的情况,例如硬件访问,涉及多方面的交互,则可以通过延迟加载降低对象的产生和销毁带来的复杂性。

5 最佳实践

      工厂方法模式在项目中使用得非常频繁,以至于很多代码中都包含工厂方法模式。该模式几乎尽人皆知,但不是每个人都能用得好。熟能生巧,熟练掌握该模式,多思考工厂方法如何应用,而且工厂方法模式还可以与其他模式混合使用(例如模版方法模式、单例模式、原型模式等),变化出无穷的优秀设计,这也正是软件设计和开发的乐趣所在。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值