更多知識,請移步我的小破站:http://hellofriend.top
什麼是工廠設計模式?
對象都需要創建,如果創建的時候直接 new
該對象,就會對該對象嚴重耦合。
假如我們要更換對象,所有 new
對象的地方都需要修改一遍,顯然違背了軟件設計的開閉原則(OCP)。如果我們使用工廠來生產對象,我們就只和工廠打交道就可以了,徹底和對象解耦,如果要更換對象,直接在工廠裏更換該對象即可,達到了與對象解耦的目的。
工廠模式最大的優點就是:解耦。
三種工廠設計模式的使用
1. 簡單工廠設計模式
定義:一個工廠方法,依據傳入的參數,生成對應的產品對象;
角色:
- 抽象產品類
- 具體產品類
- 具體工廠類
使用說明:
- 先將產品類抽象出來,比如,蘋果和梨都屬於水果。
- 抽象出來一個水果類 Fruit,蘋果和梨就是具體的產品類。
- 然後創建一個水果工廠,分別用來創建蘋果和梨。
抽象水果類
public abstract class Fruit {
abstract void getName();
}
具體類 蘋果
public class Apple extends Fruit {
@Override
public void getName() {
System.out.println("Apple");
}
}
具體類 梨
public class Pear extends Fruit {
@Override
public void getName() {
System.out.println("Pear");
}
}
水果工廠
public class FruitFactory {
public Fruit createFruit(String type) {
System.out.println("======SimpleFactory======");
if (type.equals("apple")) {// 生產蘋果
return new Apple();
} else if (type.equals("pear")) {// 生產梨
return new Pear();
}
return null;
}
}
簡單工廠使用
public class SimpleFactory {
public static void main(String[] args) {
FruitFactory Factory = new FruitFactory();
Apple apple = (Apple) Factory.createFruit("apple");// 獲得蘋果
apple.getName();
Pear pear = (Pear) Factory.createFruit("pear");// 獲得梨
pear.getName();
}
}
小結:
- 一個簡單工廠設計模式就完成了,但是有問題。如果我想喫香蕉,想喫橘子。這種方式,每當我想添加一種水果,就必然要修改工廠類,這顯然也違反了開閉原則,亦不可取;所以簡單工廠只適合於產品對象較少,且產品固定的需求,對於產品變化無常的需求來說不合適。
2. 工廠方法設計模式
定義:將工廠提取成一個接口或抽象類,具體生產什麼產品由子類決定。(具體工廠生產具體類)
角色:
- 抽象產品類
- 具體產品類
- 抽象工廠類
- 具體工廠類
和上例中一樣,產品類抽象出來,此例把工廠類也抽象出來,工廠方法模式將對象的實例化推遲到子類。
水果抽象類 蘋果類和梨類 代碼和上例一樣,此處省略。
抽象工廠類
public abstract class FruitFactory {
public abstract Fruit createFruit();//生產水果
}
具體工廠 蘋果工廠
public class AppleFactory extends FruitFactory {
@Override
public Fruit createFruit() {
return new Apple();
}
}
具體工廠 梨工廠
public class PearFactory extends FruitFactory {
@Override
public Fruit createFruit() {
return new Pear();
}
}
工廠方法使用
public class FactoryMethod {
public static void main(String[] args) {
System.out.println("======FactoryMethod======");
AppleFactory appleFactory = new AppleFactory();
PearFactory pearFactory = new PearFactory();
Apple apple = (Apple) appleFactory.createFruit();// 獲得蘋果
apple.getName();
Pear pear = (Pear) pearFactory.createFruit();// 獲得梨
pear.getName();
}
}
小結:
- 以上這種方式,雖然解耦了,也遵循了開閉原則,但是問題根本還是沒有解決啊,換湯沒換藥,如果我需要的產品很多的話,需要創建非常多的工廠,所以這種方式的缺點也很明顯。
3. 抽象工廠設計模式
定義:爲創建一組相關或者是相互依賴的對象提供的一個接口,而不需要指定它們的具體類。
角色:
- 抽象產品類
- 具體產品類
- 抽象工廠類
- 具體工廠類
抽象工廠和工廠方法的模式基本一樣,區別在於,工廠方法是生產一個具體的產品,而抽象工廠可以用來生產一組相同,有相對關係的產品;重點在於一組,一批,一系列。
舉個例子,假如生產 Iphone 手機,Iphone 手機有很多系列,Iphone8、IphoneXS等;假如Iphone8生產需要 CPU A11的處理器,LCD屏幕,而 IphoneXS 需要 CPU A12 的處理器和 OLED 屏幕;用抽象工廠來實現:
CPU 抽象類和實現類
public abstract class CPU {
public abstract void run();
}
public class A11 extends CPU {
@Override
public void run() {
System.out.println("A11");
}
}
public class A12 extends CPU {
@Override
public void run() {
System.out.println("A12");
}
}
屏幕 抽象類和實現類
public abstract class Screen {
public abstract void isScreen();
}
public class LCD extends Screen {
@Override
public void isScreen() {
System.out.println("LCD");
}
}
public class OLED extends Screen {
@Override
public void isScreen() {
System.out.println("OLED");
}
}
手機工廠接口
public interface PhoneFactory {
CPU getCpu();// 使用的cpu
Screen getScreen();// 使用的屏幕
}
手機工廠實現
public class Iphone8Factory implements PhoneFactory {
@Override
public CPU getCpu() {
return new A11();
}
@Override
public Screen getScreen() {
return new LCD();
}
}
public class IphoneXsFactory implements PhoneFactory {
@Override
public CPU getCpu() {
return new A12();
}
@Override
public Screen getScreen() {
return new OLED();
}
}
抽象工廠使用
public class AbstractFactory {
public static void main(String[] args) {
Iphone8Factory iphone8Factory = new Iphone8Factory();
IphoneXsFactory iphoneXSFactory = new IphoneXsFactory();
System.out.println("======iphone8Factory=====");
CPU cpuA11 = iphone8Factory.getCpu();
cpuA11.run();
Screen screenLCD = iphone8Factory.getScreen();
screenLCD.isScreen();
System.out.println("======iphoneXsFactory=====");
CPU cpuA12 = iphoneXSFactory.getCpu();
cpuA12.run();
Screen screenOLED = iphoneXSFactory.getScreen();
screenOLED.isScreen();
}
}
小結:
- 以上例子可以看出,抽象工廠可以解決一系列的產品生產的需求,對於大批量,多系列的產品,用抽象工廠可以更好的管理和擴展。
三種工廠方式總結
- 對於簡單工廠和工廠方法來說,兩者的使用方式實際上是一樣的,如果對於產品的分類和名稱是確定的,數量是相對固定的,推薦使用簡單工廠模式。
- 抽象工廠用來解決相對複雜的問題,適用於一系列、大批量的對象生產。
JDK 中工廠設計模式的應用 Calendar 類
下面是 JDK 的源碼:
Calendar.getInstance();
public static Calendar getInstance(){
return createCalendar(TimeZone.getDefault(), Locale.getDefault(Locale.Category.FORMAT));
}
public static Locale getDefault(Locale.Category category) {
// do not synchronize this method - see 4071298
switch (category) {
case DISPLAY:
if (defaultDisplayLocale == null) {
synchronized(Locale.class) {
if (defaultDisplayLocale == null) {
defaultDisplayLocale = initDefault(category);
}
}
}
return defaultDisplayLocale;
case FORMAT:
if (defaultFormatLocale == null) {
synchronized(Locale.class) {
if (defaultFormatLocale == null) {
defaultFormatLocale = initDefault(category);
}
}
}
return defaultFormatLocale;
default:
assert false: "Unknown Category";
}
return getDefault();
}
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;
}