设计模式笔记2——工厂模式及其应用
1,简单工厂模式
简单工厂模式:由一个类封装实例化对象的行为
解决问题:一旦要根据类别进行创建对象,需要在调用的地方修改逻辑,违反ocp原则,且修改的地方会很多。
简单工厂模式又叫静态工厂模式。
简单工厂模式类图:
实现起来大概是这样:
public class EasyFactory {
// 简单工厂,根据字符串创建相应的对象
public static Operation createOperation(String name) {
Operation operationObj = null;
switch (name) {
case "+":
operationObj = new Add();
break;
case "-":
operationObj = new Sub();
break;
case "*":
operationObj = new Mul();
break;
case "/":
operationObj = new Div();
break;
}
return operationObj;
}
}
调用则这样调用:
Operation add = EasyFactory.createOperation("+");
这样做的优点:我们可以对创建的对象进行一些 “加工” ,而且客户端并不知道,因为工厂隐藏了这些细节。如果,没有工厂的话,那我们是不是就得自己在客户端上写这些代码,这就好比本来可以在工厂里生产的东西,拿来自己手工制作,不仅麻烦以后还不好维护。
但是缺点也很明显:如果需要在方法里写很多与对象创建有关的业务代码,而且需要的创建的对象还不少的话,我们要在这个简单工厂类里编写很多个方法,每个方法里都得写很多相应的业务代码,而每次增加子类或者删除子类对象的创建都需要打开这简单工厂类来进行修改。这会导致这个简单工厂类很庞大臃肿、耦合性高,而且增加、删除某个子类对象的创建都需要打开简单工厂类来进行修改代码也违反了开-闭原则。
2,工厂方法模式
这时候就需要使用工厂模式了。工厂方法模式是对简单工厂模式进一步的解耦,因为在工厂方法模式中是一个子类对应一个工厂类,而这些工厂类都实现于一个抽象接口。这相当于是把原本会因为业务代码而庞大的简单工厂类,拆分成了一个个的工厂类,这样代码就不会都耦合在同一个类里了。
这时上面那个例子的结构图为:
1,首先定义一个工厂接口:
import org.zero01.operation.Operation;
public interface Factory {
public Operation createOperation() ;
}
2. 然后是具体的工厂类:
// 加法类工厂
public class AddFactory implements Factory{
public Operation createOperation() {
System.out.println("加法运算");
return new Add();
}
}
// 减法类工厂
public class SubFactory implements Factory{
public Operation createOperation() {
System.out.println("减法运算");
return new Sub();
}
}
工厂方法模式增加的类还是很多的,感觉可以切分的比例降低。
比较:
工厂模式中,要增加产品类时也要相应地增加工厂类,客户端的代码也增加了不少。工厂方法把简单工厂的内部逻辑判断转移到了客户端代码来进行。
你想要加功能,本来是改工厂类的,而现在是修改客户端。而且各个不同功能的实例对象的创建代码,也没有耦合在同一个工厂类里,这也是工厂方法模式对简单工厂模式解耦的一个体现。工厂方法模式克服了简单工厂会违背开-闭原则的缺点,又保持了封装对象创建过程的优点。
但工厂方法模式的缺点是每增加一个产品类,就需要增加一个对应的工厂类,增加了额外的开发量。
3. 抽象工厂模式
场景:对数据库中的表进行修改
此时,使用工厂模式结构图如下:
1. 我们现在要对mysql/oracle数据库中的User表进行操作,User表定义如下:
public class User {
private int uid;
private String uname;
public int getUid() {
return uid;
}
public void setUid(int uid) {
this.uid = uid;
}
public String getUname() {
return uname;
}
public void setUname(String uname) {
this.uname = uname;
}
}
2. 接下来我们定义一个对User进行操作的接口:
public interface IUser {
public void insert(User user);
public User getUser(int uid);
}
3. 实现一个对mysql中User进行操作的类:
public class mysqlUser implements IUser{
public void insert(User user){
System.out.println("在mysql中的user表中插入一条元素");
}
public User getUser(int id){
System.out.println("在mysql中的user表得到id为"+id+"的一条数据");
return null;
}
}
实现对oracle中User进行操作的类:
public class oracleUser implements IUser{
@Override
public void insert(User user) {
System.out.println("在oracle中的user表中插入一条元素");
}
@Override
public User getUser(int uid) {
System.out.println("在oracle中的user表得到id为"+uid+"的一条数据");
return null;
}
}
4. 接下来定义一个工厂接口,用于生产访问User表的对象:
public interface sqlFactory {
public IUser createUser(); //用于访问User表的对象
}
5. 生产mysqlUser对象的mysql工厂类:
public class mysqlFactory implements sqlFactory {
@Override
public IUser createUser() {
return new mysqlUser(); //访问mysql中User表的对象
}
}
生成oracleUser对象的oracle工厂类:
public class oracleFactory implements sqlFactory {
@Override
public IUser createUser() {
return new oracleUser(); //访问oracle中User表的对象
}
}
6. 最后用户测试类如下:
public class test_abstractFactory {
public static void main(String[] args) {
sqlFactory factory1 = new mysqlFactory();
IUser userOperator = factory1.createUser();
userOperator.getUser(1);
userOperator.insert(new User());
}
}
结果为:
在mysql中的user表得到id为1的一条数据
在mysql中的user表中插入一条元素
到此为止,工厂模式都可以很好的解决,由于多态的关系,sqlFactory在声明对象之前都不知道在访问哪个数据库,却可以在运行时很好的完成任务,这就是业务逻辑与数据访问的解耦。
但是,当数据库中不止一个表的时候该怎么解决问题呢,此时就可以引入抽象工厂模式了,结构图如下:
1. 比如说现在增加了一个Login类,用于记录登陆信息:
package DesignPattern.abstractFactory;
import java.util.Date;
public class Login {
private int id;
private Date date;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public Date getDate() {
return date;
}
public void setDate(Date date) {
this.date = date;
}
}
2. 此时就要相应地添加 对login表操作的Ilogin接口,mysqlLogin类,oracleLogin类:
public interface ILogin {
public void insert(Login login);
public Login getLogin(int id);
}
public class MysqlLogin implements ILogin{
public void insert(Login login) {
System.out.println("对 MySQL 里的 Login 表插入了一条数据");
}
public Login getLogin(int id) {
System.out.println("通过 uid 在 MySQL 里的 Login 表得到了一条数据");
return null;
}
}
public class OracleLogin implements ILogin{
public void insert(Login login) {
System.out.println("对 Oracle 里的 Login 表插入了一条数据");
}
public Login getLogin(int id) {
System.out.println("通过 uid 在 Oracle 里的 Login 表得到了一条数据");
return null;
}
}
3. 修改Factory接口及Factory实现类的内容:
IFactory,定义一个抽象的工厂接口,该工厂用于生产访问User表以及Login表的对象:
public interface IFactory {
public IUser createUser();
public ILogin createLogin();
}
public class MysqlFactory implements IFactory{
public IUser createUser() {
return new MysqlUser();
}
public ILogin createLogin() {
return new MysqlLogin();
}
}
public class OracleFactory implements IFactory{
public IUser createUser() {
return new OracleUser();
}
public ILogin createLogin() {
return new OracleLogin();
}
}
4. 客户端代码:
public class Client {
public static void main(String[] args){
User user=new User();
Login login = new Login();
// 只需要确定实例化哪一个数据库访问对象给factory
// IFactory factory=new MysqlFactory();
IFactory factory=new OracleFactory();
// 已与具体的数据库访问解除了耦合
IUser userOperation=factory.createUser();
userOperation.getUser(1);
userOperation.insert(user);
// 已与具体的数据库访问解除了耦合
ILogin loginOperation=factory.createLogin();
loginOperation.insert(login);
loginOperation.getLogin(1);
}
}
结果:
通过 uid 在 Oracle 里的 User 表得到了一条数据
对 Oracle 里的 User 表插入了一条数据
对 Oracle 里的 Login 表插入了一条数据
通过 uid 在 Oracle 里的 Login 表得到了一条数据
所以抽象工厂与工厂方法模式的区别在于:抽象工厂是可以生产多个产品的,例如 MysqlFactory 里可以生产 MysqlUser 以及 MysqlLogin 两个产品,而这两个产品又是属于一个系列的,因为它们都是属于MySQL数据库的表。而工厂方法模式则只能生产一个产品,例如之前的 MysqlFactory 里就只可以生产一个 MysqlUser 产品。
抽象工厂模式的优缺点:
优点:
1. 抽象工厂模式最大的好处是易于交换产品系列,由于具体工厂类,例如 IFactory factory=new OracleFactory(); 在一个应用中只需要在初始化的时候出现一次,这就使得改变一个应用的具体工厂变得非常容易,它只需要改变具体工厂即可使用不同的产品配置。不管是任何人的设计都无法去完全防止需求的更改,或者项目的维护,那么我们的理想便是让改动变得最小、最容易,例如我现在要更改以上代码的数据库访问时,只需要更改具体的工厂即可。
2. 抽象工厂模式的另一个好处就是它让具体的创建实例过程与客户端分离,客户端是通过它们的抽象接口操作实例,产品实现类的具体类名也被具体的工厂实现类分离,不会出现在客户端代码中。就像我们上面的例子,客户端只认识IUser和ILogin,至于它是MySQl里的表还是Oracle里的表就不知道了。
缺点:
1. 如果你的需求来自增加功能,比如增加Login表,就有点太烦了。首先需要增加 ILogin,mysqlLogin,oracleLogin。 然后我们还要去修改工厂类: sqlFactory, mysqlFactory, oracleFactory 才可以实现,需要修改三个类,实在是有点麻烦。
2. 还有就是,客户端程序肯定不止一个,每次都需要声明sqlFactory factory=new MysqlFactory(), 如果有100个调用数据库的类,就需要更改100次sqlFactory factory=new oracleFactory()。
4,工厂方法的应用——java.util.Calendar
在这个类中获取Calendar类用到了简单工厂模式:
public static Calendar getInstance() {
return createCalendar(TimeZone.getDefault(), Locale.getDefault(Category.FORMAT));
}
private static Calendar createCalendar(TimeZone var0, Locale var1) {
CalendarProvider var2 = LocaleProviderAdapter.getAdapter(CalendarProvider.class, var1).getCalendarProvider();
if (var2 != null) {
try {
return var2.getInstance(var0, var1);
} catch (IllegalArgumentException var7) {
}
}
Object var3 = null;
if (var1.hasExtensions()) {
String var4 = var1.getUnicodeLocaleType("ca");
if (var4 != null) {
byte var6 = -1;
switch(var4.hashCode()) {
case -1581060683:
if (var4.equals("buddhist")) {
var6 = 0;
}
break;
case -752730191:
if (var4.equals("japanese")) {
var6 = 1;
}
break;
case 283776265:
if (var4.equals("gregory")) {
var6 = 2;
}
}
switch(var6) {
case 0:
var3 = new BuddhistCalendar(var0, var1);
break;
case 1:
var3 = new JapaneseImperialCalendar(var0, var1);
break;
case 2:
var3 = new GregorianCalendar(var0, var1);
}
}
}
if (var3 == null) {
if (var1.getLanguage() == "th" && var1.getCountry() == "TH") {
var3 = new BuddhistCalendar(var0, var1);
} else if (var1.getVariant() == "JP" && var1.getLanguage() == "ja" && var1.getCountry() == "JP") {
var3 = new JapaneseImperialCalendar(var0, var1);
} else {
var3 = new GregorianCalendar(var0, var1);
}
}
return (Calendar)var3;
}
这里面用到的设计模式可不少,有适配器模式、建造者模式,外层创建类采用的是简单工厂模式,将创建Calendar的过程封装起来。
小结
简单工厂模式,即直接把new的过程封装起来;工厂方法模式,先加类别的工厂,然后根据类别创建;抽象工厂方法模式,则分不同工厂类别,然后直接通过工厂进行创建(不用创类别)。
参考:
一篇博客
韩顺平《图解设计模式》