二、設計模式總覽及工廠模式詳解


二、架構師內功心法之設計模式

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.優缺點

  • 工廠方法適用於以下場景:
    1. 創建對象需要大量重復的代碼。
    2. 客戶端(應用層)不依賴於產品類實例如何被創建、實現等細節。
    3. 一個類通過其子類來指定創建哪個對象。
  • 工廠方法也有缺點:
    1. 類的個數容易過多,增加復雜度。
    2. 增加了系統的抽象性和理解難度。

3.4.抽象工廠模式

3.4.1.定義

抽象工廠模式(AbastractFactory Pattern)是指提供一個創建一系列相關或相互依賴對象的接口,無須指定他們具體的類。客戶端(應用層)不依賴於產品類實例如何被創建、實現等細節,強調的是一
系列相關的產品對象(屬於同一產品族)一起使用創建對象需要大量重復的代碼。需要提供一個產品類
的庫,所有的產品以同樣的接口出現,從而使客戶端不依賴於具體實現。

講解抽象工廠之前,我們要了解兩個概念產品等級結構產品族,看下面的圖:

image-20200225191403439

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

再看下面的這張圖,最左側的小房子我們就認為具體的工廠,有美的工廠,有海信工廠,有格力工廠
每個品牌的工廠都生產洗衣機熱水器空調

image-20200225191741688

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.優缺點

抽象工廠缺點

  1. 規定了所有可能被創建的產品集合,產品族中擴展新的產品困難,需要修改抽象工廠的接口。
  2. 增加了系統的抽象性和理解難度。

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();
    }
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM