第三篇、创建型设计模式——抽象工厂模式

前言:工作中,经常会有朋友抱怨说,最烦的就是翻来覆去的修改功能,尤其是客户天马行空的功能需求,用再多的设计模式也避免不了改改改。。。没错,功能的变更不可避免的会导致代码的修改,万能的模式是不存在的。但是,在同样的功能修改的前提下,我们能加以控制的,就是多改与少改。好的设计可以使我们在修改功能需求时变得更加高效。

——————————————————————————————————————————————————————————————————————

有这样的一个场景,相信许多的朋友也遇到过,就是我们的某些已经比较成熟的项目,突然要重新包装一下,换个名字,换个数据库,当成一个新的项目提供给客户。也相信许多朋友在这个“重新包装”的过程中,吃尽了苦头。有些朋友吃的还不只一次,这就是没能很好地运用设计模式,导致程序出现变化时,不能做到灵活的扩展,从而徒增了大量的工作量。下面我们就用这个场景进行举例,来说说我们今天要讲的一个设计模式。(哈哈,这个例子也是被大家举烂了的)


首先,描述一下场景。我们现在需要重新修改一个程序,使之变成一个新的产品提供给客户,这个程序涉及到两个数据库表User(用户)和Department(部门),我们成熟的项目用的是SQL Server,但是新的产品需要用到Access。对于这样的一个程序,我们应该如何设计。

一、对于经验比较少的人,或对面向对象编程与设计模式理解不是很深刻的人,他可能会这样编写代码:

1、首先创建User与Department的实体类(我们仅用id与name两个属性)

public class Department {
	public String id;
	public String dName;
}

public class Department {
	public String id;
	public String dName;
}
2、创建用于操作数据库增删改查功能的工具类:

/**
 * 操作User的工具类
 */
public class SqlServerUser {
	public void insert(User user) {
		// ......
		System.out.println("在SqlServer中插入User成功");
	}

	public User getUser(String id) {
		User u = null;
		// ......
		System.out.println("在SqlServer中查询到了User");
		return u;
	}
}

/**
 * 操作Department的工具类
 */
public class SqlServerDepartment {
	public void insert(Department department) {
		// ......
		System.out.println("在SqlServer中插入Department成功");
	}

	public Department getDepartment(String id) {
		Department d = null;
		// ......
		System.out.println("在SqlServer中查询到了Department");
		return d;
	}
}

3、之后,在客户端调用

SqlServerUser su = new SqlServerUser();
		su.insert(new User()); // 插入User
		User u = su.getUser("1"); // 查询User

		SqlServerDepartment sd = new SqlServerDepartment();
		sd.insert(new Department()); // 插入Department
		Department d = sd.getDepartment("1"); // 查询Department

好,从功能上看,这段代码是实现了操作数据库的目的。但是,这样写没有体现出面向对象语言的特性。SqlServerUser su = new SqlServerUser()与SqlServerDepartment du = new SqlServerDepartment()的写法完全的将其框死在了Sql Server上,这种写法专业点说,不是多态的,违反了里氏代换原则。其次就是,我们将实例化过程放到了客户端,使其必须考虑所用的具体数据库的种类,以及它的组成过程,这也违背了依赖倒转原则,不是面向接口编程的实现。当发生前面描述的变化时,直接导致的后果是,我们需要增加AccessUser和AccessDepartment,并且要修改多处使用了SqlServerUser su = new SqlServerUser()与SqlServerDepartment du = new SqlServerDepartment()的地方。如此看来,这种设计是不是很糟糕。


有些朋友会说,我哪知道以后会有哪些变化,毕竟功能需求是千变万化的。没错,但是我们也要尽可能全面的想到以后会有的变化,提前做好准备。或者在出现变化苗头的时候,及时重构我们的代码,应变将来发生的变化。


首先,我们程序开发的时候,我们应该知道,数据库操作,就是增删改查的操作,所以不管是什么表,用的是什么类型的数据库,都会有insert(插入)、get(查询获取)、updata(更新)和delete(删除)操作,唯一的区别就是我们操作的对象可能是User或者Deparment。按照依赖倒转原则,我们可以将这部分抽象成一个个的接口,然后针对接口进行程序的编写,这样就会提高我们程序的灵活性:

将对User对象的操作抽象成接口IUser

public abstract class IUser {
	public abstract void insert(User user);
	public abstract User getUser(String id);
}
将对Department对象的操作抽象成接口IDepartment

public abstract class IDepartment {
	public abstract void insert(Department deprecated);
	public abstract Department getDepartment(String id);
}

然后我们分别去实现这几个接口,因为我们自己用到的是SQL Server,我们就用它来实现,作为接口的子类

实现IUser

public class SqlServerUser extends IUser {
	
	@Override
	public void insert(User user) {
		// ......
		System.out.println("在SqlServer中插入User成功");
	}

	@Override
	public User getUser(String id) {
		User u = null;
		// ......
		System.out.println("在SqlServer中查询到了User");
		return u;
	}
}

实现IDepartment
public class SqlServerDepartment extends IDepartment {

	@Override
	public void insert(Department department) {
		// ......
		System.out.println("在SqlServer中插入Department成功");
	}

	@Override
	public Department getDepartment(String id) {
		Department d = null;
		// ......
		System.out.println("在SqlServer中查询到了Department");
		return d;
	}
}
接下来,我们要做的就是在客户端中调用他们,进行数据库的操作,这里我们要考虑的就是实例化SqlServerUser和SqlServerDepartment对象的过程了。有朋友会在客户端这样写:IUser u = new SqlServerUser(),还记得刚才我们分析的,让客户端自己考虑使用哪个数据库,考虑产品对象的组合过程,是不合理的。打个比方来说,我去快餐店吃薯条,我只需要知道,我要大份还是小份的就好,其他的我不关心。将数据库实例化放到客户端,就相当于,我去吃薯条,我要知道薯条怎么做成的,还要知道大份有多少,小份有多少,怎么包装的等等,这对我来说显然是不合理的。所以说,就我们程序而言,对SqlServerUser的实例化放到客户端也是不合理的。那我们应该怎么做呢?


我们一直在讲的就是创建型模式,所谓创建型模式就是将产品的创建过程抽象了出来,使客户不需要知道它是如何创建的,只知道我需要时,有途径可以创建它。我们前面已经学了两个创建型设计模式:简单工厂,工厂方法。这里要提的一句是:通常来说,设计应该从工厂方法开始,当设计者发现需要更大的灵活性时,设计便会向其他的创建型模式演变。

好,现在我们来设计接下来的代码,首先,抽象出创建所需对象的接口IFactory:

public abstract class IFactory {
	public abstract  IUser creatUser();
	public abstract IDepartment creatDepartment();
}
因为有两个表,他们各自操作的对象不同,因此,User与Department属于不同的等级结构,所以我们要分别对他们的对象进行创建

然后,实现他们的创建过程:

public class SqlServerFactory extends IFactory{

	@Override
	public IUser creatUser() {
		return new SqlServerUser();
	}

	@Override
	public IDepartment creatDepartment() {
		return new SqlServerDepartment();
	}

}

最后,在客户端进行调用操作:

	IFactory factory = new SqlServerFactory();
		IUser user = factory.creatUser();
		user.insert(new User()); // 插入User
		User u = user.getUser("1"); // 查询User

		IDepartment department = factory.creatDepartment();
		department.insert(new Department()); // 插入Department
		Department d = department.getDepartment("1"); // 查询Department

这样再看,是不是就比较舒服了。首先对于客户端,我无需知道数据库操作类的对象是如何创建的,我只通过create接口就获得了我所需要的对象,他的组成过程被封装到了SqlServerFactory中。当需要变更数据库为Access时,我只需要:

1、增加AccessUser和AccessDepartment操作类:

public class AccessUser extends IUser {

	@Override
	public void insert(User user) {
		// ......
		System.out.println("在Access中插入User成功");
	}

	@Override
	public User getUser(String id) {
		User u = null;
		// ......
		System.out.println("在Access中查询到了User");
		return u;
	}

}


public class AccessDepartment extends IDepartment {

	@Override
	public void insert(Department deprecated) {
		// ......
		System.out.println("在Access中插入Department成功");
	}

	@Override
	public Department getDepartment(String id) {
		Department d = null;
		// ......
		System.out.println("在Access中查询到了Department");
		return d;
	}

}

2、增加其实例化的工厂子类:

public class AccessFactory extends IFactory{

	@Override
	public IUser creatUser() {
		return new AccessUser();
	}

	@Override
	public IDepartment creatDepartment() {
		return new AccessDepartment();
	}

}

3、在客户端中,只需要将工厂替换为AccessFactory就可以完成数据库的替换:

 IFactory factory = new AccessFactory();
		 IUser user = factory.creatUser();
		 user.insert(new User()); // 插入User
		 User u = user.getUser("1"); // 查询User
		
		 IDepartment department = factory.creatDepartment();
		 department.insert(new Department()); // 插入Department
		 Department d = department.getDepartment("1"); // 查询Department

是不是很方便?有人又会说,你还是在客户端实例化了,只不过这次实例化的是工厂AccessFactory。我举个例子,还是你去吃薯条,你最起码也要知道你去吃肯德基,还是吃麦当劳吧,不可能你虎躯一震,大吼一声:“老子要吃薯条!”天上就会掉下薯条吧,总需要有人去制作它们。而肯德基和麦当劳就充当了制造薯条的工厂,而他们的薯条就是产品对象,只不过属于不同的牌子。就像SqlServerUser(肯德基牌薯条)和AccessUser(麦当劳牌薯条)都是IUser(薯条),只不过SqlServerUser(肯德基牌薯条)是SqlServerFactory(肯德基)生产的,AccessUser(麦当劳牌薯条)是AccessFactory(麦当劳)生产的。


说了这么多,有朋友会问,不是说要讲抽象工厂模式么,说了那么多,抽象工厂到底怎么写?哈哈,其实前面的这些就是抽象工厂模式了,怎么样,没想到吧。


抽象工厂就是提供一个创建一系列相关对象的接口,而无需指定它们具体的类。简单来说就是,抽象工厂是伴随着产品族的概念而生的,它提供了创建这些产品的接口,而不必指出具体怎么创建某个确定的产品。


这么说,估计有人还是不理解,我再举例子解释下。

1、不管是肯德基还是麦当劳,它们不仅仅只生产薯条一种产品,也会生产汉堡

2、它们生产的产品,都属于各自的品牌。如麦当劳薯条,肯德基汉堡

3、它们都属于快餐店,或者说快餐工厂,都提供了create薯条() 和 create汉堡() 的接口

4、它们都有自己的名字,创建出属于自己品牌的制作流水线

这种一个工厂提供创建多种产品,一个产品可以有多重品牌的模式,就是抽象工厂了。


在拿我们的程序说明:

1、首先IUser和IDepartment都是产品父类。如薯条与汉堡

2、SqlServerUser、AccessUser / SqlServerDepartment、AccessDepartment就是用来生产不同品牌产品的流水线对象。如生产肯德基薯条、麦当劳薯条/ 肯德基汉堡、麦当劳汉堡

3、IFactory就是工厂父类,定义了createUser()和CreateDepartment()的接口,以便统一规范生产流水线的过程。

4、SqlServerFactory和AccessFactory就是各自的工厂子类,负责实例化出生产各自产品的流水线对象。如肯德基快餐店,麦当劳快餐店,负责生产各自的产品


优点:

1、抽象工厂很好地应对了产品族的场景,它最大的好处就是便于在产品系列当中进行切换,由于具体工厂类,如IFactory factory = new AccessFactory(),只在一个程序初始化的时候出现一次即可,这就使得改变一个应用的具体工厂变得非常容易,只需改变这个工厂,就可以使用不同的产品配置

2、它让具体的创建实例过程与客户端分离,客户端是通过它们的抽象接口操作实例,产品的具体类名也被具体的工厂实现分离,不会出现在客户的代码中。

例如:又有新的快餐店——德克士,也要生产薯条,汉堡。只需实现薯条,汉堡接口,生产自己品牌的产品,然后实现工厂接口,生成德克士快餐店


缺点:

如果我的更改不是来自于品牌(流水线)的增加,而是来自于产品的研发。例如,我要增加Project表,我不仅要增加IProject和SqlServerProject、AccessProjet,还要修改已有的IFactory、SqlServerFactory和AccessFactory,添加createProject新接口与实现。


好了,抽象工厂就先说到这里,在下一篇中,我们再说如何在抽象工厂的基础上进行优化,以便更好地提升程序的扩展与维护性。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值