前言
今天開始我們專題的第三課了,開始對設計模式進行講解,本章節介紹:了解設計模式的由來,介紹設計模式能幫我們解決那些問題以及剖析工廠模式的曆史由來及應用場景。本章節參考資料書籍《Spring 5核心原理》中的第一篇 Spring 内功心法(Spring中常用的設計模式)(沒有電子檔,都是我取其精華并結合自己的了解,一個字一個字手敲出來的)。
回顧軟體設計原則
在講設計模式之前,我們一定要先了解軟體設計原則。現在先來回顧一下軟體設計七大原則:
開閉原則:對擴充開放,對修改關閉。
依賴倒置原則:通過抽象使各個類或者子產品不互相影響,實作松耦合。
單一職責原則:一個類、接口、方法隻做一件事。
接口隔離原則:盡量保證接口的純潔性,用戶端不應該依賴不需要的接口。
迪米特法則:又叫最少知道原則,一個類對其所依賴的類知道得越少越好。
裡氏替換原則:子類可以擴充父類的功能但不能改變父類原有的功能。
合成複用原則:盡量使用對象組合、聚合,而不使用繼承關系達到代碼複用的目的。
為什麼要從設計模式開始
平時我們寫的代碼雖然滿足了需求但往往不利于項目的開發與維護,通過合理運用設計模式,可以讓我們以後維護更友善,是以學會對代碼的重構是非常重要的。Spring中對設計模式運用可謂是淋漓盡緻,比如:
工廠模式:BeanFactory
裝飾器模式:BeanWrapper
代理模式:AopProxy
委派模式:DispatcherServlet
政策模式:HandlerMapping
擴充卡模式:HandlerAdapter
模闆模式:JdbcTemplate
觀察者模式:ContextLoaderListener
需要特别聲明的是,設計模式從來都不是單個設計模式獨立使用的。在實際應用中,通常是多個設計模式混合使用, 你中有我, 我中有你。
工廠模式詳解
簡單工廠模式
簡單工廠模式(Simple Factory Pattern)是指由一個工廠對象決定建立出哪一種産品類的執行個體,但它不屬于GOF,23種設計模式。簡單工廠适用于工廠類負責建立的對象較少的場景,且用戶端隻需要傳入對應的參數,工廠就生産對應的對象,對于如何建立對象的邏輯調用方不需要關心。
我們以種植水果為例,先建立種植水果接口IFruit:
public interface IFruit {
/**
* 種植水果方法
*/
void plant();
}
//實作種植蘋果
public class Apple implements IFruit {
public void plant() {
System.out.println("種植蘋果");
}
//實作種植橙子
public class Orange implements IFruit {
public void plant() {
System.out.println("種植橙子");
}
我們再看下調用方代碼,當想種植某一種水果時候:
public static void main(String[] args) {
//種植蘋果
IFruit fruit = new Apple();
fruit.plant();
如果此時又要換成種植橙子:
//IFruit fruit = new Apple();
//種植橙子
IFruit fruit = new Orange();
fruit.plant();
父類IFruit指向子類Apple的引用,調用方代碼需要依賴Aplle,這樣随着種植水果的業務越來越多了,調用方的代碼就會變得越來越臃腫了。我們要想辦法把這種依賴減弱,把建立細節隐藏起來。雖然目前代碼中,我們建立的的對象并不複雜,但是從打碼設計角度來講不易擴充。我們用簡單工廠對代碼進行優化。
建立PlantFruitsFactory工廠:
public static class PlantFruitsFactory {
public IFruit PlantFruit(String fruitName) {
//這裡使用的if判斷,用switch一樣的道理
if ("Apple".equals(fruitName)){
return new Apple();
}else if ("Orange".equals(fruitName)){
return new Orange();
}else {
return null;
}
}
修改調用方代碼:
public class DemoTest {
public static void main(String[] args) {
IFruit fruit = PlantFruitsFactory.PlantFruit("Apple");
fruit.plant();
}
下面我們看下類圖:

調用方不再直接關心建立細節,隻需要傳入指定參數給工廠,工廠負責建立對應的對象給調用方。雖然基本實作了工廠模式的邏輯,但是如果随着業務的擴充,需要建立種植更多品種的水果時,工廠方法每次都需要跟着修改,不符合開閉原則。是以我們還可以再優化下工廠類:
public class PlantFruitsFactory {
//包路徑的字首
private static final String PACKAGE_PATH = "com.study.demo.";
public static IFruit PlantFruit(String fruitName){
try {
return (IFruit) Class.forName(PACKAGE_PATH+fruitName).newInstance();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
這樣即使有新需求添加新的水果品種,我們也不需要修改工廠類的代碼了,隻需要建立一個對應的類,工廠會根據反射建立對應的對象給調用方了。(這裡其實還可以優化,可以将方法的入參改為對應的Class對象,這樣就不需要強轉了,這裡就不做示範了。)
簡單工廠模式在JDK源碼也是無處不在,現在我們來舉個例子,例如Calendar類,看
Calendar.getInstance()方法,下面打開的是Calendar的具體建立類:
//源碼中的方法
private static Calendar createCalendar(TimeZone zone,
Locale aLocale) {
CalendarProvider provider =
LocaleProviderAdapter.getAdapter(CalendarProvider.class, aLocale)
.getCalendarProvider();
if (provider != null) {
try {
return provider.getInstance(zone, aLocale);
} catch (IllegalArgumentException iae) {
// fall back to the default instantiation
}
}
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 no known calendar type is explicitly specified,
// perform the traditional way to create a Calendar:
// create a BuddhistCalendar for th_TH locale,
// a JapaneseImperialCalendar for ja_JP_JP locale, or
// a GregorianCalendar for any other locales.
// NOTE: The language, country and variant strings are interned.
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;
}
當然工廠模式也有他的缺點:工廠類的職責相對過重,不易于擴充過于複雜的産品結構。
工廠方法模式
工廠方法模式(FatoryMethod Pattern)是指定義一個建立對象的接口,但讓實作這個接口的類來決定執行個體化哪個類,工廠方法讓類的執行個體化推遲到子類中進行。在工廠方法模式中使用者隻需要關心所需産品對應的工廠,無須關心建立細節,而且加入新的産品符合開閉原則。
工廠方法模式主要解決産品擴充的問題,在簡單工廠中,随着産品鍊的豐富,如果每個課程的建立邏輯有差別的話,工廠的職責會變得越來越多,有點像萬能工廠,并不便于維護。根據單一職責原則我們将職能繼續拆分,專人幹專事。Apple由Apple工廠建立,Orange由 Orange工廠建立,對工廠本身也做一個抽象。來看代碼,先建立IFruitFactory接口:
//工廠接口
public interface IFruitFactory {
IFruit create();
//生成蘋果的工廠
public class AppleFactory implements IFruitFactory {
@Override
public IFruit create() {
return new Apple();
}
//生成橙子的工廠
public class OrangeFactory implements IFruitFactory {
@Override
public IFruit create() {
return new Orange();
}
//調用方代碼
public static void main(String[] args) {
//建立蘋果工廠 生産蘋果
IFruitFactory fruitFactory = new AppleFactory();
fruitFactory.create().plant();
//建立橙子工廠 生成橙子
IFruitFactory orangeFactory = new OrangeFactory();
orangeFactory.create().plant();
}
現在我們看下類圖:
工廠方法适用于以下場景:
1、建立對象需要大量重複的代碼。
2、用戶端(應用層)不依賴于産品類執行個體如何被建立、實作等細節。
3、一個類通過其子類來指定建立哪個對象。
工廠方法也有缺點:
1、類的個數容易過多,增加複雜度。
2、增加了系統的抽象性和了解難度。
抽象工廠模式
抽象工廠模式(Abastract Factory Pattern)是指提供一個建立一系列相關或互相依賴産品族産品等級結構一個産品等級結構對象的接口,無須指定他們具體的類。用戶端(應用層)不依賴于産品類執行個體如何被建立、實作等細節,強調的是一系列相關的産品對象(屬于同一産品族)一起使用建立對象需要大量重複的代碼。需要提供一個産品類的庫,所有的産品以同樣的接口出現,進而使用戶端不依賴于具體實作。
還是以水果為例,現在水果工廠不僅種植水果還開始加工水果。相當于現在的業務變更為同一個水果類不單純隻是種植水果的功能。在增加兩個接口IPlant種植水果和IProcess加工水果。
public interface IPlant {
//種植産品
void plant();
public interface IProcess {
//加工産品
void process();
然後建立一個抽象工廠FruitFactory:
/**
- 抽象工廠是使用者的主入口
- 在 Spring 中應用得最為廣泛的一種設計模式
-
易于擴充
*/
public interface FruitFactory {
IPlant createPlant();
IProcess createProcess();
然後建立蘋果産品線:
//種植蘋果
public class ApplePlant implements IPlant {
@Override
public void plant() {
System.out.println("種植蘋果");
}
//加工蘋果
public class AppleProcess implements IProcess {
@Override
public void process() {
System.out.println("加工蘋果");
}
建立蘋果工廠:
public class AppleFactory implements FruitFactory {
@Override
public IPlant createPlant() {
return new ApplePlant();
}
@Override
public IProcess createProcess() {
return new AppleProcess();
}
對應的橙子也是一樣的。
上面的代碼完整地描述了兩個産品族蘋果和橙子,也描述了兩個産品等級種植和加工。抽象工廠非常完美清晰地描述這樣一層複雜的關系。但是,不知道大家有沒有發現,如果我們再繼續擴充産品等級,将出售Sale也加入産品中,那麼我們的代碼從抽象工廠,到具體工廠要全部調整,很顯然不符合開閉原則。是以抽象工廠也是有缺點的:
1、規定了所有可能被建立的産品集合,産品族中擴充新的産品困難,需要修改抽象工廠的接口。
2、增加了系統的抽象性和了解難度。
總結
工廠模式的三種方式,沒有絕對的好壞,合适的場景使用合适的模式,實際應用中,我們千萬不能犯強迫症甚至有潔癖。在實際需求中産品等級結構更新是非常正常的一件事情。我們可以根據實際情況,隻要不是頻繁更新,可以不遵循開閉原則。代碼每半年更新一次或者每年更新一次又有何不可呢?
釘釘免費直播分享目前網際網路java架構,java進階熱門技術,由業内技術大牛,行業及實戰經驗豐富的講師進行技術分享。其中涵蓋redis/mongodb/dubbo/zookeeper/kafka 高并發、高可用、分布式、微服務技術。