设计模式之抽象工厂模式

来源:互联网 发布:json查看 编辑:程序博客网 时间:2024/04/25 14:11

最近在看《大话设计模式》时,书上以一个例子作引子,这个引子很有意思,我记录一下。
假如有个项目,开始是用SQLserver作数据库,现在因为某种原因,要更换到Access,或者oracle(oracle更恶心),那如何做?
这几种数据库都有各自不同的框架和方法(因为我们不会直接操作SQL语句,那样非常脆弱)。因此,对于简单的插入表的方法就会完全不一样。

想想怎么做?很直观的想法是,将数据表示层与业务逻辑层分离,也就是抽象的思想。业务逻辑层如果根本无法区分到底使用的是什么数据库,那我们的目的就达到了。

在这个例子中,不管什么数据库,方法都是一样的。比如创建表,也许具体代码不一样,但是这个方法是一定要有的。除此之外,还有对各个表的操作,增删改查,同样,也许数据库操作的方法并不一样,但是增删改查的基本方法是要有的。

因此创建一个抽象工厂类

//抽象数据库类IDatabase{  ITableA createTableA();  ITableB createTableB();}//具体Access数据库AccessDatabase implements IDatabase{  ITableA createTableA(){    ......  } ITableB createTableB(){    ......  }}//具体oracle数据库OracleDatabase implement IDatabase{ITableA createTableA(){    ......  } ITableB createTableB(){    ......  }}//抽象的表A//因为表可以由Access创建,也可以由oracle创建ITableA{  insert(); update(); delete(); select();}//具体的表A//由AccessDatabase创建AccessTableA implements ITableA{  。。。。}//具体的表A//由OracleDatabase创建OracleTableA implements ITableA{  .....}//抽象表B。。。。

OK,抽象工厂模式就这样了,然后在客户端使用的时候,就使用IDatabase db = new AccessDatabase();
ITableA tableA = db.createTableA();再之后,客户端就完全屏蔽了数据库的差异。对它而言,只要操作tableA 就可以了。
以上就是抽象工厂模式。

下面分析一下它的优缺点:
优点在于它隔离了业务逻辑层和数据库连接层。此后无论再变成SQLite或者别的什么数据库,业务逻辑代码都不用更改了。
缺点在于,比如新添一张表,就要在抽象类中添加一个createtableC的方法,然后在各个数据库中都增加相应方法,然后在再新建一个tableC的类,这个类再实现增删改查的方法。然后就像晓峰所说的,类会越来越多,,越来越难以维护。
而且还有一个问题,比如在客户端程序中,有100个地方用来new AccessDatabase(),那当变成Oracle时,将会有100个地方要改变的。

如何解决上述问题呢?

我们可以想到使用简单工厂模式,

class Factory{   private String database="Access";   public ITableA createTableA(){      if(database.equalsOf("Access")){         return new AccessDatabase().createTableA();       }else if(database.equalsOf("Oracle")){        return new OracleDatabase().createTableA();      }   }   public ITableB createTableB(){      .....   } }

这样,当要更改数据库时,只要将这里属性中的database换成希望的database名字即可。然后再新加个switch case即可,当然了,新建数据库后,创建数据库的表的方法,以及表中所要包含的方法名一个都不能少。【这是需求要求所致,没有办法】。这种方法的结果就是客户端完全不必关心到底采用的是什么数据库。

但是上面代码还有问题:就是每新加一个数据库,都要新加一个switch case,然后还要再更改属性值。能不能不再添加switch case?

有一个方法,就是反射,反射是另外一种创建对象的方法,我们平时创建对象时,需要知道类名,然后new 类名()创建了对象,那反射呢?是使用一个字符串,对于Java而言,就是class.forName(“完整类名”).newInstance();这样就创建了一个类。
看到有什么好处了么?使用反射,可以通过动态提供类名来完成对象的创建,而这个动态的类名是一个字符串。因此,如果使用反射机制,那么上述代码可以变为:

class Factory{   private String database="com.dacas.Access";   public ITableA createTableA(){      IFactory factory = (IFactory)Class.forName(database).newInstance();      return factory.createTableA();   }   public ITableB createTableB(){      .....   } }

OK,现在如果要变成Oracle数据库,只需要将database名变成 com.dacas.oracle即可。

这种方法还有缺陷,那就是每次做修改都要拿到源码,然后更改源码。如果拿不到源码,或者以及编译好了,那就抓瞎了。
所以最好的方法是使用配置文件。

对于Java而言,使用properties配置文件是最好不过的了,它使用一个键值对来存储。
比如配置文件为:database.properties,文件内容为:database=com.dacas.Access

在上述代码中就变成了:

class Factory{   private String database;   public Factory(){     Properties pro = new Properties();     FileInputStream in = new FileInputStream("database.propreties");     pro.load(in);     in.close();     this.database = pro.getProperty(database);   }   public ITableA createTableA(){      IFactory factory = (IFactory)Class.forName(database).newInstance();      return factory.createTableA();   }   public ITableB createTableB(){      .....   } }

OK,这样一来,利用反射与简单工厂模式就完整的解决了上述难题。

0 0
原创粉丝点击