04-02-设计模式 工厂模式
简单工厂模式
需求
看一个披萨的项目:
- 披萨的种类有很多(GePizz, CePizz等)
- 披萨的制作步骤有 prepare, bake, cut, box
- 完成披萨店的订购功能
需求点: 要便于披萨种类的扩展, 要便于维护
类图
传统方式实现
package com.dance.design.designmodel.factory.simple; public class CtPizz { public static void main(String[] args) { OrderPizza.ding("ce"); OrderPizza.ding("ge"); } } /** * 披萨类 */ abstract class Pizza{ /** * 名称 */ public String name; /** * 准备材料 */ public abstract void prepare(); /** * 为了不每次都全部调用, 我创建一个构建披萨的方法 */ public void buildPizza(){ prepare(); bake(); cut(); box(); } /** * 烘烤 */ public void bake(){ System.out.println(name + "baking ..."); } /** * 切块 */ public void cut(){ System.out.println(name + "cutting ..."); } /** * 打包 */ public void box(){ System.out.println(name + "boxing ..."); } } /** * 奶酪披萨 */ class CePizza extends Pizza{ @Override public void prepare() { System.out.println("准备奶酪披萨的原材料"); name = "奶酪披萨"; } } /** * 希腊披萨 */ class GePizza extends Pizza{ @Override public void prepare() { System.out.println("准备希腊披萨的原材料"); name = "希腊披萨"; } } /** * 订购类 */ class OrderPizza{ public OrderPizza(){} public static void ding(String orderType){ Pizza pizza = null; if("ce".equals(orderType)){ pizza = new CePizza(); }else if("ge".equals(orderType)){ pizza = new GePizza(); }else{ throw new RuntimeException("订单类型错误"); } // 开始制作 pizza.buildPizza(); } }
我这里没有写设么用户输入啥的,应为感觉意义不大, 我们主要看设计模式,而不是关注其他的边缘细节
传统实现方式分析
- 优点是比较好理解, 简单容易操作
- 缺点是违反了Ocp原则, 即对扩展开放,对修改关闭, 当我们给类增加新功能的时候尽量不修改代码,或者尽可能的少修改代码
需求改进
我们需要新增一种Pizza, 我们就需要创建新的Pizza种类 ,然后修改订单类的判断逻辑, 当然, 如果在别处还有创建的话, 需要在别处也修改, 这样的话改动量就会比较大
传统方式改进
我们新增巧克力披萨
/** * 巧克力披萨 */ class QkPizza extends Pizza{ @Override public void prepare() { System.out.println("准备巧克力披萨的原材料"); name = "巧克力披萨"; } }
修改OrderPizza逻辑, 这个创建的逻辑可能在很多地方都有,所以需要都修改, 我们这里只有一种效果看着还不是很麻烦
增加调用逻辑
到此扩展完毕, 应为设计的原因披萨类型扩展很容易, 调用扩展是必须的, 但是中间部分如果很多的话, 其实是可以统一管理的, 还有就是对于OrderPizza来说, 它其实是不应该知道Pizza的创建逻辑的, 而且如果后续扩展门店的话
会有orderPizza2, orderPizza3, 这样每个类都会和Pizza,Ce.., Ge.. Qk..全部发生关系, 如果一旦再增加Pizza的种类, 就会改动量非常大
简单工厂改进
基本介绍
- 简单工厂模式是属于创建型模式,是工厂模式的一种, 简单工厂模式是由一个工厂对象决定创建出哪一种产品类的实例, 简单工厂模式是工厂模式中最简单实用的
- 简单工厂模式: 定义一个创建对象的类, 由这个类来统一对实例进行创建
- 在软件开发中, 我们会用到大量的创建某种, 某类或者某批对象时, 就会使用到工厂模式
改进思路
把创建具体披萨的任务交给工厂, 这样我们创建的时候, 只需要调用工厂的方法即可, 其调用工厂的地方是不需要修改的
改进前类图
改进后类图
这样, 我让我的订单去面对工厂, 后续改造仅限于工厂, 种类, 和调用方, 但是订单是不需要修改的, 哪怕后续扩展订单门店, 也是直接面对工厂就可以了, 不必关注细节
代码
增加工厂类
class PizzaFactory{ public static Pizza createPizza(String orderType){ Pizza pizza = null; if("ce".equals(orderType)){ pizza = new CePizza(); }else if("qk".equals(orderType)){ pizza = new QkPizza(); }else if("ge".equals(orderType)){ pizza = new GePizza(); }else{ throw new RuntimeException("订单类型错误"); } return pizza; } }
修改OrderPizza
/** * 订购类 */ class OrderPizza{ public OrderPizza(){} public static void ding(String orderType){ // 开始制作 PizzaFactory.createPizza(orderType).buildPizza(); } }
后续种类再则么多, 我的订单类都是不用动的
工厂方法模式
需求演化
我们的披萨店越做越大, 终于,在不同的地区开分店了,比如北京, 上海,广州等, 这个时候顾客就可以在不同的地区点北京的奶酪披萨, 上海的藤椒披萨等
思路
使用简单工厂模式, 我们可以创建BJPizzaFactory, SHPizzaFactory等, 从这案例来说是可以的, 但是考虑项目的规模,以为软件的可维护性和可扩展性并不是特别好, 所以我们用工厂方法模式
工厂方法模式介绍
工厂方法设计方案: 将披萨项目的实例化功能抽象成抽象方法, 在不同的口味点餐子类中做具体实现
工厂方法模式: 定义了一个创建对象的抽象方法, 由子类决定要实例化的类, 工厂方法模式将对象的实例化推迟到子类
类图
代码
package com.dance.design.designmodel.factory.simple.sp3; import com.sun.org.apache.xpath.internal.operations.Or; public class CtPizz { public static void main(String[] args) { new BJOrderPizza().ding("ce"); new BJOrderPizza().ding("ge"); new SHOrderPizza().ding("ce"); new SHOrderPizza().ding("ge"); } } /** * 披萨类 */ abstract class Pizza{ /** * 名称 */ public String name; /** * 准备材料 */ public abstract void prepare(); /** * 为了不每次都全部调用, 我创建一个构建披萨的方法 */ public void buildPizza(){ prepare(); bake(); cut(); box(); } /** * 烘烤 */ public void bake(){ System.out.println(name + "baking ..."); } /** * 切块 */ public void cut(){ System.out.println(name + "cutting ..."); } /** * 打包 */ public void box(){ System.out.println(name + "boxing ..."); } } /** * 北京奶酪披萨 */ class BJCePizza extends Pizza{ @Override public void prepare() { System.out.println("准备北京奶酪披萨的原材料"); name = "北京奶酪披萨"; } } /** * 上海奶酪披萨 */ class SHCePizza extends Pizza{ @Override public void prepare() { System.out.println("准备上海奶酪披萨的原材料"); name = "上海奶酪披萨"; } } /** * 希腊披萨 */ class BJGePizza extends Pizza{ @Override public void prepare() { System.out.println("准备北京希腊披萨的原材料"); name = "北京希腊披萨"; } } /** * 希腊披萨 */ class SHGePizza extends Pizza{ @Override public void prepare() { System.out.println("准备上海希腊披萨的原材料"); name = "上海希腊披萨"; } } /** * 订购类 */ abstract class OrderPizza{ public OrderPizza(){} public abstract void ding(String orderType); } /** * 北京店 */ class BJOrderPizza extends OrderPizza { @Override public void ding(String orderType) { Pizza pizza = null; if("ce".equals(orderType)){ pizza = new BJCePizza(); }else if("ge".equals(orderType)){ pizza = new BJCePizza(); }else{ throw new RuntimeException("订单类型错误"); } // 开始制作 pizza.buildPizza(); } } /** * 上海店 */ class SHOrderPizza extends OrderPizza { @Override public void ding(String orderType) { Pizza pizza = null; if("ce".equals(orderType)){ pizza = new SHCePizza(); }else if("ge".equals(orderType)){ pizza = new SHCePizza(); }else{ throw new RuntimeException("订单类型错误"); } // 开始制作 pizza.buildPizza(); } }
这样我们调用只针对与门店, 上海调用上海的门店, 北京调用北京的门店, 然后由门店去面对Pizza和子类, 但是我感觉这样也不太好, 接下来使用抽象工厂改造~
抽象工厂模式
基本介绍
- 抽象工厂模式:定义了一个interface用于创建相关或有依赖关系的对象簇,而无需指明具体的类
- 抽象工厂模式可以将简单工厂和工厂方法模式进行整合
- 从设计层面看, 抽象工厂模式就是对简单工厂模式的改进(或者称之为进一步抽象)
- 将工厂抽象成两层, ABSFactory(抽象工厂), 和 具体实现的工厂子类, 程序员可以根据创建对象类型使用对应的工厂子类, 这样将单个的简单工厂类变成了工厂簇, 更利于代码的维护和扩展
类图
订单抽象类, 面向 抽象工厂, 抽象工厂面向披萨抽象类, 细节由实现类去维护
代码
package com.dance.design.designmodel.factory.simple.sp3; public class CtPizz { public static void main(String[] args) { new BJOrderPizza(new BJPizzaFactory()).ding("ce"); new BJOrderPizza(new BJPizzaFactory()).ding("ge"); new SHOrderPizza(new SHPizzaFactory()).ding("ce"); new SHOrderPizza(new SHPizzaFactory()).ding("ge"); } } /** * 披萨类 */ abstract class Pizza{ public String name; public abstract void prepare(); public void buildPizza(){ prepare(); bake(); cut(); box(); } public void bake(){ System.out.println(name + "baking ..."); } public void cut(){ System.out.println(name + "cutting ..."); } public void box(){ System.out.println(name + "boxing ..."); } } /** * 北京奶酪披萨 */ class BJCePizza extends Pizza{ @Override public void prepare() { System.out.println("准备北京奶酪披萨的原材料"); name = "北京奶酪披萨"; } } /** * 上海奶酪披萨 */ class SHCePizza extends Pizza{ @Override public void prepare() { System.out.println("准备上海奶酪披萨的原材料"); name = "上海奶酪披萨"; } } /** * 北京希腊披萨 */ class BJGePizza extends Pizza{ @Override public void prepare() { System.out.println("准备北京希腊披萨的原材料"); name = "北京希腊披萨"; } } /** * 上海希腊披萨 */ class SHGePizza extends Pizza{ @Override public void prepare() { System.out.println("准备上海希腊披萨的原材料"); name = "上海希腊披萨"; } } /** * 订购类 */ abstract class OrderPizza{ /** * 面对抽象工厂 */ protected AbsPizzaFactory pizzaFactory; public OrderPizza(AbsPizzaFactory pizzaFactory){ this.pizzaFactory = pizzaFactory; } public void ding(String orderType){ // 默认逻辑 pizzaFactory.createPizza(orderType).buildPizza(); } } /** * 抽象工厂 */ abstract class AbsPizzaFactory{ protected abstract Pizza createPizza(String orderType); } /** * 北京披萨工厂 */ class BJPizzaFactory extends AbsPizzaFactory{ @Override protected Pizza createPizza(String orderType) { Pizza pizza = null; if("ce".equals(orderType)){ pizza = new BJCePizza(); }else if("ge".equals(orderType)){ pizza = new BJCePizza(); }else{ throw new RuntimeException("订单类型错误"); } return pizza; } } /** * 上海披萨工厂 */ class SHPizzaFactory extends AbsPizzaFactory{ @Override protected Pizza createPizza(String orderType) { Pizza pizza = null; if("ce".equals(orderType)){ pizza = new SHCePizza(); }else if("ge".equals(orderType)){ pizza = new SHCePizza(); }else{ throw new RuntimeException("订单类型错误"); } return pizza; } } /** * 北京店 */ class BJOrderPizza extends OrderPizza { public BJOrderPizza(AbsPizzaFactory pizzaFactory) { super(pizzaFactory); } } /** * 上海店 */ class SHOrderPizza extends OrderPizza { public SHOrderPizza(AbsPizzaFactory pizzaFactory) { super(pizzaFactory); } }
其实抽象工厂我感觉还可以改进一下, 这样的话调用方就知道了具体的类,如果调用方多的话也不好维护, 可以再提供一个工厂创建者的单利, 通过传入地区也就是BJ, SH来获取一个抽象工厂(都已经学过单利模式了吧), 这个就自己去改造吧
源码剖析
JDK中的工厂模式
- JDK中的Calendar类中, 就使用了简单工厂模式
测试
public class CalendarTest { public static void main(String[] args) { Calendar calendar = Calendar.getInstance(); System.out.println("年:" + calendar.get(Calendar.YEAR)); // 月从0 开始 需要 +1 System.out.println("月:" + (calendar.get(Calendar.MONTH)+ 1)); System.out.println("日:" + calendar.get(Calendar.DAY_OF_MONTH)); System.out.println("时:" + calendar.get(Calendar.HOUR_OF_DAY)); System.out.println("分:" + calendar.get(Calendar.MINUTE)); System.out.println("秒:" + calendar.get(Calendar.SECOND)); } }
如何,有感受到简单工厂的气息吗? 当然不只是简单工厂, 从第一行getInstance中也感受出来了吧, 这还是一个单利, 当然如果看源码的的话, 你就感受到很多其他的设计模式, 由此可以看出大佬是多么的**
源码
emm, 源码就不看了, 从使用上感受一下就可以了, 额, 要不还是看一下吧
public static Calendar getInstance(TimeZone zone,Locale aLocale){ return createCalendar(zone, aLocale); }
getInstance调用了createCalendar
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; }
好了看完了吧
工厂模式小结
工厂模式的意义
将实例化细节的代码提取出来, 放到工厂类中统一管理和维护,达到和主业务线的依赖关系的解耦,从而提高了项目的扩展和维护性
三种模式
- 简单工厂模式
- 工厂方法模式
- 抽象工厂模式
设计模式的依赖抽象原则
- 创建对象的依赖实例时, 不要直接new 类,而是吧这个new 类的动作放在一个工厂方法中,并返回, 有的书上说变量不要直接持有具体类的引用
- 不要让类继承具体类,而是继承抽象类 或者实现接口
- 不要覆盖基类中已经实现的方法