【設計模式】工廠模式

工廠模式是一種創建型設計模式。

開閉原則

開閉原則是設計模式原則之一,倡導實體應當對擴展開發,對修改關閉。
意思是說,你想要擴展或者修改已寫好代碼的功能,可以往裏面加入新的代碼,但不允許修改修改以前已經寫好的代碼
高耦合將會導致,拓展功能時,不得不修改以前的代碼。
比如

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。
發佈了37 篇原創文章 · 獲贊 12 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章