[把你的理性思维慢慢变成条件反射]
本文,我们讲介绍抽象工厂模式,文章主题结构与上文一致。惯例,先来看看我们示例工程的环境:
操作系统:win7 x64
其他软件:eclipse mars,jdk7
-------------------------------------------------------------------------------------------------------------------------------------
经典问题:
在产品簇的范围内选择合适对象的问题,如数据库驱动。(产品簇:相同功能的不同实例)
思路分析:
要点一:所有产品具有相似的功能或属性。
要点二:所有产品在实现上存在差异。
示例工程:
错误写法:
创建SqlDB.java文件,具体内容如下:(在此为简化实现过程,实际代码仅作为示例,简单的SQL连接代码,见注释部分)
package com.csdn.ingo.gof_AbstractFactory;
public class SqlDB {
public void insert(User user){
System.out.println("insert:"+user.getName());
}
public User select(String string){
System.out.println("select:"+string);
return null;
}
}
//public class DBConnection {
// public static final String url = "jdbc:mysql://127.0.0.1/student";
// public static final String name = "com.mysql.jdbc.Driver";
// public static final String user = "root";
// public static final String password = "root";
//
// public Connection conn = null;
// public PreparedStatement pst = null;
//
// public DBConnection(String sql) {
// try {
// Class.forName(name);//指定连接类型
// conn = DriverManager.getConnection(url, user, password);//获取连接
// pst = conn.prepareStatement(sql);//准备执行语句
// } catch (Exception e) {
// e.printStackTrace();
// }
// }
//
// public void close() {
// try {
// this.conn.close();
// this.pst.close();
// } catch (SQLException e) {
// e.printStackTrace();
// }
// }
//}
创建User.java文件,具体内容如下:
package com.csdn.ingo.gof_AbstractFactory;
public class User {
private String id;
private String name;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
创建Window.java文件,具体内容如下:
package com.csdn.ingo.gof_AbstractFactory;
public class Window {
public static void main(String[] args) {
User u = new User();
SqlDB s = new SqlDB();
u.setId("aaaa");
u.setName("bbbb");
s.insert(u);
s.select("aaaa");
}
}
错误原因:
数据库的选择直接写入代码,并且与Sql语句的耦合度非常高。一旦后期进行适配别的数据库,就会违反“开闭原则”,从而需要大量的工作才能修改到对应的数据库,并且这种修改的影响范围的深度与广度尚未可知。因此,这种代码书写方式是一种非常不好的书写方式。工厂方法模式的实现:
在前文中,我们介绍了工厂方法模式,在此,我们先来看看使用工厂方法模式的实现过程。
创建IFactory.java文件,具体内容如下:
package com.csdn.ingo.gof_AbstractFactory.one;
public interface IFactory {
public IUser createUser();
}
创建AccessFactory.java文件,具体内容如下:
package com.csdn.ingo.gof_AbstractFactory.one;
public class AccessFactory implements IFactory {
public IUser createUser() {
return new AccessUser();
}
}
创建SqlserverFactory.java文件,具体内容如下:
package com.csdn.ingo.gof_AbstractFactory.one;
public class SqlserverFactory implements IFactory{
public IUser createUser() {
return new SqlserverUser();
}
}
创建IUser.java文件,具体内容如下:
package com.csdn.ingo.gof_AbstractFactory.one;
public interface IUser {
void insert(User u);
User select(String id);
}
创建AccessUser.java,SqlserverUser.java文件,具体内容如下:
package com.csdn.ingo.gof_AbstractFactory.one;
public class AccessUser implements IUser{
public void insert(User user){
System.out.println("insert:"+user.getName());
}
public User select(String string){
System.out.println("select:"+string);
return null;
}
}
创建User.java文件,具体内容如下:
package com.csdn.ingo.gof_AbstractFactory.one;
public class User {
private String id;
private String name;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
创建Window.java文件,具体内容如下:
package com.csdn.ingo.gof_AbstractFactory.one;
public class Window {
public static void main(String[] args) {
User u = new User();
u.setName("aaaa");
IFactory f = new SqlserverFactory();
IUser iu = f.createUser();
iu.insert(u);
iu.select("aaa");
}
}
工厂方法实现的优点与不足:
优点:切换数据时,仅需修改IFactory f = new SqlserverFactory();及对应的SQL语句。
缺点:任何新增和修改,都可能会导致需要针对不同的数据库进行对应的实现。
推荐写法:(抽象工厂模式)
详细代码与上文类似,工程结构图见下文模式总结部分。
在抽象工厂实现的基础上,结合简单工厂模式在进行略微修改:
创建DateAccess.java文件,具体内容如下:
package com.csdn.ingo.gof_AbstractFactory.three;
public class DataAccess {
// private static final String db = "Sqlserver";//可由配置文件实现
private static final String db = "Access";
public static IUser createUser() {
IUser re = null;
switch (db) {
case "Sqlserver":
re = new SqlserverUser();
break;
case "Access":
re = new AccessUser();
break;
default:
break;
}
return re;
}
public static IDepartment createDepartment() {
IDepartment re = null;
switch (db) {
case "Sqlserver":
re = new SqlserverDepartment();
break;
case "Access":
re = new AccessDepartment();
break;
default:
break;
}
return re;
}
}
创建IUser.java文件,具体内容如下:
package com.csdn.ingo.gof_AbstractFactory.three;
public interface IUser {
void insert(User u);
User select(String id);
}
创建IDepartment.java文件,具体内容如下;
package com.csdn.ingo.gof_AbstractFactory.three;
public interface IDepartment {
void insert(Department d);
Department select(String id);
}
创建SqlserverUser.java,AccessUser.java,SqlserverDepartment.java,AccessDepartment.java文件,具体内容如下:
package com.csdn.ingo.gof_AbstractFactory.three;
public class SqlserverUser implements IUser {
public void insert(User user){
System.out.println("SqlserverUser insert:"+user.getName());
}
public User select(String string){
System.out.println("SqlserverUser select:"+string);
return null;
}
}
创建User.java,Department.java文件,见上文。
创建Window.java文件,具体内容如下:
package com.csdn.ingo.gof_AbstractFactory.three;
import org.omg.PortableServer.IdAssignmentPolicy;
public class Window {
public static void main(String[] args) {
User u = new User();
u.setName("aaaa");
IUser iu = DataAccess.createUser();
iu.insert(u);
iu.select("aaa");
Department de = new Department();
de.setName("asdf");
IDepartment d = DataAccess.createDepartment();
d.insert(de);
d.select("asdf");
}
}
推荐原因:
具体的Factory实现类,直到运行时才确定,并且,这个具体的Factory负责创建具体的对象。客户端如需要不同的对象,就需要调用不同的具体工厂。由此,在切换时,可以跟方便的切换调用方式即可。因为,客户端是面向抽象的,具体的实现已经分离到具体的子类当中。中间通过抽象接口作为中介,对于客户端完全屏蔽。非常符合“开闭原则”“迪米特法则”等。最后,上面的功能其实已经足够说明抽象工厂模式,简单工厂模式,工厂方法模式的区别了。但在此,为了完整的展示三种工厂模式的强大功能,我们再将配置文件与反射的结合的代码给出,供各位看官学习。
创建Init.java文件,具体内容如下:
package com.csdn.ingo.gof_AbstractFactory.four;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
public class Init {
public static Properties getPro() throws FileNotFoundException, IOException {
InputStream inputStream =Object.class.getResourceAsStream("/classpro.properties");
Properties pro = new Properties();
pro.load(inputStream);
return pro;
}
}
在resources文件夹下,创建classpro.properties文件,具体内容如下:
User=com.csdn.ingo.gof_AbstractFactory.four.AccessUser
Department=com.csdn.ingo.gof_AbstractFactory.four.SqlserverDepartment
创建UserFactory.java,DepartmentFactory.java文件,具体内容如下:
package com.csdn.ingo.gof_AbstractFactory.four;
public class UserFactory {
public static IUser getInstance(String className) {
IUser u = null;
try {
u = (IUser) Class.forName(className).newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return u;
}
}
创建Window.java文件,具体内容如下:
package com.csdn.ingo.gof_AbstractFactory.four;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Properties;
import org.omg.PortableServer.IdAssignmentPolicy;
public class Window {
public static void main(String[] args) throws FileNotFoundException, IOException {
Properties pro = Init.getPro();
User u = new User();
u.setName("aaaa");
IUser iu = UserFactory.getInstance(pro.getProperty("User"));
iu.insert(u);
iu.select("aaa");
Department de = new Department();
de.setName("asdf");
IDepartment d = DepartmentFactory.getInstance(pro.getProperty("Department"));
d.insert(de);
d.select("asdf");
}
}
其他文件请参考上文工程。现在,对于任意功能的数据访问层切换都可以完全脱离修改源代码。极大的方便了后期的维护与扩展。
模式总结:
本例UML结构图:标准抽象工厂模式UML结构图:
概念总结:
抽象工厂模式:提供一个创建一系列相关或者相互依赖对象的接口,而无需指定它们具体的类。组成部分:抽象工厂类,具体工厂类,抽象产品类,具体产品类四部分组成。
反思:
首先,我们介绍下两个概念:产品簇:具体例子:MySQL,SQLserver,Oracle等都提供相似的功能,拥有相似的结构。产品等级:举个例子:各个数据库的版本。
由上面的示例可以总结出,在抽象工厂模式中,扩展一个产品簇是相对容易的。但是,如果要扩展一个产品等级,意味着需要对所有的具体类都进行相关功能的实现。
因此,在应用该模式时,需要实现设计好各种产品等级,随着时间推进,如果需要新增一个产品等级,工作量非常的巨大。希望各位看官牢记。
应用场景:
- 需要将对象的应用与创建及细节进行屏蔽时。
- 需要使用一个产品簇,但是每次只会使用一个具体的产品时。
- 在这个产品簇内,拥有相似的功能和特征。
- 产品簇结构稳定,不会轻易修改。
优点:
- 客户端不关心具体的产品的创建及对象细节,其面向接口编程。
- 保证产品簇中只能示例话出一个实例。
- 一定程度上符合“开闭原则”
缺点:
- 不适合产品等级随时变化的场景。
- 如果发生切换,不仅工厂需要修改,客户端也需要切换到对应的声明上。
-------------------------------------------------------------------------------------------------------------------------------------
至此,被说了很多遍的设计模式---抽象工厂模式 结束
参考资料:
图书:《大话设计模式》
其他博文:http://blog.csdn.NET/lovelion/article/details/7563445