二、架構師內功心法之設計模式
2.架構師內功心法之設計模式
2.1.課程目標
1、通過對本章內容的學習,了解設計模式的由來。
2、介紹設計模式能幫我們解決哪些問題。
3、剖析工廠模式的歷史由來及應用場景。
2.2.內容定位
不用設計模式並非不可以,但是用好設計模式能幫助我們更好地解決實際問題,設計模式最重要的
是解耦。設計模式天天都在用,但自己卻無感知。我們把設計模式作為一個專題,主要是學習設計模式
是如何總結經驗的,把經驗為自己所用。學設計模式也是鍛煉將業務需求轉換技術實現的一種非常有效
的方式。
2.3.回顧軟件設計原則
設計原則 | 解釋 |
---|---|
開閉原則 | 對擴展開放,對修改關閉 |
依賴倒置原則 | 通過抽象使各個類或者模塊不相互影響,實現松耦合。 |
單一職責原則 | 一個類、接口、方法只做一件事。 |
接口隔離原則 | 盡量保證接口的純潔性,客戶端不應該依賴不需要的接口。 |
迪米特法則 | 又叫最少知道原則,一個類對其所依賴的類知道得越少越好。 |
里氏替換原則 | 子類可以擴展父類的功能但不能改變父類原有的功能。 |
合成復用原則 | 盡量使用對象組合、聚合,而不使用繼承關系達到代碼復用的目的。 |
2.4.設計模式總覽
寫出優雅的代碼
更好地重構項目
經典框架都在用設計模式解決問題
Spring就是一個把設計模式用得淋漓盡致的經典框架,其實從類的命名就能看出來,我來一一列舉:
設計模式名稱 | 舉例 |
---|---|
工廠模式 | BeanFactory |
裝飾器模式 | BeanWrapper |
代理模式 | AopProxy |
委派模式 | DispatcherServlet |
策略模式 | HandlerMapping |
適配器模式 | HandlerAdapter |
模板模式 | JdbcTemplate |
觀察者模式 | ContextLoaderListener |
我們的課程中,會圍繞 Spring 的 IOC、AOP、MVC、JDBC
這樣的思路展開,根據其設計類型來設計講解順序:
類型 | 名稱 | 英文 |
---|---|---|
創建型模式 | 工廠模式 | Factory Pattern |
單例模式 | Singleton Pattern | |
原型模式 | Prototype Pattern | |
結構型模式 | 適配器模式 | Adapter Pattern |
裝飾器模式 | Decorator Patter | |
代理模式 | Proxy Pattern | |
行為性模式 | 策略模式 | Strategy Pattern |
模板模式 | Template Pattern | |
委派模式 | Delegate Pattern | |
觀察者模式 | Observer Pattern |
3.工廠模式詳解
3.1.工廠模式的歷史由來
原始社會自給自足(沒有工廠)、農耕社會小作坊(簡單工廠,民間酒
坊)、工業革命流水線(工廠方法,自產自銷)、現代產業鏈代工廠(抽象工廠,富士康)
3.2.簡單工廠模式
3.2.1.定義
簡單工廠模式(Simple Factory Pattern)是指由一個工廠對象決定創建出哪一種產品類的實例,
但它不屬於GOF 23種設計模式。簡單工廠適用於工廠類負責創建的對象較少的場景,且客戶端只需要
傳入工廠類的參數,對於如何創建對象的邏輯不需要關心。
3.2.2.demo
public class SimpleFactoryTest {
public static void main(String[] args) {
CourseFactory factory = new CourseFactory();
ICourse course = factory.create(JavaCourse.class);
course.record();
}
}
public class JavaCourse implements ICourse {
public void record() {
System.out.println("錄制Java課程");
}
}
public class CourseFactory {
public ICourse create(Class<? extends ICourse> clazz){
// 反射
try {
if (null != clazz) {
return clazz.newInstance();
}
}catch (Exception e){
e.printStackTrace();
}
return null;
}
}

3.2.3.源碼
-
Calendar.getInstance()
-
LoggerFactory.getLogger()
簡單工廠模式在 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;
}
還有一個大家經常使用的 logback,我們可以看到 LoggerFactory 中有多個重載的方法
getLogger():
public static Logger getLogger(String name) {
ILoggerFactory iLoggerFactory = getILoggerFactory();
return iLoggerFactory.getLogger(name);
}
public static Logger getLogger(Class clazz) {
return getLogger(clazz.getName());
}
3.2.4.優缺點
- 優點
- 簡單
- 缺點
- 工廠類的職責相對過重,不易於擴展過於復雜的產品結構。
3.3.工廠方法模式
3.3.1.定義
工廠方法模式(Factory Method Pattern)是指定義一個創建對象的接口,但讓實現這個接口的類
來決定實例化哪個類,工廠方法讓類的實例化推遲到子類中進行。在工廠方法模式中用戶只需要關心所
需產品對應的工廠,無須關心創建細節,而且加入新的產品符合開閉原則。
3.3.2.demo
public class FactoryMethodTest {
public static void main(String[] args) {
// Python課程工廠
ICourseFactory factory = new PythonCourseFactory();
ICourse course = factory.create();
course.record();
// Java課程工廠
factory = new JavaCourseFactory();
course = factory.create();
course.record();
}
}
public class JavaCourseFactory implements ICourseFactory {
public ICourse create() {
return new JavaCourse();
}
}
public interface ICourseFactory {
ICourse create();
}
public class JavaCourse implements ICourse {
public void record() {
System.out.println("錄制Java課程");
}
}
public interface ICourse {
void record();
}

3.3.3.源碼
ApplicationContext就是工廠方法模式
再來看看logback中工廠方法模式的應用,看看類圖就OK了:

3.3.4.優缺點
- 工廠方法適用於以下場景:
- 創建對象需要大量重復的代碼。
- 客戶端(應用層)不依賴於產品類實例如何被創建、實現等細節。
- 一個類通過其子類來指定創建哪個對象。
- 工廠方法也有缺點:
- 類的個數容易過多,增加復雜度。
- 增加了系統的抽象性和理解難度。
3.4.抽象工廠模式
3.4.1.定義
抽象工廠模式(AbastractFactory Pattern)是指提供一個創建一系列相關或相互依賴對象的接口,無須指定他們具體的類。客戶端(應用層)不依賴於產品類實例如何被創建、實現等細節,強調的是一
系列相關的產品對象(屬於同一產品族)一起使用創建對象需要大量重復的代碼。需要提供一個產品類
的庫,所有的產品以同樣的接口出現,從而使客戶端不依賴於具體實現。
講解抽象工廠之前,我們要了解兩個概念產品等級結構和產品族,看下面的圖:

從上圖中看出有正方形,圓形和菱形三種圖形,相同顏色深淺的就代表同一個產品族,相同形狀的代表
同一個產品等級結構。同樣可以從生活中來舉例,比如,美的電器生產多種家用電器。那么上圖中,顏
色最深的正方形就代表美的洗衣機、顏色最深的圓形代表美的空調、顏色最深的菱形代表美的熱水器,
顏色最深的一排都屬於美的品牌,都是美的電器這個產品族。再看最右側的菱形,顏色最深的我們指定
了代表美的熱水器,那么第二排顏色稍微淺一點的菱形,代表海信的熱水器。同理,同一產品結構下還
有格力熱水器,格力空調,格力洗衣機。
再看下面的這張圖,最左側的小房子我們就認為具體的工廠,有美的工廠,有海信工廠,有格力工廠。
每個品牌的工廠都生產洗衣機、熱水器和空調。

3.4.2.demo
public class AbstractFactoryTest {
public static void main(String[] args) {
JavaCourseFactory factory = new JavaCourseFactory();
factory.createNote().edit();
factory.createVideo().record();
}
}
/**
* 抽象工廠CourseFactory類:
* 抽象工廠是用戶的主入口
* 在Spring中應用得最為廣泛的一種設計模式
* 易於擴展
*/
public abstract class CourseFactory {
public void init(){
System.out.println("初始化基礎數據");
}
protected abstract INote createNote();
protected abstract IVideo createVideo();
}
/**
* 創建Java產品族的具體工廠JavaCourseFactory
*/
public class JavaCourseFactory extends CourseFactory {
public INote createNote() {
super.init();
return new JavaNote();
}
public IVideo createVideo() {
super.init();
return new JavaVideo();
}
}
/**
* 創建Java產品族,Java視頻JavaVideo類:Java視頻
*/
public class JavaVideo implements IVideo {
public void record() {
System.out.println("錄制Java視頻");
}
}
/**
* 錄播視頻:IVideo接口
*/
public interface IVideo {
void record();
}
/**
* 擴展產品等級Java課堂筆記JavaNote類:Java筆記
*/
public class JavaNote implements INote {
public void edit() {
System.out.println("編寫Java筆記");
}
}
/**
* 課堂筆記:INote接口
*/
public interface INote {
void edit();
}
// 創建Python產品族的具體工廠PythonCourseFactory省略。。。

上面的代碼完整地描述了兩個產品族Java課程和Python課程,也描述了兩個產品等級視頻和手記。抽象工廠非常完美清晰地描述這樣一層復雜的關系。但是,不知道大家有沒有發現,如果我們再繼續擴展
產品等級,將源碼 Source也加入到課程中,那么我們的代碼從抽象工廠,到具體工廠要全部調整,很顯然不符合開閉原則。
3.4.3.源碼
AbstractFactory
AnnotationApplicationContext
Xml
適合長時間不變動的場景
3.4.3.優缺點
抽象工廠缺點
- 規定了所有可能被創建的產品集合,產品族中擴展新的產品困難,需要修改抽象工廠的接口。
- 增加了系統的抽象性和理解難度。
3.5.簡單工廠 vs 工廠方法 vs 抽象工廠
簡單工廠:產品的工廠
工廠方法:工廠的工廠
抽象工廠:復雜產品的工廠
簡單工廠:工廠是一個實體類,內部直接根據邏輯創建對應的產品。
工廠方法:工廠首先有個接口定義規范。不同的產品使用不同的實體類工廠根據規范和需求創建對應的產品。這就是它們的區別。
工廠方法是生產一類產品,抽象工廠是生產一個產品族
3.6.作業
1、工廠類一定需要將構造方法私有化嗎,為什么?
不一定。抽象工廠類就不能,否則父類的私有構造方法就不能被子類調用。
2、用工廠模式設計支付業務場景,包含跨境支付,支付寶、微信、銀聯支付,並畫出類圖。
/**
* description: 支付接口
*/
public interface IPay {
/**
* 支付方法
*/
void pay();
}
/**
* description: 支付寶支付
*/
public class AliPay implements IPay {
public void pay() {
System.out.println("支付寶支付");
}
}
/**
* description: 微信支付
*/
public class WxPay implements IPay {
public void pay() {
System.out.println("微信支付");
}
}
/**
* description: 銀聯支付
*/
public class UniPay implements IPay {
public void pay() {
System.out.println("銀聯支付");
}
}
/**
* description: 蘋果支付
*/
public class ApplePay implements IPay {
public void pay() {
System.out.println("蘋果支付");
}
}
/**
* description: 支付抽象工廠
*/
public abstract class AbstractPayFactory {
public void init() {
System.out.println("初始化基礎數據");
}
}
/**
* description: 國內支付
*/
public class ChinaPayFactory extends AbstractPayFactory {
protected IPay createAliPay() {
super.init();
return new AliPay();
}
protected IPay createWxPay() {
super.init();
return new WxPay();
}
protected IPay createUniPay() {
super.init();
return new UniPay();
}
}
/**
* description: 國外支付
*/
public class ForeignPayFactory extends AbstractPayFactory {
protected IPay createApplePay() {
super.init();
return new ApplePay();
}
}
/**
* description: 抽象工廠方法測試
*/
public class AbstractPayFactoryTest {
public static void main(String[] args) {
ChinaPayFactory chinaPayFactory = new ChinaPayFactory();
chinaPayFactory.createAliPay().pay();
chinaPayFactory.createWxPay().pay();
chinaPayFactory.createUniPay().pay();
ForeignPayFactory foreignPayFactory = new ForeignPayFactory();
foreignPayFactory.createApplePay().pay();
}
}
