这是我看Head first设计模式书籍之后想要总结的知识点,一方面是对自己学习的东西总结和提炼加强自己的理解和记忆,另一方面是给大家简化这本书,方便大家快速了解各种设计模式。
我想提醒大家的是,设计模式只是前人总结的一些经验套路,实际上还是要在开发项目中慢慢体会,不可成为设计模式的中毒患者,强行照搬设计模式的一些规则。
我们除了使用new 操作符以外,还有更多制造对象的方法。你将了解到实例化这个活动不应该总是公开地进行,
也会认识到初始化经常造成"耦合"问题。那么工厂模式将你从复杂的依赖解脱出来。
我们这次要讲解的设计模式是工厂方法模式和抽象工厂模式
在这里开个头,我们都知道在软件设计中应该高内聚高耦合,这是模块独立性的两个定性的标准,将软件系统
划分模块时,尽量做到高内聚低耦合,提高模块的独立性,为设计高质量的软件结构奠定基础.
我们举个栗子:
我们有个披萨店专门生产不同种类的披萨,那么代码可能是这么写:
//如果你有一个Pizza店铺,你创造了一个点Pizza的方法,你可能会直接这么写
Pizza orderPizza(){
/*为了使系统更有弹性,我们希望这里是一个抽象类或者接口,但是如果是
这样子,这些类或接口无法直接实例化*/
Pizza pizza = new Pizza():
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
为了可以点更多不同种类的披萨,我们这样写:
//必须增加一些代码,来"决定"适合的披萨类型,然后再"制造"这个披萨
Pizza orderPizza(String type){
Pizza pizza;
if(type.equals("cheese")){
pizza = new CheesePizza();
} else if(type.equals("greek")){
pizza = new GreekPizza();
} else if(type.equals("perpperroni")){
pizza = new PerpperroniPizza();
}
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
我们发现,这个代码没有对修改封闭,如果披萨店改变供应的披萨风味,就得进入到这里来修改。
我们需要将其变化的部分封装起来
//定义一个简单的工厂类
public class SimplePizzaFactory{
public Pizza createPizza(String type){
Pizza pizza = null;
if(type.equals("cheese")){
pizza = new CheesePizza();
} else if(type.equals("greek")){
pizza = new GreekPizza();
} else if(type.equals("perpperroni")){
pizza = new PerpperroniPizza();
}
return pizza;
}
}
//使用这个简单的工厂
public class PizzaStore{
SimplePizzaFactory factory;
public PizzaStore(SimplePizzaFactory factory){
this.factory = factory;
}
public Pizza orderPizza(String type){
Pizza pizza = null;
pizza = factory.createPizza(type);
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
} //下面是其他的方法省略
}
新的披萨店类图
注意: 在设计模式中,所谓的"实现一个接口",并"不一定"表示"写一个类,并利用Implements关键字来实现某个Java接口".
"实现一个接口"泛指"实现某个超类型(可以是类或接口的某个方法”。
当在不同地区开不同类型的披萨店时,需要将PizzaStore抽象出来,让各个子类负责定义自己的createPizza()方法
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;
}
abstract Pizza createPizza(String type); //把工厂对象移到这个抽象方法中
}
不同地区的披萨店继承抽象披萨店类,并重写创造披萨对象的方法
//纽约披萨
public class NYPizzaStore extends PizzaStore{
Pizza createPizza(String item){
if(item.equals("cheese")){
return new NYStyleCheesePizza(); //创建具体实例的地方
} else if(item.equals("veggie")){
return new NYStyleVeggiePizza();
} else if(item.equals("clam")){
return new NYStyleClamPizza();
} else if(item.equals("pepperoni")){
return new NYPepperoniPizza();
} else return null;
}
}
//芝加哥披萨
public class ChicagoPizzaStore extends PizzaStore{
protected Pizza createPizza(String item){
if(item.equals("cheese")){
return new ChicagoStyleCheesePizza();
} else if(item.equals("veggie")){
return new ChicagoStyleVeggiePizza();
} else if(item.equals("clam")){
return new ChicagoStyleClamPizza();
} else if(item.equals("pepperoni")){
return new ChicagoStylePepperoniPizza();
} else return null;
}
}
//加州披萨
public class CaliforniaPizzaStore extends PizzaStore{
protected Pizza createPizza(String item){
if(item.equals("cheese")){
return new CaliforniaStyleCheesePizza();
} else if(item.equals("veggie")){
return new CaliforniaStyleVeggiePizza();
} else if(item.equals("clam")){
return new CaliforniaStyleClamPizza();
} else if(item.equals("pepperoni")){
return new CaliforniaStylePepperoniPizza();
} else return null;
}
}
抽象Pizza类和具体实现Pizza类
//披萨类
public abstract class Pizza{
/*每个披萨都具有自己的名称、面团类型、酱料类型、一套作料*/
String name;
String dough;
String sauce;
ArrayList toppings = new ArrayList();
//抽象类提供了某些默认的基本做法,用来进行烘烤,切片,装盒
void prepare(){
//准备工作需要以特定的顺序进行,有一连串的步骤
System.out.println("Preparing " + name);
System.out.println("Tossing dough...");
System.out.println("Adding sauce...");
System.out.println("Adding toppings:");
for(int i = 0; i < toppings.size(); i++){
System.out.println(" " + toppings.get(i));
}
}
void bake(){
System.out.println("Bake for 25 minutes at 350");
}
void cut(){
System.out.println("Cutting the pizza into diagonal slices");
}
void box(){
System.out.println("Place pizza in official PizzaStore box");
}
public String getName(){
return name;
}
}
public class NYStyleCheesePizza extends Pizza{
public NYStyleCheesePizza(){
name = "NY Style Sauce and Chees Pizza";
dough = "Thin Crust Dough";
sauce = "Marinara Sauce";
toppings.add("Grated Reggiano Cheese");
}
}
public class ChicagoStyleCheesePizza extends Pizza{
public ChicagoStyleCheesePizza(){
name = "Chicago Style Deep Dish Cheese Pizza";
dough = "Extra Thick Crust Dough";
sauce = "Plum Tomato Sauce";
toppings.add("Shredded Mozzarella Cheese");
}
void cut(){
System.out.println("Cutting the pizza into square slices");
}
}
测试类
public class PizzaTestDrive{
public static void main(String args[]){
PizzaStore nyStore = new NYPizzaStore();
PizzaStore chicagoStore = new chicagoPizzaStore();
Pizza pizza = nyStore.orderPizza("cheese");
System.out.println("Ethan ordered a " + pizza.getName() + "\n");
pizza = chicagoStore.orderPizza("cheese");
System.out.println("Joel ordered a " + pizza.getName() + "\n");
}
}
我们可以看到,在PizzaStore类中定义了一个抽象方法createPizza(),实例化披萨的责任被移到另一个"方法"中,也就是子类的方法中,此方法就如同是一个工厂方法,所以这个方法是个工厂方法。
下面是我们的工厂方法模式出厂了:
工厂方法模式:定义了一个创建对象的接口,但由子类决定要实例化的类是哪一个。工厂方法让类把实例化推迟到子类。
也就是说,工厂方法模式能够封装具体类型的实例化,通过让子类决定该创建的对象是什么,来达到将对象创建的过程封装的目的。
我们来看一下披萨案例类图结构:
我们将一个orderPizza()方法和一个工厂方法联合起来,就可以成为一个框架。除此之外,工厂方法将生产知识封装进各个
创建者。
下面是工厂方法模式类图示例:
设计原则 要依赖抽象,不要依赖具体类(依赖倒置原则)
当你直接实例化一个对象时,就是在依赖它的具体类。
这个原则听起来像针对接口编程,不针对实现编程。但这里更强调"抽象",这个原则说明了
不能让高层组件依赖低层组件,不管高层或低层组件,“两者”都应该依赖于抽象
(例如,PizzaStore是个高层组件,它的行为是由披萨定义的,而Pizza子类依赖于父类Pizza,
也就是说,高层组件(PizzaStore)和低层组件(这些披萨)都依赖Pizza抽象)
下面的指导方针可以让你避免在OO设计中违反依赖倒置原则
(如果有一个不像是会改变的类,那么在代码中直接实例化具体类也就没什么大碍,总之应该尽量达到这个原则,而不是随时都要遵守)
1.变量不可以持有具体类的引用
2.不要让类派生自具体类
3.不要覆盖基类中已实现的方法
下面建造一家生产原料的工厂,并将原料送到不同地区的披萨店里来制作披萨
我们需要一个原料工厂来生产原料,比如生产面团、酱料、芝士等
原料工厂类
注意这个类是个接口,如果每个工厂实例都有某一种通用的"机制"(其实就是方法)需要实现,就可以吧这个方法改写成抽象类
public interface PizzaIngredientFactory{
//原料接口中,每个原料都有一个对应的方法创建该原料
public Dough createDough();
public Sauce createSauce();
public Cheese createCheese();
public Veggies createVeggies();
public Pepperoni createPepperoni();
public Clams createClam();
}
实现原料工厂类,创造芝加哥和加州原料工厂 原料类就不写了,很简单
public class NYPizzaIngredientFactory implements PizzaIngredientFactory{
public Dough createDough(){
return new ThinCrustDough();
}
public Sauce createSauce(){
return new MarinaraSauce();
}
public Cheese create Cheese(){
return new ReggianoCheese();
}
public Veggies[] createVeggies(){
Veggies veggies[] = {new Garlic(), new Onion(), new Mushroom(), new RedPepper()};
return veggies;
}
public Pepperoni createPepperoni(){
return new SlicedPepperoni();
}
public Clams createClam(){
return new FreshClams();
}
}
public class ChicagoPizzaStoreIngredientFactory implements PizzaIngredientFactory{
public Dough createDough(){
return new ThickCrustDough();
}
public Sauce createSauce(){
return new PlumTomatoSauce();
}
public Cheese create Cheese(){
return new MozzarellaCheese();
}
public Veggies[] createVeggies(){
Veggies veggies[] = {new BlackOlives(), new Spinash(), new Eggplant()};
return veggies;
}
public Pepperoni createPepperoni(){
return new SlicedPepperoni();
}
public Clams createClam(){
return new FrozonClams();
}
}
重做披萨抽象类Pizza
public abstract class Pizza{
String name;
Dough dough;
Sauce sauce;
Veggies veggies[];
Cheese cheese;
Pepperoni pepperoni;
Clams clam;
abstract void prepare();
void bake(){
System.out.println("Bake for 25 minutes at 350");
}
void cut(){
System.out.println("Cutting the pizza into diagonal slices");
}
void box(){
System.out.println("Place pizza in official PizzaStore box");
}
String getName(){
return name;
}
void setName(String name){
this.name = name;
}
public String toString(){
//这里打印披萨的代码
}
}
继承抽象Pizza,创建具体披萨类CheesePizza和ClamPizza
public class CheesePizza extends Pizza{
PizzaIngredientFactory ingredientFactory;
public CheesePizza(PizzaIngredientFactory ingredientFactory){
this.ingredientFactory = ingredientFactory;
}
void prepare(){
System.out.println("Preparing " + name);
dough = ingredientFactory.createDough();
sauce = ingredientFactory.createSauce();
cheese = ingredientFactory.createCheese();
}
}
public class ClamPizza extends Pizza{
PizzaIngredientFactory ingredientFactory;
public ClamPizza(PizzaIngredientFactory ingredientFactory){
this.ingredientFactory = ingredientFactory;
}
void prepare(){
System.out.println("Preparing " + name);
dough = ingredientFactory.createDough();
sauce = ingredientFactory.createSauce();
cheese = ingredientFactory.createCheese();
clam = ingredientFactory.createClam();
}
}
修改具体披萨店
只需加入实例变量,在不同地区的披萨店用不同披萨配料具体工厂初始化就可以了
我们使用了抽象工厂模式: 提供一个接口,用于创建相关或依赖对象的家族,而不需要明确指定具体类。
工厂方法和抽象工厂的区别:
工厂方法使用继承,把对象的创建委托给子类,子类实现工厂方法来创建对象
抽象工厂使用对象组合,对象的创建被实现在工厂接口所暴露出来的方法中。
所有工厂模式都是通过减少应用程序和具体类之间的依赖促进松耦合。
当你需要创建产品家族和想让制造的相关产品集合起来时,你可以使用抽象工厂模式
如果只有一种产品,那么使用工厂方法模式。
下一节我们将单例模式