时间:2025-05-02 22:23
人气:
作者:admin
《Head First设计模式》读书笔记
相关代码:Vks-Feng/HeadFirstDesignPatternNotes: Head First设计模式读书笔记及相关代码
当看到“new”就会想到“具体”
代码绑着具体类会导致代码更脆弱
Duck duck = new MallardDuck();
根据运行时条件决定实例化的具体类,有变化或扩展时需要对原有代码进行检查和修改,导致该部分更难维护和更新
Duck duck;
if (picnic) {
duck = new MallardDuck();
} else if (hunting) {
duck = new DecoyDuck();
} else if (inBathTub) {
duck = new RubberDuck();
}
“new”没有错,错在“改变”
你是一个披萨店主人
你的初始代码如下:
Pizza orderPizza() {
//为了让系统有弹性,我们希望这是一个抽象类或接口,但如果这样,无法直接实例化
Pizza pizza = new Pizza();
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
但你需要更多的披萨类型
Pizza orderPizza(String type) {
Pizza pizza = new Pizza();
// 根据披萨类型实例化正确的具体类
if (type.equals("cheese")) {
pizza = new CheesePizza();
} else if (type.equals("greek")) {
pizza = new GreekPizza();
} else if (type.equals("pepperoni")) {
pizza = new PepperoniPizza();
}
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
由于实例化具体类的缘故,当你新增或删除某些种类的披萨时,你需要修改上述的orderPizza(),这违背了“对修改关闭”的原则。
上例中不难看出,披萨的准备过程是不变的
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
而变化的部分是披萨的具体类型,即实例化特定披萨种类的部分
// 根据披萨类型实例化正确的具体类
if (type.equals("cheese")) {
pizza = new CheesePizza();
} else if (type.equals("greek")) {
pizza = new GreekPizza();
} else if (type.equals("pepperoni")) {
pizza = new PepperoniPizza();
}
所以我们需要考虑将这部分封装起来
将创建对象的部分移到orderPizza()之外
我们称这个新对象为“工厂(factory)”
/**
* 只做一件事:为客户创建披萨
*/
public class SimplePizzaFactory {
/**
* 客户用此方法实例化新对象
* @param type 披萨类型
* @return 相应类型的披萨
*/
public Pizza createPizza(String type) {
Pizza pizza = null;
if (type.equals("cheese")) {
pizza = new CheesePizza();
} else if (type.equals("pepperoni")) {
pizza = new PepperoniPizza();
} else if (type.equals("clam")) {
pizza = new ClamPizza();
} else if (type.equals(veggie)) {
pizza = new VeggiePizza();
}
return pizza;
}
}
Q:这么做的好处是什么?这似乎只是把问题从一个对象搬到另一个对象,但问题仍然存在。
A:SimplePizzaFactory可以有很多用户。把创建披萨的代码包装进一个类,当以后实现改变时,只需要修改这个类即可(而不用再找所有有这段代码的地方再去修改)。
Q:把工厂定义成静态的方法,与上述方法有何区别?
A:利用静态方法定义一个简单的工厂是很常见的技巧,被称为静态工厂。之所以使用静态方法,是因为不需要使用创建对象的方法来实例化对象(即不需要实例化工厂对象,就能获得产品)。这种方法也存在缺点,不能通过继承来改变创建方法的行为。
简单工厂其实不是一个设计模式,反而是比较像一种编程习惯。

当披萨店做大做强,出现了加盟店,而不同地区的加盟店又希望能提供不同风味的披萨……
我们可以利用SimplePizzaFactory写出多个种类的工厂,从而通过不同的工厂获得不同风味的披萨
推广SimplePizza时,你发现加盟店的确是采用了你的工厂创建披萨,但是其他部分却开始采用他们自创的流程。
解决方法时,将createPizza()方法放回PizzaStore中,但将其设置为“抽象方法”,然后为每个区域风味创建一个PizzaStore的子类
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;
}
public abstract Pizza createPizza(String type);
}
上面的PizzaStore相当于为子类提供了一套框架,各个店铺以PizzaStore为基类,实现自己的createPizza()方法

所谓“子类自己做决定”
orderPizza()在抽象的PizzaStore中定义,但是只在子类中实现具体类型,所以store并不知道是哪个子类将实际制作披萨orderPizza()对Pizza对象做了很多事情,但由于Pizza对象是抽象的,orderPizza()并不知道哪些实际的具体类参与进来了。即实现了解耦(decouple)orderPizza()调用createPizza()时,某个披萨店子类将负责创建披萨,具体的披萨类型也由披萨店来决定。abstract Product factoryMethod(String type)
工厂方法用来处理对象的创建,并将这样的行为封装在子类中。这样,客户程序中关于超类的代码和子类对象创建代码解耦了
orderPizza(),和实际创建具体产品的代码分割开来其组成元素:
创建者类

产品类

另一观点:平行的类层级

工厂方法模式定义了一个创建对象的接口,但由子类决定要实例化的类是哪一个。工厂方法让类把实例化推迟到子类。

理解“工厂方法让子类决定要实例化的类”
Q:当只有一个ConcreteCreator时,工厂方法模式有什么优点?
A:尽管只有一个具体创建者,工厂方法模式仍然很有用。因为它帮助我们将产品的“实现”从“使用”中解耦,如果增加产品或者改变产品的实现,Creator并不会受到影响。(因为Creator与任何其他ConcreteProduct之间都不是紧耦合)
Q:工厂方法和创建者是否总是抽象的?
A:不,可以定义一个默认的工厂方法来产生某些具体的产品,这么一来。即使创建者没有任何子类,依然可以创建产品
Q:简单工厂与工厂方法之间的差异
A:简单工厂把全部的事情在一个地方都处理完了,然而工厂方法却是创建一个框架,让子类决定要如何实现
反例:一个很依赖的披萨店。store需要依赖很多披萨对象

当你直接实例化一个对象时,就是在依赖他的具体类
依赖倒置原则(Dependency Inversion Principle):要依赖抽象,不要依赖具体类
通过工厂方法对上述的非常依赖的披萨店进行改进

工厂方法并非是唯一的技巧,但却是最有威力的技巧之一
依赖倒置原则中的“倒置”指的是和一般OO设计的思考方式完全相反
从上图中可以看到
倒置思考方式:
正如同许多其他原则一样,我们要做的时尽量到达此原则,而非随时都要遵循
我们已经通过工厂方法模式导入了新的框架,让加盟店严格遵循我们的流程,现在我们需要考虑确保原料的一致,从而保证品控。
打算建造一家生产原料的工厂,将原料运送到各家加盟店。但是对于不同地区的店铺,需要准备多组原料。
在店铺扩增时,原料也会相应改变,所以需要考虑如何处理原料家族

建造一个工厂来生产原料,即创建原料工厂的每一种原料
开始先为工厂定义一个接口,这个接口负责创建所有的原料。
public interface PizzaIngredientFactory {
public Dough createDough();
public Sauce createSauce();
public Cheese createCheese();
public Veggies[] createVeggies();
public Clams createClam();
}
重写Pizza类,将其中的prepare()方法改为抽象方法,由子类实现,从而使用特定的原料工厂去获取原料
public class CheesePizza extends Pizza{
PizzaIngredientFactory ingredientFactory;
public CheesePizza(PizzaIngredientFactory ingredientFactory) {
this.ingredientFactory = ingredientFactory;
}
@Override
public void prepare() {
System.out.println("Preparing " + name);
dough = ingredientFactory.createDough();
sauce = ingredientFactory.createSauce();
cheese = ingredientFactory.createCheese();
}
}
public class NYPizzaStore extends PizzaStore{
public Pizza createPizza(String item) {
Pizza pizza = null;
PizzaIngredientFactory ingredientFactory = new NYPizzaIngredientFactory();
if (item.equals("cheese")) {
pizza = new CheesePizza(ingredientFactory);
pizza.setName("New York Style Cheese Pizza");
} else if (item.equals("pepperoni")) {
pizza = new PepperoniPizza(ingredientFactory);
pizza.setName("New York Style Pepperoni Pizza");
} else if (item.equals("clam")) {
pizza = new ClamPizza(ingredientFactory);
pizza.setName("New York Style Clam Pizza");
} else if (item.equals("veggie")) {
pizza = new VeggiesPizza(ingredientFactory);
pizza.setName("New York Style veggie Pizza");
}
return pizza;
}
}

一连串代码的变化,其本质是:我们引入新类型的工厂,即所谓的抽象工厂,来创建披萨原料家族
通过抽象工厂所提供的接口,可以创建产品的家族,利用这个接口书写代码,我们的代码将从实际工厂解耦,以便在不同上下文中实现各式各样的工厂,制造出各种不同的产品
因为代码从实际的产品中解耦,所以我们可以替换不同的工厂来取得不同的行为
抽象工厂模式提供一个接口,用于创建相关或依赖对象的家族,而不需要明确指定具体类

从PizzaStore的角度来看:

在上述代码中我们可以发现,抽象工厂的每个方法实际上看起来都像是工厂方法,每个方法被声明成抽象,子类的方法覆盖这些方法来创建某些对象。这不就是工厂方法吗
共同作用:将对象的创建封装起来,使应用程序解耦,并降低其对特定实现的依赖
| 对比内容 | 工厂方法 | 抽象方法 |
|---|---|---|
| 使用的设计模式 | 创建型模式 | 创建型模式 |
| 关注的对象类型 | 专注于单个产品的创建 | 专注于一组相关产品的创建 |
| 目标 | 提供一个接口,用来创建对象,子类决定具体的实现 | 提供一个接口,来创建产品家族中的多个相关对象 |
| 解耦的方式 | 客户端与具体产品解耦,客户端只需要知道接口而不关心具体实现 - 通过子类来创建对象 - 子类决定具体类型,客户只需要知道所用的抽象类型 |
客户端与整个产品族解耦,客户端只需知道工厂接口,具体产品族的变化由工厂子类处理 - 提供一个用来创建一个产品家族的抽象类型,该类型的子类定义产品被生产的方法。 - 想使用工厂,必须先实例化它,然后将它传入一些针对抽象类型所编写的代码中。 |
| 是否有多个产品 | 创建单一产品(通常是一个具体的类) | 创建多个相关产品,产品之间通常有依赖关系,属于同一产品族 |
| 扩展性 | 如果需要新产品,可以通过继承和重写工厂方法来实现新类型的创建 | 扩展产品族时,可能需要修改抽象工厂的接口,增加或改变产品接口,但可以通过新子类来应对不同产品族的需求 |
| 代码结构 | 客户端通过工厂方法来创建产品,工厂方法通常在具体类中实现 | 客户端通过抽象工厂来获得产品,工厂方法在不同的子工厂类中实现 |
| 使用类 vs 使用对象 | 使用具体的工厂类,通过继承来扩展产品创建方式,客户端依赖于工厂类的继承层次 | 使用抽象工厂接口,通过组合多个相关产品对象来创建产品家族,客户端依赖于工厂接口而不是具体实现 |
| 继承 vs 对象组合 | 依赖于继承来实现产品的创建,不同的具体工厂类通过继承抽象工厂来实现不同的创建方法 | 依赖于对象组合来实现产品的创建,不同的具体工厂类实现抽象工厂接口,并通过组合多个产品来实现产品创建 |
| 使用时机 | 当只需要创建单一产品,且可能需要在未来扩展或修改具体产品的创建方式时,使用工厂方法 | 当需要创建多个相关的产品,且这些产品有共同的特性或属于同一产品家族时,使用抽象工厂模式 |
采用工厂方法:

采用抽象工厂

OO基础
OO原则
OO模式
要点: