本期我们要介绍一个能让你烘烤自己的OO设计的一种模式,工厂模式
请问除了使用new以外,你还有其他创造对象的方法吗?如果你说没有,那么和我一起好好学习下这个模式吧。你会认识到往往实例化不应该总是公开进行,也会认识到初始化经常造成的“耦合”问题。
在之前的模式当中,我有介绍过一个原则,我们不应该针对实现编程。但当看到“new”时,就会想到“具体”,无论是 A a=new A()还是 A a=new B(),都其实在实例化一个具体类,所以的确用的是实现,而不是接口,或者再比如在之前“模拟鸭子”当中
Duck d = new MallardDuck(); Duck是一个接口,但MallardDuck还是得建立具体类呀,OK若这样想那么至少明白了,代码与具体类“捆绑”是耦合,缺少弹性一种表现。
那这时就有小伙伴说了:“但总要有创建对象的时候把,Java中只提供了new关键字键对象,还能有别的吗?” 当然new关键字本身没有问题,这是Java原生部分,其实阻碍我们的是“改变”,针对接口编程,可以隔离掉当“改变”来了以后的许多问题,如果代码是针对接口(这里我在此强调,接口是一个大概念,不单单interface是接口,抽象类,类也能是一个“接口”)而写,那么通过多态,它可以与任何新类实现该接口。但是我个人也觉得似乎有问题:当代码大量使用具体类时,会自找麻烦,因为当一旦要求增加某些内容时,就必须改变代码。也就是说这样代码也不是“对修改关闭”(前期内容)了,想要新的具体类型来拓展代码,必须重新修改并覆盖它。
说了那么多,具体咋写代码呢,不能纸上谈兵把?OK,我具体来写个实例。假设你有家披萨店。
注:设计模式需要反复多次练习,方可灵活运用,前期可以先模仿。
一开始这样写(这里我引用书中笔记)
当然我们还要传入类型来制作不同口味的披萨,如下图
但是你知道吗?这样设计是会有很大压力的,比如你要添加新的口味披萨,或者取出掉某些卖的不好风味的披萨的话。要这么写,无论增加新披萨或者删除掉原有披萨你都要到这里操作,上面部分会进行修改,而下面部分准备原料,烘烤,切片,装盘,操作都是不改变的,只有上面打算做什么口味披萨的这个动作会改变。
注:请区别出哪些是会改变的,哪些不会改变
我们做的改进,封装创建对象的代码,要把创建披萨的代码转移到另一个对象中,由这个对象专职创建披萨,这个新对象只干这一件事,如图
没错,我们称呼为它“工厂”。
在JavaWeb当中有个持久层框架Hibernate,其一个核心接口“工厂”(SessionFactory)就是用到工厂模式,负责初始化hibernate,是数据源的代理,负责创建Session对象,一个数据库对象只需配置一个SessionFactory,若有多个也可以配置多个。
工厂(factory)处理创建对象的细节,一旦有了Factory,orderPizza()就变成了,此对象的客户。当需要什么类型披萨时,就叫披萨工厂做一个。现在orderPizza()方法只关心从工厂得到一个披萨,而这个披萨实现了Pizza接口,所以可以调用准备材料,烘烤,切片,装盘方法。
那我们来创建个简单工厂。
可能有小伙伴提问:“感觉只是把之前会被大量修改的问题移到另一个对象里而已”
注:SimlePizzaFactory会有很多客户,目前我们看到orderPizza()方法是它客户,但是,可能还有PizzaShopMenu(披萨店菜单),xxxxPizzaShop(各种不同风格披萨店)等等会利用这个工厂来取得披萨里的内容,总而言之,SimlePizzaFactory会有许多客户。所以,把创建披萨的代码包装进一个类,当以后实现改变时,只需修改此类即可。我正要把具体实例化的过程,在客户代码中删除。
此时我们写一个披萨“工厂”的客户类(PizzaStore)
以上就是简单工厂模式的内容了,
注:简单工程严格来说不是一个设计模式,更像是一种编程习惯,但由于经常被使用,所以有时候很多开发人员误认为“工厂模式”。
下面是简单工程的类图。(请注意理解)
注:
再次提醒:所谓的“实现一个接口”并不一定表示“写一个(interface)类,然后利用implements关键词来实现某个Java接口”。“实现一个接口”泛指“实现某个超类型(可以是类或接口)的某个方法”。
你以为这篇文章写道这里就结束了?哈哈,别忘了我的口号是啥~~
既然我们有了自己的披萨店,我们当然希望将它做大做强,我们开始加盟披萨店。
这里我们创建对应的“工厂”对象。
但这又有一个问题了,难道你能放任加盟店不管吗?这样各个加盟店就按照自己的方式,烘烤的做法不同,不要切片,使用其他厂商的盒子,如果能够建立一个框架,把加盟店和创建披萨捆绑在一起的同时又保持一定的弹性。
有个做法可以把披萨制作活动局限在客户(PizzaStore)类中,而同时又能让这些加盟店依然可以自由的创建该区域披萨,就是将createPizza方法放回PizzaStore内,将其设置为“抽象方法”,让后每个区域的加盟店创建它的子类,就是说两者之间形成依赖关系。
让我们详细一点。
从orderPizza()方法观点来看,此方法在抽象父类PizzaStore里定义,但具体实现还是在不同子类中。面对每个不同子类,父类的orderPizza()方法其实并不知道哪个子类将实际上制作披萨。
orderPizza()方法对Pizza对象指向,(准备材料,烘烤,切片,装盒)但由于Pizza对象是抽象的,orderPizza()对象也并不知道哪些实际参与的具体类,换句话说,这就是解耦。(WTF?这就是解耦,很多时候往往初学设计模式的人还不能做到很敏感,包括我自己,需要长时间不断学习)
OK,我们这时候开一家加盟店咯。
注:超类(父类)PizzaStore中orderPizza()方法,并不知道正要创建什么披萨,它只知道制作披萨有四个步骤。。。
现在我们创建工厂方法,之前都是new,对象负责具体类的实例化,现在我们通过PizzaStore做一个小转变(其实在这里,我个人感觉说他是工厂方法感觉怪怪的)
我们再来巩固下工厂方法(这里我放出我的笔记截图)
我们来写下购买披萨流程
1:首先,客户们需要取得披萨店实例。客户需要实例化一个NYStylePizzaStore或ChicagoStylePizzaStore
2:有了各自的PizzaStore订单,客户们分别调用orderPizza()方法,并传入他们所喜爱的比萨类型(芝士,素食)。
3:orderPizza调用createPizza()创建披萨,纽约风味披萨(NYStylePizzaStore)或者芝加哥风味披萨(ChicagoStylePizzaStore)。
4:orderPizza()并不知道具体创建的是哪一种披萨,只知道这是一个披萨,能够准备原料,烘烤,切片,被打包装盒,然后送出给顾客。
定义工厂方式模式:工厂模式定义了一个创建对象的接口,不在直接使用new了,但由子类决定要实例化的类是哪一个。工厂方法让类把实例化推迟到子类了。
注:工厂方法模式能够封装具体类型的实例化。工厂方法让子类决定要实例化的类是哪一个。所谓“决定”,并不是指模式允许子类本身在运行时做决定,而是指在编写创建者类时,不需要知道实际创建的产品是哪一个。选择了哪个子类,自然就决定了实际创建的东西是什么(可能有点难以理解。。。)
下图是工厂方法模式类图
好的说这么多,下面就是具体实现的时候了,由于此例子量稍多,但是量大不代表我会偷工减料的,嘿嘿嘿~~~
这是参考的Java工程class图
代码开始
PizzaStore(工厂类)
package Store;
import Pizza.Pizza;
/**
* PizzaStore相当于客户(工厂类), 往后会有很多类似此类进行拓展
*
* 在这里orderPizza()方法对pizz对象做了很多事(createPizza,prepare,bake,cut,box)
* 但是由于Pizza对象是抽象的(接口),
* orderPizza()并不知道具体哪些实际类参与进来了(相当于实现Pizza接口的实现类有哪些orderPizza()方法并不知道)
* 换句话说,这就是解耦,减少相互依赖
*
* 同理createPizza()方法中也不知道创建的具体是什么披萨 只知道披萨可以被准备,被烘烤,被切片,被装盒行为而已
*
*/
// 披萨店
public abstract class PizzaStore {
// 点单
public Pizza orderPizza(String type) {
// createPizza()方法从工厂对象中取出,
// 并且方法也不再这里实现,定义为抽象类,谁需要在拿走方法去具体实现
Pizza pizza = createPizza(type);
System.out.println("----制作一个" + pizza);
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
// 把工厂对象移到这个方法里,并封装
// 现在工厂方法为抽象的,子类必须实现这个抽象方法
public abstract Pizza createPizza(String item);
}
下面是“工厂”具体实现
纽约风格披萨店(分店)
package Store;
import Pizza.NYStyleCheesePizza;
import Pizza.NYStyleClamPizza;
import Pizza.NYStylePepperoniPizza;
import Pizza.NYStyleVeggiePizza;
import Pizza.Pizza;
/**
* 此时NYStylePizzaStore所封装的是关于如何制作纽约风格的比萨
*
* @author Joy
*
*/
// 纽约风格披萨店(分店)
public class NYStylePizzaStore extends PizzaStore {
// 根据披萨的不同,创建不同披萨(具体实现类)
@Override
public Pizza createPizza(String item) {
if (item.equals("芝士")) {
return new NYStyleCheesePizza();
} else if (item.equals("素食")) {
return new NYStyleVeggiePizza();
} else if (item.equals("海鲜")) {
return new NYStyleClamPizza();
} else if (item.equals("香肠")) {
return new NYStylePepperoniPizza();
}
return null;
}
}
芝加哥披萨店(分店)
package Store;
import Pizza.ChicagoStyleCheesePizza;
import Pizza.ChicagoStyleClamPizza;
import Pizza.ChicagoStylePepperoniPizza;
import Pizza.ChicagoStyleVeggiePizza;
import Pizza.Pizza;
/**
* 此时ChicagoStylePizzaStore所封装的是关于如何制作芝加哥风格的比萨
*
* @author Joy
*
*/
// 芝加哥披萨店(分店)
public class ChicagoStylePizzaStore extends PizzaStore {
@Override
public Pizza createPizza(String item) {
if (item.equals("芝士")) {
return new ChicagoStyleCheesePizza();
} else if (item.equals("素食")) {
return new ChicagoStyleVeggiePizza();
} else if (item.equals("海鲜")) {
return new ChicagoStyleClamPizza();
} else if (item.equals("香肠")) {
return new ChicagoStylePepperoniPizza();
}
return null;
}
}
Pizza类
(相当于各个“产品”的接口)
package Pizza;
import java.util.ArrayList;
public abstract class Pizza {
String name;// 披萨名称
String dough;// 面团类型
String sauce;// 酱料
ArrayList toppings = new ArrayList();// 一套佐料
public void prepare() {
System.out.println("准备:" + name);
System.out.println("添加面团");
System.out.println("添加酱料");
System.out.println("添加佐料如下:");
for (int i = 0; i < toppings.size(); i++) {
System.out.print(toppings.get(i) + " ");
}
}
public void bake() {
System.out.println("\n" + "披萨正在烘烤,需烘烤25分钟。。。");
}
public void cut() {
System.out.println("制作完成,给披萨切片。。。");
}
public void box() {
System.out.println("给披萨打包装盒。。。");
}
public String getName() {
return name;
}
// 输出客人点的披萨信息
@Override
public String toString() {
StringBuffer display = new StringBuffer();
display.append(name + " ----\n");
display.append(dough + "\n");
display.append(sauce + "\n");
for (int i = 0; i < toppings.size(); i++) {
display.append((String) toppings.get(i) + "\n");
}
return display.toString();
}
}
下面是各种披萨具体实现类
package Pizza;
//加州披萨店的芝士披萨
public class ChicagoStyleCheesePizza extends Pizza {
public ChicagoStyleCheesePizza() {
name="加州芝士披萨";
dough="厚饼";
sauce="番茄酱";
toppings.add("干碎奶酪");
}
@Override
public void cut() {
System.out.println("将披萨切成方形");
}
}
package Pizza;
//加州披萨店的海鲜披萨
public class ChicagoStyleClamPizza extends Pizza {
public ChicagoStyleClamPizza() {
name = "加州海鲜披萨";
dough = "厚饼";
sauce = "番茄酱";
toppings.add("干碎奶酪");
toppings.add("蟹黄");
toppings.add("龙虾");
toppings.add("各种海鲜");
}
@Override
public void cut() {
System.out.println("将披萨切成三角形");
}
}
package Pizza;
//加州披萨店里的香肠披萨
public class ChicagoStylePepperoniPizza extends Pizza {
public ChicagoStylePepperoniPizza() {
name = "加州香肠披萨";
dough = "厚饼";
sauce = "番茄酱";
toppings.add("干碎奶酪");
toppings.add("黑胡椒");
toppings.add("菠菜");
toppings.add("茄子");
toppings.add("香肠切片");
}
@Override
public void cut() {
System.out.println("切成方形薄披萨");
}
}
package Pizza;
//加州披萨店里的素披萨
public class ChicagoStyleVeggiePizza extends Pizza {
public ChicagoStyleVeggiePizza() {
name = "加州素披萨";
dough = "厚饼";
sauce = "番茄酱";
toppings.add("干碎奶酪");
toppings.add("圣女果");
toppings.add("黑胡椒粉");
toppings.add("红辣椒");
toppings.add("甜玉米粒");
toppings.add("色拉");
}
@Override
public void cut() {
System.out.println("切成方形薄披萨");
}
}
package Pizza;
//纽约披萨店里的芝士披萨
public class NYStyleCheesePizza extends Pizza {
public NYStyleCheesePizza() {
name="纽约芝士披萨";
dough="薄饼";
sauce="番茄酱";
toppings.add("干奶酪");
}
}
package Pizza;
//纽约披萨店里的河鲜披萨
public class NYStyleClamPizza extends Pizza {
public NYStyleClamPizza() {
name = "纽约海鲜披萨";
dough="薄饼";
sauce="番茄酱";
toppings.add("巴马干奶酪");
toppings.add("蟹黄");
toppings.add("龙虾");
}
}
package Pizza;
//纽约披萨店里的香肠披萨
public class NYStylePepperoniPizza extends Pizza {
public NYStylePepperoniPizza() {
name = "纽约香肠披萨";
dough="薄饼";
sauce="番茄酱";
toppings.add("巴马干酪奶酪");
toppings.add("意大利香肠切片");
toppings.add("大蒜");
toppings.add("洋葱");
toppings.add("香菇");
toppings.add("红辣椒");
}
}
package Pizza;
//纽约披萨店里的素披萨
public class NYStyleVeggiePizza extends Pizza {
public NYStyleVeggiePizza() {
name = "纽约素食披萨";
dough = "薄饼";
sauce = "番茄酱";
toppings.add("巴马干酪奶酪");
toppings.add("洋葱");
toppings.add("各种蔬菜");
}
}
测试类
package TestMain;
import Pizza.Pizza;
import Store.ChicagoStylePizzaStore;
import Store.NYStylePizzaStore;
import Store.PizzaStore;
public class TestMain {
public static void main(String[] args) {
PizzaStore nyStore = new NYStylePizzaStore();
PizzaStore chicagoStore = new ChicagoStylePizzaStore();
// System.out.println("====================================");
// Pizza pizza1 = nyStore.orderPizza("芝士");
// System.out.println("Joy点了一个" + pizza1.getName());
// System.out.println("====================================");
// Pizza pizza2 = nyStore.orderPizza("素食");
// System.out.println("Joy点了一个" + pizza2.getName());
System.out.println("====================================");
Pizza pizza3 = nyStore.orderPizza("海鲜");
System.out.println("Joy点了一个" + pizza3.getName() + " ,正在送出");
// System.out.println("====================================");
// Pizza pizza4 = nyStore.orderPizza("香肠");
// System.out.println("Joy点了一个" + pizza4.getName());
System.out.println("====================================");
Pizza pizza11 = chicagoStore.orderPizza("芝士");
System.out.println("Joy点了一个" + pizza11.getName() + " ,正在送出");
// System.out.println("====================================");
// Pizza pizza22 = chicagoStore.orderPizza("素食");
// System.out.println("Joy点了一个" + pizza22.getName());
// System.out.println("====================================");
// Pizza pizza33 = chicagoStore.orderPizza("海鲜");
// System.out.println("Joy点了一个" + pizza33.getName());
// System.out.println("====================================");
// Pizza pizza44 = chicagoStore.orderPizza("香肠");
// System.out.println("Joy点了一个" + pizza44.getName());
}
}
效果如下:
这个模式是个很有用的模式,让我更加能封装变化了,模拟代码已经全部放出,注释也写的比较清楚,若有不理解欢迎留言。
感谢你看到这里,工厂模式上部分结束,但工厂模式内容还没完全结束 本人文笔随便,若有不足或错误之处望给予指点,90度弯腰~~~很快我会补全完这个内容。 生命不息,编程不止!
参考书籍:《Head First 设计模式》
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。