设计模式——工厂模式和抽象工厂模式

Java设计模式——工厂模式

工厂模式和抽象工厂模式都属于创建型模式。

创建型模式
这些设计模式提供了一种在创建对象的同时隐藏创建逻辑的方式,而不是使用 new 运算符直接实例化对象。这使得程序在判断针对某个给定实例需要创建哪些对象时更加灵活。

简单工厂模式

在简单工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。
作用:

  • 1、减少和复用代码:同一类复杂的对象(比如圆形、方形、三角形都属于图形;线性布局、相对布局都属于布局),如果它们在创建时都需要编写大量的步骤,那么可以考虑将它们的创建交给工厂去进行;

  • 2、减少耦合度:两个类的关系最好是A创造B或者是A使用B,而不是A又创造B又使用B(单一职能原则)。比如我使用B时是直接new一个B对象,往后我们需求变更,需要将B替换为C,那么我就需要更改代码中new Bnew 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;
    } 

}

然后,我们在需要使用这两种数据库连接的AConnectSQLBConnectSQL代码更改成这样:

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);
}

然后,我们创建两个类SQLiteHelperFactoryMySQLHelperFactory两个工厂类,它们的作用分别是用来生产SQLiteHelperMySQLHelper

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系统上所写的连接数据库的方法可能就不管用了,需要重新写工厂模式,而抽象工厂则是把工厂再抽象一层,每一个工厂都有连接MySQLSQLite的方法,而具体是哪个系统去连接,则交给他们自己去实现。
因此,可以说,工厂模式是针对一个产品,抽象工厂是针对多个产品。

优点:
同工厂模式。

缺点:
扩展极其困难。

使用场景:
比如我们针对MySQLSQLite都生产了不同的工厂,那么,如果我们在不同的操作系统(Android,IOS等)上连接数据库的方式不同,那么此时就不在使用工厂模式,而使用抽象工厂模式了。

1、首先,我们新建一个抽象产品类ConnectMySQLHelper,用来连接MySQL数据库:

public abstract class ConnectMySQLHelper {
    
    public abstract void connectMySQL();

}

然后建两个实体类AndroidConnectMySQLHelperIOSConnectMySQLHelper分别实现去连接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和两个实体类AndroidConnectSQLiteHelperIOSConnectSQLiteHelper分别去连接SQLite数据库。

3、创建一个抽象工厂类,用来连接MySQLSQLite

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那一层开始更改起),因此,我不打算在自己的编码中引入这种模式。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容

  • 该文章属于刘小壮原创,转载请注明:刘小壮[https://www.jianshu.com/u/2de707c93d...
    刘小壮阅读 12,634评论 29 59
  • 工厂模式和抽象工厂模式都属于创建型模式。 创建型模式这些设计模式提供了一种在创建对象的同时隐藏创建逻辑的方式,而不...
    TokyoZ阅读 256评论 0 0
  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,036评论 1 32
  • 文章部分内容转载自:http://blog.csdn.net/zhangerqing 一、设计模式的分类 总体来说...
    j_cong阅读 2,021评论 0 20
  • 《记忆》 作者:蝼蚁 就回到少年 笑脸是无邪 就回到从前 听不见的是 谎言 就回到昨天 萤火虫和夏天 在回忆里的...
    野生作者蝼蚁阅读 234评论 0 3