定義
抽象工廠模式提供一個創建一系列相關或相互依賴對象的接口
無須指定它們具體的類
類型
創建型
適用場景
- 客戶端(應用層)不依賴於產品類實例如何被創建、實現等細節
- 強調一系列相關的產品對象(屬於同一產品族)一起使用,創建對象需要大量重複的代碼
- 提供一個產品類的庫,所有的產品以同樣的接口出現,從而使客戶端不依賴於具體的實現
優點
- 具體的產品在應用層代碼隔離,無須關心創建細節
- 將一系列的產品族統一到一起創建
缺點
- 規定了所有可能被創建的產品集合,產品族中擴展新的產品困難,需要修改抽象工廠的接口
- 增加了系統的抽象性和理解難度
產品等級結構與產品族
工廠方法模式針對的是產品等級結構,抽象工廠模式針對的就是產品族,我們只要在美的工廠裏取空調,取出來的肯定是美的的空調,在美的工廠裏取冰箱,取出來的肯定也是美的冰箱。所以,我們只要指出一個產品所處的產品族,以及所屬的等級結構,就可以唯一確定這個產品。
這裏一定要注意抽象工廠和工廠方法最大的區別,具體使用哪一個還是要看我們實際的業務場景。理論上來說,當一個工廠可以創建出分屬於不同產品等級結構的一個產品族中的所有對象時,那這個時候抽象工廠模式比工廠方法模式更爲簡單更有效率。
coding
業務場景:現在有一個新的要求,每一個課程不僅要有視頻,還要有對應的手記。
那如果按照工廠方法的方式來擴展的話,我們想象一下:前端手機、java手記都是各有特色,那如果按照工廠方法現在的擴展來說,我們要創建一個前端的手記類、java的手記類、python的手記類,前端手記的工廠、java手記的工廠、python手記的工廠,同時我們還要創建手記的抽象類,還有手記的工廠。
在工廠方法中,如果像這種我們的業務場景發生了比較大的擴展,很容易發生類爆炸這麼一種情況,也就是說我們要建很多很多的類。
而原來呢一個視頻就是一個課程,現在是一個視頻+手記是一個課程,現在分析下,java視頻、前端視頻、python視頻屬於同一產品等級,java手記、前端手記、python手記都是同一產品等級;java視頻+java手記屬於同一產品族
抽象工廠,使用接口、抽象類都行
public interface CourseFactory {
Video getVideo();
Article getArticle();
}
手記
public abstract class Article {
public abstract void produce();
}
視頻
public abstract class Video {
public abstract void produce();
}
視頻+手記是一個課程,下面聲明一個java產品族的工廠
public class JavaCourseFacotry implements CourseFactory {
@Override
public Video getVideo() {
return new JavaVideo();
}
@Override
public Article getArticle() {
return new JavaArticle();
}
}
java手記
public class JavaArticle extends Article{
@Override
public void produce() {
System.out.println("編寫Java課程手記");
}
}
java視頻
public class JavaVideo extends Video {
@Override
public void produce() {
System.out.println("錄製java視頻課程");
}
}
視頻+手記是一個課程,下面聲明一個python產品族的工廠
public class PythonCourseFacotry implements CourseFactory {
@Override
public Video getVideo() {
return new PythonVideo();
}
@Override
public Article getArticle() {
return new PythonArticle();
}
}
python手記
public class PythonArticle extends Article{
@Override
public void produce() {
System.out.println("編寫Python課程手記");
}
}
python產品族
public class PythonVideo extends Video {
@Override
public void produce() {
System.out.println("錄製Python視頻課程");
}
}
類圖
每個設計模式都是結合實際的業務場景,在某些業務場景可能這種設計模式就要改進,甚至優化,甚至可能帶來更多的麻煩。例如我們現在要增加算法課程工廠,對於現在的抽象工廠模式非常的容易擴展,對原來的類還不需要變化,也就是說,我們在這裏創建一個算法的課程工廠,它實現課程工廠接口,接着再創建具體的算法視頻、算法手記這兩個實際的產品就可以了,其它的類是不需要動的。
應用層
public class Test {
public static void main(String[] args) {
CourseFactory courseFactory=new JavaCourseFacotry();
Video video=courseFactory.getVideo();
Article article=courseFactory.getArticle();
video.produce();
article.produce();
}
}
看上面應用層代碼,在使用時候並不關心產品族創建的一個過程,我們應用層代碼只需要關心它是那個產品族工廠就可以了,因爲我們應用層代碼不會直接創建Java手記和python手記,java視頻和python視頻,整個創建過程是交由具體的工廠來創建的,並且它創建的是一個產品族產品,這裏邊創建出來的肯定都是java的。
這也是抽象工廠的一大優點:
- 應用層代碼不和具體的產品發生依賴,只和具體的產品族工廠發生依賴關係
- 從具體的產品族工廠取出來的肯定是同一產品族
- 擴展性好:比如新增 前端的產品族,只需要增加前端的課程工廠和前端的具體產品就可以了,很方便,無需修改現有系統,符合開閉原則。
抽象工廠的缺點是新增產品等級比較麻煩,要對原有的系統進行比較大的修改,在這種業務場景下就違背了開閉原則,所以說某些原則在某些場景是符合的,比如擴展產品族,這個是符合開閉原則的;但是在產品族裏爲現在有的產品增加新的產品等級,那就不符合開閉原則了。比如:
我們引入一個新的業務場景,這個課程呢除了視頻、手記,還要將源碼放在裏面,也就是說視頻、手記、源碼才能構成課程,那麼對原有的修改就比較大了。首先對課程工廠增加一個源碼,再對具體的課程工廠增加源碼,最後再創建源碼的抽象類和寫具體的java源碼、python源碼的實際產品,這時候是不符合開閉原則的,因爲我們要修改具體的實現了。
所以對於產品族裏擴展新的產品等級還是比較麻煩的,所以我們在平時工作的業務場景會找一些產品等級結構相對固定的,並且還需要多個產品來組合到一起形成產品族的這麼一個業務場景,當然如果碰到頻繁改動也不適於抽象工廠,比如今天加一個產品等級,明天還要加一個,後天還要加一個,這個是太頻繁了,這個類要經常經常改。我們要找相對固定的,比如可以固定個一年兩年三年的,這個時間就很長了,這種我們去改也是ok的,只不過測試的時候多仔細些。比如隨着網站不斷的擴展,一年兩年呢增加一個新的產品等級也是沒有問題的,半年也是ok的。
源碼解析
java.sql.Connection
它是一個接口,所以方法也都是抽象方法,看下面這兩個方法
Statement createStatement() throws SQLException;
PreparedStatement prepareStatement(String sql)
throws SQLException;
很明顯這兩個方法返回的都是同一個產品族,例如MySQL的、PostgreSQL的。再看方法返回值Statement
,
也是用到抽象工廠。再看Connection的實現類
我們只要從MySQL這個實現類中獲取,得到的一定是MySQL這個產品族中的東西。
org.apache.ibatis.session.SqlSessionFactory
package org.apache.ibatis.session;
import java.sql.Connection;
public interface SqlSessionFactory {
SqlSession openSession();
SqlSession openSession(boolean var1);
SqlSession openSession(Connection var1);
SqlSession openSession(TransactionIsolationLevel var1);
SqlSession openSession(ExecutorType var1);
SqlSession openSession(ExecutorType var1, boolean var2);
SqlSession openSession(ExecutorType var1, TransactionIsolationLevel var2);
SqlSession openSession(ExecutorType var1, Connection var2);
Configuration getConfiguration();
}
這個接口不僅返回SqlSession,還要返回Configuration,那很明顯只要通過SqlSessionFactory去獲取的這兩種類型,都是同一種產品族。
再看一下實現類
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
DefaultSqlSession var8;
try {
Environment environment = this.configuration.getEnvironment();
TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
Executor executor = this.configuration.newExecutor(tx, execType);
var8 = new DefaultSqlSession(this.configuration, executor, autoCommit);
} catch (Exception var12) {
this.closeTransaction(tx);
throw ExceptionFactory.wrapException("Error opening session. Cause: " + var12, var12);
} finally {
ErrorContext.instance().reset();
}
return var8;
}
定義的是返回一個SqlSession,實際返回的是一個DefaultSqlSession。