设计模式——工厂模式和抽象工厂模式
Java设计模式——工厂模式
工厂模式和抽象工厂模式都属于创建型模式。
创建型模式
这些设计模式提供了一种在创建对象的同时隐藏创建逻辑的方式,而不是使用 new 运算符直接实例化对象。这使得程序在判断针对某个给定实例需要创建哪些对象时更加灵活。
简单工厂模式
在简单工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。
作用:
-
1、减少和复用代码:同一类复杂的对象(比如圆形、方形、三角形都属于图形;线性布局、相对布局都属于布局),如果它们在创建时都需要编写大量的步骤,那么可以考虑将它们的创建交给工厂去进行;
-
2、减少耦合度:两个类的关系最好是A创造B或者是A使用B,而不是A又创造B又使用B(单一职能原则)。比如我使用B时是直接
new
一个B对象,往后我们需求变更,需要将B替换为C,那么我就需要更改代码中new B
为new C
,这样的替换很麻烦也很容易出错。
优点:
- 1、一个调用者想创建一个对象,只要知道其名称就可以了;
- 2、屏蔽产品的具体实现,调用者只关心产品的接口;
- 3、简单粗暴,可以创建任何我们想要创建的对象。
缺点:
- 1、每次增加一个产品时,都需要修改工厂类,这违反了开闭原则,同时也增加了系统具体类的依赖。这并不是什么好事。
使用案例:
首先,我们现在有一个需求,要连接MySQL数据库,那么我们建一个连接MySQL数据库的类:
public class MySQLHelper {
private String userName;
private String passWord;
public MySQLHelper(String userName, String password) {
this.userName = userName;
this.passWord = password;
}
public void setTimeout(long time) {
//Set TimeOut
}
public void setSelectMode(String mode) {
// Set SelectMode
}
public void setCache(String cache) {
// Set Cache
}
public void connect() {
// Connect SQL
}
// ... 更多初始化操作
}
现在,我们在AConnectSQL
类和BConnectSQL
类中分别用不同的账号密码连接数据库:
AConnectSQL
:
public class AConnectSQL {
public void connect() {
MySQLHelper mySQLHelper = new MySQLHelper("jack", "123");
mySQLHelper.setTimeout(1000);
mySQLHelper.setSelectMode("A Mode");
mySQLHelper.setCache("ALL");
mySQLHelper.connect();
}
}
BConnectSQL
:
public class BConnectSQL {
public void connect() {
MySQLHelper mySQLHelper = new MySQLHelper("lucy", "456");
mySQLHelper.setTimeout(1000);
mySQLHelper.setSelectMode("A Mode");
mySQLHelper.setCache("ALL");
mySQLHelper.connect();
}
}
以上代码存在两个问题:
1、每一次创建MySQLHelper
时,都需要编写大量初始化代码;
2、如果项目需求改动,要求不再所有的数据库连接都连接MySQL
,而改用部分连接MySQL
,部分连接SQLite
,此时就需要将部分连接MySQL
的地方都改成连接SQLite
,一个两个类还好改,要是有更多的类,那么不仅工作量巨大,而且极有可能造成问题隐患。
此时,工厂模式就可以很好的解决如上两个问题。
首先,我们先建一个接口SQLHelper
,只有一个connectSQL
方法用于连接数据库:
public interface SQLHelper {
void connectSQL();
}
然后,让我们的MySQLHelper
实现这个接口,在重写的connectSQL
中调用connect
方法连接数据库:
public class MySQLHelper implements SQLHelper{
private String userName;
private String passWord;
public MySQLHelper(String userName, String password) {
this.userName = userName;
this.passWord = password;
}
public void setTimeout(long time) {
//Set TimeOut
}
public void setSelectMode(String mode) {
// Set SelectMode
}
public void setCache(String cache) {
// Set Cache
}
public void connect() {
// Connect SQL
}
// ... 更多初始化操作
@Override
public void connectSQL() {
connect();
}
}
接着,我们新建一个类似于MySQLHelper
的类SQLiteHelper
用于连接SQLite
数据库,并且它也实现了SQLHelper
这个接口:
public class SQLiteHelper implements SQLHelper {
private String userName;
private String passWord;
public SQLiteHelper(String userName, String password) {
this.userName = userName;
this.passWord = password;
}
public void setTimeout(long time) {
//Set TimeOut
}
public void setSelectMode(String mode) {
// Set SelectMode
}
public void setCache(String cache) {
// Set Cache
}
public void connect() {
// Connect SQL
}
// ... 更多初始化操作
@Override
public void connectSQL() {
connect();
}
}
好,我们接下来新建一个连接数据库的工厂类SQLHelperFactory
:
public class SQLHelperFactory {
public static final int TYPE_SQLITE = 0x0001;
public static final int TYPE_MYSQL = 0x0002;
public SQLHelper createSQLHelper(int type, String userName, String passWord) {
if(type == TYPE_SQLITE) {
MySQLHelper mySQLHelper = new MySQLHelper(userName, passWord);
mySQLHelper.setTimeout(1000);
mySQLHelper.setSelectMode("A Mode");
mySQLHelper.setCache("ALL");
return mySQLHelper;
}else if(type == TYPE_MYSQL) {
SQLiteHelper sqLiteHelper = new SQLiteHelper(userName, passWord);
sqLiteHelper.setTimeout(1000);
sqLiteHelper.setSelectMode("A Mode");
sqLiteHelper.setCache("ALL");
return sqLiteHelper;
}
return null;
}
}
然后,我们在需要使用这两种数据库连接的AConnectSQL
和BConnectSQL
代码更改成这样:
AConnectSQL
:
public class AConnectSQL {
public void connect() {
SQLHelperFactory sqlHelperFactory = new SQLHelperFactory();
SQLHelper sqlHelper = sqlHelperFactory.createSQLHelper(SQLHelperFactory.TYPE_SQLITE, "jack", "123");
sqlHelper.connectSQL();
}
}
BConnectSQL
:
public class BConnectSQL {
public void connect() {
SQLHelperFactory sqlHelperFactory = new SQLHelperFactory();
SQLHelper sqlHelper = sqlHelperFactory.createSQLHelper(SQLHelperFactory.TYPE_MYSQL, "lucy", "456");
sqlHelper.connectSQL();
}
}
这样,需要连接哪个数据库就从工厂中创造该类型的数据库即可。即使以后需要将SQLite
换成Oracle
也仅仅只是将SQLHelperFactory
中的SQLite
更改就行。
有的朋友也许会通过反射的写法写工厂类的方法:
public SQLHelper createSQLHelper2(Class<? extends SQLHelper> clazz) {
SQLHelper sqlHelper = null;
try {
sqlHelper = (SQLHelper) Class.forName(clazz.getName()).newInstance();
} catch (InstantiationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return sqlHelper;
}
用的时候,这样调用:
SQLHelperFactory sqlHelperFactory = new SQLHelperFactory();
SQLHelper sqlHelper = sqlHelperFactory.createSQLHelper2(SQLiteHelper.class);
但是根据迪米特原则(越少知道越好),调用者是不知道有SQLiteHelper
类存在的,如果他知道,那么为什么不直接new SQLiteHelper
呢?所以,采用上述这种调用方法的模式,我认为背离了工厂模式的原则。
工厂模式
其实工厂模式和简单工厂模式类似,只是在简单工厂模式中,我们创建了一个类SQLHelperFactory
用来创造我们需要的连接数据库的对象比如SQLiteHelper
或者MySQLHelper
。
而在工厂模式中,我们是把SQLHelperFactory
也抽象化,而会针对不同的数据库创建不同的具体工厂,通过具体工厂来创造我们需要的对象。
因为在简单工厂模式中,我们其实可以返回任何我们想要的对象,如果我们想要生产一辆飞机,只需要在判断时传入Type
为飞机,同时在create
方法中返回飞机的对象就行。而我们的工厂的目的明明是返回SQLHelper
。因此,工厂模式就很好的解决了这个问题。
优点:
- 1、减轻工厂类的负担,某一类对象交给某一类的工厂去生产;
- 2、增加新的分类时不用修改工厂类,只需要修改某一类的工厂,符合开闭原则。
缺点:
1、每新增一个分类,就需要增加分类的工厂和产品类,造成代码量成倍增加。
使用场景:
首先,我们把类SQLHelperFactory
更改成抽象类SQLHelperFactory
:
public abstract class SQLHelperFactory {
public abstract SQLHelper createSQLHelper(String username, String password);
}
然后,我们创建两个类SQLiteHelperFactory
和MySQLHelperFactory
两个工厂类,它们的作用分别是用来生产SQLiteHelper
和MySQLHelper
:
SQLiteHelperFactory
:
public class SQLiteHelperFactory extends SQLHelperFactory{
@Override
public SQLHelper createSQLHelper(String username, String password) {
SQLiteHelper sqLiteHelper = new SQLiteHelper(username, password);
sqLiteHelper.setTimeout(1000);
sqLiteHelper.setSelectMode("A Mode");
sqLiteHelper.setCache("ALL");
return sqLiteHelper;
}
}
MySQLHelperFactory
:
public class MySQLHelperFactory extends SQLHelperFactory{
@Override
public SQLHelper createSQLHelper(String username, String password) {
MySQLHelper mySQLHelper = new MySQLHelper(username, password);
mySQLHelper.setTimeout(1000);
mySQLHelper.setSelectMode("A Mode");
mySQLHelper.setCache("ALL");
return mySQLHelper;
}
}
使用的时候:
public class AConnectSQL {
public void connect() {
SQLiteHelperFactory sqLiteHelperFactory = new SQLiteHelperFactory();
sqLiteHelperFactory.createSQLHelper("jack", "123");
SQLHelper sqlHelper = sqLiteHelperFactory.createSQLHelper("jack", "123");
sqlHelper.connectSQL();
}
}
抽象工厂
在工厂模式中,我们将每一个不同的数据库都更改为一个单独的工厂。这样做,我们避免了“超级工厂”的存在,仅仅针对不同产品,提供不同的工厂。
而抽象工厂的出现,是在工厂模式的基础上又进一步抽象——抽象工厂将工厂和产品都抽象化了,创造一系列相互依赖的接口或类,而无需指明它们具体的实现类。
比如,我们在工厂模式中,我们假设现在是在Android系统上连接数据库,那么此时我们仅仅对每一个不同的数据库做一个单独的工厂,如果我们现在切换到IOS系统上,可能此时连接数据库的方式又变了,那么此时我们在Android系统上所写的连接数据库的方法可能就不管用了,需要重新写工厂模式,而抽象工厂则是把工厂再抽象一层,每一个工厂都有连接MySQL
和SQLite
的方法,而具体是哪个系统去连接,则交给他们自己去实现。
因此,可以说,工厂模式是针对一个产品,抽象工厂是针对多个产品。
优点:
同工厂模式。
缺点:
扩展极其困难。
使用场景:
比如我们针对MySQL
和SQLite
都生产了不同的工厂,那么,如果我们在不同的操作系统(Android,IOS等)上连接数据库的方式不同,那么此时就不在使用工厂模式,而使用抽象工厂模式了。
1、首先,我们新建一个抽象产品类ConnectMySQLHelper
,用来连接MySQL
数据库:
public abstract class ConnectMySQLHelper {
public abstract void connectMySQL();
}
然后建两个实体类AndroidConnectMySQLHelper
和IOSConnectMySQLHelper
分别实现去连接Android和IOS的方法:
AndroidConnectMySQLHelper
:
public class AndroidConnectMySQLHelper extends ConnectMySQLHelper {
@Override
public void connectMySQL() {
System.out.println("Android connect MySQL");
}
}
IOSConnectMySQLHelper
:
public class IOSConnectMySQLHelper extends ConnectMySQLHelper{
@Override
public void connectMySQL() {
System.out.println("IOS connect MySQL");
}
}
2、同样的,我们也要像步骤1一样建立一个抽象类ConnectSQLiteHelper
和两个实体类AndroidConnectSQLiteHelper
,IOSConnectSQLiteHelper
分别去连接SQLite
数据库。
3、创建一个抽象工厂类,用来连接MySQL
和SQLite
:
public abstract class AbstractSQLFactory {
public abstract ConnectMySQLHelper connectMySQL();
public abstract ConnectSQLiteHelper connectSQLite();
}
4、分别创建Andoird和IOS连接两个数据库的工厂类:
AndroidSQLFactory
:
public class AndroidSQLFactory extends AbstractSQLFactory {
@Override
public ConnectMySQLHelper connectMySQL() {
return new AndroidConnectMySQLHelper();
}
@Override
public ConnectSQLiteHelper connectSQLite() {
return new AndroidConnectSQLiteHelper();
}
}
IOSSQLFactory
:
public class IOSSQLFactory extends AbstractSQLFactory {
@Override
public ConnectMySQLHelper connectMySQL() {
return new IOSConnectMySQLHelper();
}
@Override
public ConnectSQLiteHelper connectSQLite() {
return new IOSConnectSQLiteHelper();
}
}
抽象工厂模式,我个人也掌握的不是很好,没有理解它的精髓,但是却看到了它扩展很难的一面(比如此时我再来一个Windows系统,又需要从AbstractSQLFactory
那一层开始更改起),因此,我不打算在自己的编码中引入这种模式。