04-02-设计模式 工厂模式

2022-05-28 09:20:28 浏览数 (2)

简单工厂模式

需求

看一个披萨的项目:

  1. 披萨的种类有很多(GePizz, CePizz等)
  2. 披萨的制作步骤有 prepare, bake, cut, box
  3. 完成披萨店的订购功能

需求点: 要便于披萨种类的扩展, 要便于维护

类图

传统方式实现

代码语言:javascript复制
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();
    }
}

我这里没有写设么用户输入啥的,应为感觉意义不大, 我们主要看设计模式,而不是关注其他的边缘细节

传统实现方式分析

  1. 优点是比较好理解, 简单容易操作
  2. 缺点是违反了Ocp原则, 即对扩展开放,对修改关闭, 当我们给类增加新功能的时候尽量不修改代码,或者尽可能的少修改代码

需求改进

我们需要新增一种Pizza, 我们就需要创建新的Pizza种类 ,然后修改订单类的判断逻辑, 当然, 如果在别处还有创建的话, 需要在别处也修改, 这样的话改动量就会比较大

传统方式改进

我们新增巧克力披萨

代码语言:javascript复制
/**
 * 巧克力披萨
 */
class QkPizza extends Pizza{
    @Override
    public void prepare() {
        System.out.println("准备巧克力披萨的原材料");
        name = "巧克力披萨";
    }
}

修改OrderPizza逻辑, 这个创建的逻辑可能在很多地方都有,所以需要都修改, 我们这里只有一种效果看着还不是很麻烦

增加调用逻辑

到此扩展完毕, 应为设计的原因披萨类型扩展很容易, 调用扩展是必须的, 但是中间部分如果很多的话, 其实是可以统一管理的, 还有就是对于OrderPizza来说, 它其实是不应该知道Pizza的创建逻辑的, 而且如果后续扩展门店的话

会有orderPizza2, orderPizza3, 这样每个类都会和Pizza,Ce.., Ge.. Qk..全部发生关系, 如果一旦再增加Pizza的种类, 就会改动量非常大

简单工厂改进

基本介绍

  1. 简单工厂模式是属于创建型模式,是工厂模式的一种, 简单工厂模式是由一个工厂对象决定创建出哪一种产品类的实例, 简单工厂模式是工厂模式中最简单实用的
  2. 简单工厂模式: 定义一个创建对象的类, 由这个类来统一对实例进行创建
  3. 在软件开发中, 我们会用到大量的创建某种, 某类或者某批对象时, 就会使用到工厂模式

改进思路

把创建具体披萨的任务交给工厂, 这样我们创建的时候, 只需要调用工厂的方法即可, 其调用工厂的地方是不需要修改的

改进前类图

改进后类图

这样, 我让我的订单去面对工厂, 后续改造仅限于工厂, 种类, 和调用方, 但是订单是不需要修改的, 哪怕后续扩展订单门店, 也是直接面对工厂就可以了, 不必关注细节

代码

增加工厂类

代码语言:javascript复制
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

代码语言:javascript复制
/**
 * 订购类
 */
class OrderPizza{
    public OrderPizza(){}

    public static void ding(String orderType){
        // 开始制作
        PizzaFactory.createPizza(orderType).buildPizza();
    }
}

后续种类再则么多, 我的订单类都是不用动的

工厂方法模式

需求演化

我们的披萨店越做越大, 终于,在不同的地区开分店了,比如北京, 上海,广州等, 这个时候顾客就可以在不同的地区点北京的奶酪披萨, 上海的藤椒披萨等

思路

使用简单工厂模式, 我们可以创建BJPizzaFactory, SHPizzaFactory等, 从这案例来说是可以的, 但是考虑项目的规模,以为软件的可维护性和可扩展性并不是特别好, 所以我们用工厂方法模式

工厂方法模式介绍

工厂方法设计方案: 将披萨项目的实例化功能抽象成抽象方法, 在不同的口味点餐子类中做具体实现

工厂方法模式: 定义了一个创建对象的抽象方法, 由子类决定要实例化的类, 工厂方法模式将对象的实例化推迟到子类

类图

代码

代码语言:javascript复制
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和子类, 但是我感觉这样也不太好, 接下来使用抽象工厂改造~

抽象工厂模式

基本介绍

  1. 抽象工厂模式:定义了一个interface用于创建相关或有依赖关系的对象簇,而无需指明具体的类
  2. 抽象工厂模式可以将简单工厂和工厂方法模式进行整合
  3. 从设计层面看, 抽象工厂模式就是对简单工厂模式的改进(或者称之为进一步抽象)
  4. 将工厂抽象成两层, ABSFactory(抽象工厂), 和 具体实现的工厂子类, 程序员可以根据创建对象类型使用对应的工厂子类, 这样将单个的简单工厂类变成了工厂簇, 更利于代码的维护和扩展

类图

订单抽象类, 面向 抽象工厂, 抽象工厂面向披萨抽象类, 细节由实现类去维护

代码

代码语言:javascript复制
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中的工厂模式

  1. JDK中的Calendar类中, 就使用了简单工厂模式

测试

代码语言:javascript复制
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, 源码就不看了, 从使用上感受一下就可以了, 额, 要不还是看一下吧

代码语言:javascript复制
public static Calendar getInstance(TimeZone zone,Locale aLocale){
  return createCalendar(zone, aLocale);
}

getInstance调用了createCalendar

代码语言:javascript复制
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;
}

好了看完了吧

工厂模式小结

工厂模式的意义

将实例化细节的代码提取出来, 放到工厂类中统一管理和维护,达到和主业务线的依赖关系的解耦,从而提高了项目的扩展和维护性

三种模式

  • 简单工厂模式
  • 工厂方法模式
  • 抽象工厂模式

设计模式的依赖抽象原则

  1. 创建对象的依赖实例时, 不要直接new 类,而是吧这个new 类的动作放在一个工厂方法中,并返回, 有的书上说变量不要直接持有具体类的引用
  2. 不要让类继承具体类,而是继承抽象类 或者实现接口
  3. 不要覆盖基类中已经实现的方法

0 人点赞