二、架構師内功心法之設計模式
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();
}
}