HeadFirst設計模式讀書筆記之工廠模式
1. 簡單工廠
1. 你開了一家披薩店,點披薩的方法可能是這樣:
public Pizza orderPizza(String type) { Pizza pizza; if (type.equals("芒果披薩")){ pizza = new MangoPizza(); }else if (type.equals("核桃披薩")){ pizza = new WalnutPizza(); }else if (type.equals("橙皮披薩")){ pizza = new FlavedoPizza(); }else { pizza = new StarPizza(); } pizza.prepare(); pizza.bake(); pizza.cut(); pizza.box(); return pizza; }
可以看到,每當你想增加一種披薩型別,就要修改程式碼,新增一種if else條件.當有多個系統存在orderPizza的需求時,每個系統都要同時修改他們的程式碼.因此,需要將這種例項化具體物件的程式碼封裝起來.
public class PizzaStore { SimplePizzaFactory factory; public PizzaStore(SimplePizzaFactory factory){ this.factory = factory; } public Pizza orderPizza(String type) { Pizza pizza; pizza = factory.createPizza(type); pizza.prepare(); pizza.bake(); pizza.cut(); pizza.box(); return pizza; } }
public class SimplePizzaFactory { public Pizza createPizza(String type){ Pizza pizza = null; if (type.equals("芒果披薩")){ pizza = new MangoPizza(); }else if (type.equals("核桃披薩")){ pizza = new WalnutPizza(); }else if (type.equals("橙皮披薩")){ pizza = new FlavedoPizza(); }else { pizza = new StarPizza(); } return pizza; } }
這就是簡單工廠方法,他不算一種設計模式,而是一種程式設計習慣.其要點有二:
1. 將產品定義為抽象類,可以被覆蓋
2. 將具體的產品放到工廠類中來例項化,由工廠建立並返回給客戶,這樣便使得產品的建立邏輯可以解耦合,並增加了複用性
2. 工廠方法
1. 假設你現在有了很多加盟店,每種加盟店根據地區的差異有自己的工廠,但加盟店雖然採用工廠方法建立披薩,但其他部分卻有所不同:烘烤的做法,不切片,使用雜七雜八的盒子...
這時,你想把建立披薩的方法與每家披薩店繫結在一起,讓每家PizzaStore中都使用createPizza()建立pizza,並且這些店還可以擁有一定的製作披薩的自主權.因此,我們需要把PizzaStore和createPizza()定義成抽象的
/**
* 抽象的PizzaStore
*/
public abstract class PizzaStore {
public Pizza orderPizza(String type) {
Pizza pizza;
// 使用自己的方法做披薩
pizza = createPizza(type);
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
// 將做披薩的工廠方法放到PizzaStore中,工廠方法是抽象的,所以依賴子類來處理物件的建立
public abstract Pizza createPizza(String type);
}
2. 現在由PizzaStore的子類來具體賣披薩
public class BeijingPizzaStore extends PizzaStore {
@Override
public Pizza createPizza(String item) {
if (item.equals("橙皮披薩")){
return new FlavedoPizza();
}else {
return new PekingDuckPizza();
}
}
}
public class ChengduPizza extends PizzaStore {
@Override
public Pizza createPizza(String type) {
if (type.equals("芒果披薩")){
return new MangoPizza();
}else {
return new StarPizza();
}
}
}
現在,orderPizza()與具體的Pizza實現了一定的解耦合,決定最終Pizza物件的變成了某個具體的pizza店,而pizza店則由顧客來決定
3. 一些對工廠方法的理解:
1. 工廠方法是抽象的,所以依賴子類來建立物件,這樣便將超類的程式碼與具體建立產品的程式碼分割開了
2. 工廠方法必須返回一個產品,超類通常中會用到這個產品
3. 工廠方法將產品與建立者抽象為介面,使他們成為平級的存在,具體的產品建立則使用子類來互動
4. 工廠方法將產品例項化的操作推遲到子類,子類的選擇則由建立者來決定
5. 簡單工廠方法更趨向組合,工廠方法則利用抽象和繼承
6. 簡單工廠封裝了物件的建立,一個工廠處理所有的產品,但對同一個產品不可變更其做法,工廠方法則更有彈性,子類通過重寫可以更改產品的建立
3. 依賴倒置原則
下面是一個簡單粗暴的披薩店:
/**
* 依賴具體物件的PizzaStore
*/
public class PizzaStore {
public Pizza createPizza(String style, String type){
Pizza pizza = null;
if (style.equals("一元檔")){
if (type.equals("芒果披薩")){
pizza = new MangoPizza();
}else if (type.equals("核桃披薩")){
pizza = new WalnutPizza();
}
}else if (style.equals("二元檔")){
if (type.equals("北京烤鴨披薩")){
pizza = new PekingDuckPizza();
}else if (type.equals("橙皮披薩")){
pizza = new FlavedoPizza();
}
}else {
System.out.println("披薩賣完了");
}
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
}
可以看到,這個披薩店依賴於每一個具體的披薩物件,每增加一個披薩就要修改一次程式碼,增加一個依賴,可以說是非常不好用了
從圖中看到,高層元件直接依賴於低層元件而依賴倒置原則的的內容就是:
要依賴抽象,不要依賴具體類
這個原則說明不管高層還是低層元件都不應該依賴具體類,而應該依賴於抽象.如圖所示:
與之前相比高層元件PizzaStore不在直接依賴於具體的Pizza物件,而是依賴於Pizza介面,而具體的Pizza產品則由被高層元件依賴顛倒成了依賴Pizza介面,這就是一種依賴倒置.總體來看,PizzaStore和Pizza都抽象化了,不用再依賴於具體的型別,符合依賴倒置的設計原則.
有三個指導方針可以讓你更好地遵守依賴倒置原則:
- 變數不可以持有具體類的引用
- 不要讓類派生自具體類
- 不要覆蓋基類中已覆蓋的方法
4. 抽象工廠方法:建立產品家族
與普通的工廠方法比較,抽象工廠方法要顯得重型得多: 工廠方法只建立一種產品,抽象工廠方法則建立一個家族的產品
1. 典型做法是建立一個工廠的介面,這個介面負責建立所有的產品家族成員,如建立一個生產調料家族的工廠:
/**
* 原料工廠的介面,實現這樣的介面,可以隨意組合產出各式各樣的調料
* 每個原料都是一個類
*/
public interface SeasoningFactory {
Chili createChili();
DouBan createDouBan();
Pepper createPepper();
Salt createSalt();
}
/**
* 郫縣調料生產廠
*/
public class PiXianSeasoningFactory implements SeasoningFactory {
/**
* 虎皮青椒
* @return
*/
@Override
public Chili createChili() {
return new HuPiQingJiao();
}
/**
* 郫縣豆瓣
* @return
*/
@Override
public DouBan createDouBan() {
return new PiXianDouBan();
}
/**
* 花椒油
* @return
*/
@Override
public Pepper createPepper() {
return new HuaJiaoYou();
}
/**
* 食用鹽
* @return
*/
@Override
public Salt createSalt() {
return new ShiYongYan();
}
}
/**
* 灌縣調料生產廠
*/
public class GuanXianSeasoningFactory implements SeasoningFactory {
/**
* 虎皮青椒
* @return
*/
@Override
public Chili createChili() {
return new HuPiQingJiao();
}
/**
* 老乾媽豆瓣醬
* @return
*/
@Override
public DouBan createDouBan() {
return new LaoGanMaDouBanJiang();
}
/**
* 花椒油
* @return
*/
@Override
public Pepper createPepper() {
return new HuaJiaoYou();
}
/**
* 食用鹽
* @return
*/
@Override
public Salt createSalt() {
return new ShiYongYan();
}
}
下面是抽象工廠方法的類圖:
從類圖中可以看出來,抽象工廠方法有一個缺點,就是當添加了一個新產品介面時,要去抽象工廠介面中新增一個方法,這會造成一些麻煩
2. 工廠方法與抽象工廠的一些對比
相同點:
- 都是負責建立物件
- 都能使程式解耦,將程式從建立大量具體物件的泥潭中拉出來
不同點:
- 工廠方法用子類來建立具體物件,利用抽象和繼承.抽象工廠包含了一大堆介面,利用組合的思想來建立物件
- 工廠方法一般用來建立一種產品,抽象工廠則建立一族產品
工廠方法功能簡單,使用輕便.抽象工廠功能強大,使用時會建立大量的類
3. 下面使用抽象工廠生產的原料來製作披薩
/**
* 原料工廠的介面,實現這樣的介面,可以隨意組合產出各式各樣的調料
* 每個原料都是一個類
*/
public interface SeasoningFactory {
/**
* 辣椒
* @return
*/
Chili createChili();
/**
* 豆瓣
* @return
*/
DouBan createDouBan();
/**
* 花椒
* @return
*/
Pepper createPepper();
/**
* 鹽
* @return
*/
Salt createSalt();
}
/**
* 豆瓣
*/
public abstract class DouBan {
public abstract String sayMyName();
}
public class PiXianDouBan extends DouBan {
@Override
public String sayMyName() {
return "郫縣豆瓣";
}
}
public class LaoGanMaDouBanJiang extends DouBan {
@Override
public String sayMyName() {
return "老乾媽豆瓣醬";
}
}
/**
* 郫縣調料生產廠
*/
public class PiXianSeasoningFactory implements SeasoningFactory {
/**
* 虎皮青椒
* @return
*/
@Override
public Chili createChili() {
return new HuPiQingJiao();
}
/**
* 郫縣豆瓣
* @return
*/
@Override
public DouBan createDouBan() {
return new PiXianDouBan();
}
/**
* 花椒油
* @return
*/
@Override
public Pepper createPepper() {
return new HuaJiaoYou();
}
/**
* 食用鹽
* @return
*/
@Override
public Salt createSalt() {
return new ShiYongYan();
}
}
/**
* 灌縣調料生產廠
*/
public class GuanXianSeasoningFactory implements SeasoningFactory {
/**
* 虎皮青椒
* @return
*/
@Override
public Chili createChili() {
return new HuPiQingJiao();
}
/**
* 老乾媽豆瓣醬
* @return
*/
@Override
public DouBan createDouBan() {
return new LaoGanMaDouBanJiang();
}
/**
* 花椒油
* @return
*/
@Override
public Pepper createPepper() {
return new HuaJiaoYou();
}
/**
* 食用鹽
* @return
*/
@Override
public Salt createSalt() {
return new ShiYongYan();
}
}
/**
* 每一個披薩都有一組抽象工廠生產的原料
*
*/
public abstract class Pizza {
protected String name;
/**
* 辣椒
*/
protected Chili chili;
/**
* 豆瓣
*/
protected DouBan douBan;
/**
* 花椒
*/
protected Pepper pepper;
/**
* 鹽
*/
protected Salt salt;
/**
* 在這個方法中,收集抽象工廠生產的原料
*/
public abstract void prepare();
public void bake(){
System.out.println("烘烤...");
}
public void cut(){
System.out.println("切片...");
}
public void box() {
System.out.println("包裝...");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString(){
return "披薩製作完畢:"+name
+"\n生產原料:"+chili.sayMyName()
+"\t"+douBan.sayMyName()
+"\t"+pepper.sayMyName()
+"\t"+salt.sayMyName();
}
}
public class ChengduPizza extends Pizza{
// 調料工廠
SeasoningFactory seasoningFactory;
public ChengduPizza(SeasoningFactory seasoningFactory) {
this.name = "舊成都披薩";
this.seasoningFactory = seasoningFactory;
}
@Override
public void prepare() {
System.out.println("開始準備 "+getName()+":");
chili = seasoningFactory.createChili();
douBan = seasoningFactory.createDouBan();
pepper = seasoningFactory.createPepper();
salt = seasoningFactory.createSalt();
System.out.println("以下原料準備完畢:"+chili.sayMyName()
+","+douBan.sayMyName()+","+pepper.sayMyName()+","+salt.sayMyName());
}
}
public abstract class PizzaStore {
public abstract Pizza createPizza(String type);
}
public class StarBoundPizzaStore extends PizzaStore {
@Override
public Pizza createPizza(String type) {
Pizza pizza;
SeasoningFactory factoryA = new PiXianSeasoningFactory();
SeasoningFactory factoryB = new GuanXianSeasoningFactory();
if (type.equals("舊成都披薩")){
pizza = new ChengduPizza(factoryA);;
}else if (type.equals("新北京披薩")){
pizza = new PekingPizza(factoryB);
}else {
pizza = new ChengduPizza(factoryB);
}
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
public static void main(String[] args) {
PizzaStore pizzaStore = new StarBoundPizzaStore();
Pizza pizza = pizzaStore.createPizza("舊成都披薩");
System.out.println(pizza.toString());
Pizza pizza1 = pizzaStore.createPizza("新北京披薩");
System.out.println(pizza1.toString());
}
}
結果:
開始準備 舊成都披薩:
以下原料準備完畢:虎皮青椒,郫縣豆瓣,花椒油,食用鹽
烘烤...
切片...
包裝...
披薩製作完畢:舊成都披薩
生產原料:虎皮青椒 郫縣豆瓣 花椒油 食用鹽
開始準備 新北京披薩:
以下原料準備完畢:虎皮青椒,老乾媽豆瓣醬,花椒油,食用鹽
烘烤...
切片...
包裝...
披薩製作完畢:新北京披薩
生產原料:虎皮青椒 老乾媽豆瓣醬 花椒油 食用鹽