工廠模式是一種創建型設計模式。
開閉原則
開閉原則是設計模式原則之一,倡導實體應當對擴展開發,對修改關閉。
意思是說,你想要擴展或者修改已寫好代碼的功能,可以往裏面加入新的代碼,但不允許修改修改以前已經寫好的代碼。
高耦合將會導致,拓展功能時,不得不修改以前的代碼。
比如
public class Service {
Dao dao = new JpaDao();
void service(){
//使用dao進行一系列持久層操作
}
}
起初我們使用JPA作爲ORM,將數據庫操作都封裝在JpaDao裏面,通過new來創建JpaDao實例。如果要把JPA改成Mybatis,就需要逐個去修改用到JpaDao的Service類。這就是Service和Dao的緊耦合導致違反開閉原則。
爲什麼會緊耦合?是因爲Service中直接new了一個非常具體的對象。
new操作本身就是一個耦合操作
引入工廠類
想要解耦,就得去掉new,但是要創建對象就必須得new。所以我們將new換個地方,創建一個工廠類。
public class DaoFactory {
public static Dao getDao() {
return new JpaDao();
}
}
public class Service {
Dao dao = DaoFactory.getDao();
void service(){
//使用dao進行一系列持久層操作
}
}
這樣的好處就是,後續不管更換什麼持久層框架,只需要改工廠類裏的代碼一個地方就好。
但是根本問題還是沒解決,還是違反了開閉原則。因爲到時候還是得改DaoFactory的代碼。
如何才能不改代碼修改實現方法?可以將類名寫在配置文件,通過反射創建對象。這樣就把耦合從代碼提到了配置文件。
dao=com.example.demo.pattern.factory.JpaDao
public class DaoFactory {
public static Dao getDao() {
Properties properties = new Properties();
properties.load(DaoFactory.class.getClassLoader().getResourceAsStream(“config.properties”));
Class<?> dao = Class.forName((String) properties.get("dao"));
return (Dao) dao.newInstance();
}
}
簡單工廠模式
簡單工廠模式,又叫靜態工廠模式。顧名思義,就是通過靜態方法獲取實例。
public class Factory {
public static <R,P> R getInstance(P type){
//根據傳入的type不同做if else判斷返回不同類型的對象。
}
}
public class Factory {
public static <R,P> R getInstance01(){
//創建一種類型的對象
}
public static <R,P> R getInstance02(){
//創建另一種類型的對象
}
}
也可以不定義爲靜態方法
public class Factory {
public <R,P> R getInstance(P type){
//根據傳入的type不同做if else判斷返回不同類型的對象。
}
}
這種方式又叫實例工廠模式。去靜態工廠模式區別在於,靜態方法的調用不用創建實例對象。但是靜態方法可以繼承,但是不能重寫,沒有重寫就不能實現多態的特性。所以靜態工廠模式無法通過繼承動態改變創建對象的行爲。
優缺點
- 優點:一定程度上實現解耦,這種解耦的特點是,將對象的創建和使用解耦。其實所有創建型的設計模式都有這個特點。
- 缺點:不符合開閉原則。靜態工廠模式的本質在於,通過各種方式(方法名,入參)傳入一個參數,做if判斷,達到返回不同對象的目的。因此,要加入新的類型,就不得不修改原來的代碼,違反開閉原則。上面說的反射+配置文件的方法,也沒有從根本解決問題。
或許就是這個原因,靜態工廠模式不屬於GOF23體系。
工廠方法模式
靜態工廠模式之所以違反開閉原則,是因爲它把所有相關對象的創建都集中在一個工廠類了。所以沒新增一個對象都要修改這個工廠類。
如果給每個對象寫一個自己的工廠類呢?
工廠方法模式,是在靜態工廠模式的基礎上,進一步抽象,將工廠類設計爲抽象類或者接口,不同產品實現自己的工廠實體類。創建對象時,只需要提供對應的產品類和該產品的工廠類即可,不需要知道內部過程。
interface DaoFactory{
Dao getDao();
}
class JpaDaoFactory implements DaoFactory{
public Dao getDao() {
return new JpaDao();
}
}
class MybatisDaoFactory implements DaoFactory{
public Dao getDao() {
return new MybatisDao();
}
}
然後創建Service類時,將具體的工廠類傳入,利用多態就可以實現不同的持久層的切換。
public class Service {
private Dao dao;
public Service(DaoFactory daoFactory) {
dao = DaoFactory.getDao();
}
void service() {
//使用dao進行一系列持久層操作
}
}
優缺點
- 優點:符合開閉原則。當有新的持久層實現時,只需要實現DaoFactory接口的getDao()方法即可,不改動原有代碼。
- 優點2:符合依賴倒置原則。
依賴倒置原則
依賴倒置原則倡導我們面向接口編程,不要面向現實編程。比如上面例子中,和業務類Service耦合的是最高層接口DaoFactory,而底層實現是用的時候才決定的。
- 缺點:如果產品的類型非常多,並且這些產品聯繫緊密,完全可以一起創建,但是你還是得給每一個產品寫一個工廠類,非常繁瑣。
比如要創建一個手槍產品,同時又得創建子彈產品。如果用工廠方法模式,得寫一個手槍和子彈的公共工廠接口,兩個工廠實現類,如果有其他配套,還得寫新的實現類,如果要生產另一種手槍B,工廠實現類會成倍增長。
抽象工廠模式
抽象工廠模式和工廠方法模式在GOF23中是單獨的模式。抽象工廠模式的出現,就是爲了解決上述工廠方法的問題。
工廠方法模式創建的對象,歸根結底都是同一類對象。
上面的JpaDao是一類對象,MybatisDao是一類對象。而在手槍的例子中,手槍A、手槍B各自的一系列產品,是一類對象,叫產品族。抽象工廠模式就是爲了創建一系列以產品族爲單位的對象。
我們將一種武器的手槍、子彈等包裝爲一個工廠類,就能大大減少工廠類的數量。
public interface Weapen {}
interface Gun extends Weapen{}
interface Bullet textends Weapen {}
public class GunA implements Gun {}
public class BulletA implements Bulle {}
public class GunB implements Gun {}
public class BulletB implements Bulle {}
//頂層武器工廠
public interface WeapenFactory{
Gun getGun() ;
Bullet getBullet();
}
//武器A實現類
public class WeapanAFactoryImpl implements WeapenFactory {
public GunA getGun() {
return new GunA();
}
public BulletA getBullet() {
return new BulletA();
}
}
//武器B實現類
public class WeapanBFactoryImpl implements WeapenFactory {
public GunB getGun() {
return new GunB();
}
public BulletB getBullet() {
return new BulletB();
}
}
抽象工廠與工廠方法
抽象工廠模式是工廠方法模式的升級。如果不涉及子彈,不涉及產品族,抽象工廠與工廠方法一模一樣。
工廠模式的應用
工廠模式應用很廣泛。
- JDK的Calendar,構造器是protected的,獲取對象需要通過getInstance()方法,這個方法就採用了靜態工廠模式,通過調用createCalendar()方法,傳入的兩個參數TimeZone(時區)和Local(區域),創建需要的對象。
private static Calendar createCalendar(TimeZone zone, Locale aLocale) {
//省略大量無關代碼
Calendar cal = null;
if (aLocale.hasExtensions()) {
String caltype = aLocale.getUnicodeLocaleType("ca");
if (caltype != null) {
switch (caltype) {
case "buddhist":
cal = new BuddhistCalendar(zone, aLocale);
break;
case "japanese":
cal = new JapaneseImperialCalendar(zone, aLocale);
break;
case "gregory":
cal = new GregorianCalendar(zone, aLocale);
break;
}
}
}
if (cal == null) {
if (aLocale.getLanguage() == "th" && aLocale.getCountry() == "TH") {
cal = new BuddhistCalendar(zone, aLocale);
} else if (aLocale.getVariant() == "JP" && aLocale.getLanguage() == "ja"
&& aLocale.getCountry() == "JP") {
cal = new JapaneseImperialCalendar(zone, aLocale);
} else {
cal = new GregorianCalendar(zone, aLocale);
}
}
return cal;
}
- Spring中有個FactoryBean接口,用來指定其實現類爲一個具體的工廠類,以創建特定類型的對象。
比如ThreadPoolExecutorFactoryBean就是創建返回一個線程池對象;ProxyFactoryBean用來創建一個代理對象,就是典型的工廠方法模式的實現。
public class ThreadPoolExecutorFactoryBean extends ExecutorConfigurationSupport
implements FactoryBean<ExecutorService>, InitializingBean, DisposableBean {
//...
@Override
protected ExecutorService initializeExecutor(
ThreadFactory threadFactory, RejectedExecutionHandler rejectedExecutionHandler) {
BlockingQueue<Runnable> queue = createQueue(this.queueCapacity);
ThreadPoolExecutor executor = createExecutor(this.corePoolSize, this.maxPoolSize,
this.keepAliveSeconds, queue, threadFactory, rejectedExecutionHandler);
if (this.allowCoreThreadTimeOut) {
executor.allowCoreThreadTimeOut(true);
}
// Wrap executor with an unconfigurable decorator.
this.exposedExecutor = (this.exposeUnconfigurableExecutor ?
Executors.unconfigurableExecutorService(executor) : executor);
return executor;
}
protected ThreadPoolExecutor createExecutor(
int corePoolSize, int maxPoolSize, int keepAliveSeconds, BlockingQueue<Runnable> queue,
ThreadFactory threadFactory, RejectedExecutionHandler rejectedExecutionHandler) {
return new ThreadPoolExecutor(corePoolSize, maxPoolSize,
keepAliveSeconds, TimeUnit.SECONDS, queue, threadFactory, rejectedExecutionHandler);
}
}
- Mybaties的SqlSessionFactory。