草庐IT

初识设计模式 - 工厂模式

程序员翔仔 2023-03-28 原文

简介

工厂设计模式(Factory Design Pattern)是一种创建型的设计模式,它提供了一种创建对象的最佳方式,是一种代替 new 操作符的一种模式。

在工厂模式中,创建对象不会对客户端暴露创建逻辑,而是通过使用一个共同的接口来指向新创建的对象。

工厂模式还可以细分为三种的类型:简单工厂模式、工厂方法模式和抽象工厂模式。

简单工厂模式

概念

简单工厂模式(Simple Factory Pattern)的定义是,由一个工厂对象决定创建出哪一种产品类的实例,被创建的产品类实例具有共同的父类或实现同样的接口。

因为在简单工厂模式中用于创建实例的方法通常是静态方法,因此简单工厂模式又被称为静态工厂模式(Static Factory Pattern)。

实现方式

下面是使用果汁生产来展示简单工厂模式:

果汁 FruitJuice 接口:描述生产果汁的必要方法

public interface FruitJuice {
    void make();
}

苹果汁 AppleJuice 类:生产苹果汁的类

public class AppleJuice implements FruitJuice {
    public AppleJuice() {
        this.make();
    }

    @Override
    public void make() {
        System.out.println("This is AppleJuice make!");
    }
}

橙汁 OrangeJuice 类:生产橙汁的类

public class OrangeJuice implements FruitJuice {
    public OrangeJuice() {
        this.make();
    }

    @Override
    public void make() {
        System.out.println("This is OrangeJuice make!");
    }
}

果汁工厂 FruitJuiceFactory 类:负责生产各种果汁的类

public class FruitJuiceFactory {
    public FruitJuiceFactory() {}

    public static FruitJuice createFruitJuice(String juiceType) {
        FruitJuice fruitJuice = null;
        if (juiceType.equals("AppleJuice")) {
            fruitJuice = new AppleJuice();
        } else if (juiceType.equals("OrangeJuice")) {
            fruitJuice = new OrangeJuice();
        }
        return fruitJuice;
    }
}

通过 if-else 逻辑是简单工厂的第一种实现方法,还可以通过静态代码块和 Map 结构实现简单工厂模式,具体的代码示例如下:

import java.util.Map;
import java.util.HashMap;

public class FruitJuiceFactory {
    // 存储产品类的映射关系
    private static final Map<String, FruitJuice> cachedFruitJuice = new HashMap<>();

    static {
        cachedFruitJuice.put("AppleJuice", new AppleJuice());
        cachedFruitJuice.put("OrangeJuice", new OrangeJuice());
    }

    public FruitJuiceFactory() {}

    public static FruitJuice createFruitJuice(String juiceType) {
        if (juiceType == null || juiceType.isEmpty()) {
            return null;
        }
        // 直接从 Map 结构中取到对应的产品类实例
        return cachedFruitJuice.get(juiceType.toLowerCase());
    }
}

优点

简单工厂模式的主要优点如下:

  • 简单工厂模式实现了对象创建和使用的分离
  • 客户端无需知道所创建的具体产品类的类名,只需要知道具体产品类所对应的参数即可
  • 可以在不改变客户端代码的情况下更换或增加新的具体产品类

缺点

简单工厂模式的主要缺点如下:

  • 工厂类集中了相似产品类的创建逻辑,职责过重
  • 简单工厂模式违反开闭原则,新增产品类时需要改动到工厂类
  • 通常使用静态方法作为创建实例的方法,无法实现继承关系

适用场景

简单工厂模式的适用场景如下:

  • 对于一批产品类,并且不会新增产品类,可以选择简单工厂模式

源码

在 JDK 中,java.util.Calendar 使用了简单工厂模式。如下是其的一些实现逻辑:

public abstract class Calendar implements Serializable, Cloneable, Comparable<Calendar> {
    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) {
                cal = switch (caltype) {
                    case "buddhist" -> new BuddhistCalendar(zone, aLocale);
                    case "japanese" -> new JapaneseImperialCalendar(zone, aLocale);
                    case "gregory"  -> new GregorianCalendar(zone, aLocale);
                    default         -> null;
                };
            }
        }
        if (cal == null) {
            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;
    }
}

工厂方法模式

概念

简单工厂模式违背了开闭原则,没有能够做到“对扩展开放、对修改关闭”,新增产品类时需要改动到工厂类。

而工厂方法模式(Factory Method Pattern)是对简单工厂模式的进一步抽象,其好处是可以使系统在不修改原来代码的情况下引入新的产品类,即满足开闭原则。

和简单工厂模式中工厂负责生产所有的产品相比,工厂方法模式抽象出简单工厂的接口,然后将生产具体产品的任务分发给具体的产品工厂,即简单工厂的工厂。

由于工厂方法模式利用了面向对象的多态特性,因此又被称为多态工厂模式(Polymorphic Factory Pattern)。

实现方式

在上述简单工厂模式的基础上,将果汁生产改造成工厂方法模式:

抽象工厂 AbstractFactory 接口:描述生产果汁工厂的必要方法

public interface FruitJuiceFactory {
    FruitJuice createFruitJuice();
}

苹果汁工厂 AppleJuiceFactory 类:详细的生产苹果汁的类

public class AppleJuiceFactory implements FruitJuiceFactory {
    public AppleJuiceFactory() {}

    @Override
    public FruitJuice createFruitJuice() {
        return new AppleJuice();
    }
}

橙汁工厂 OrangeJuiceFactory 类:详细的生产橙汁的类

public class OrangeJuiceFactory implements FruitJuiceFactory {
    public OrangeJuiceFactory() {}

    @Override
    public FruitJuice createFruitJuice() {
        return new OrangeJuice();
    }
}

优点

工厂方法模式除了具有简单工厂模式的优点之外,还有以下优点:

  • 在系统中增加新的产品类时,无需修改原有的工厂代码,只需添加一个具体产品类和具体工厂类

缺点

工厂方法模式的主要缺点如下:

  • 添加新的产品类时,需要同时提供具体产品类和对应的工厂类,系统中类的个数将成对增加
  • 工厂方法模式引入了抽象层,增加了系统的抽象性和理解难度

适用场景

工厂方法模式的适用场景如下:

  • 客户端知道其需要的接口实现类,不知道所需要的具体对象类,可以通过对应的工厂创建实例

源码

在 JDK 中,java.util.Collection 接口中定义的 iterator() 方法就是一个工厂方法。

所有实现了 Collection 接口的类,都需要显式地实现此方法并返回一个 Iterator 实例,不同的实现类可以拥有自己的实现逻辑。

抽象工厂模式

概念

在工厂方法模式中,具体工厂负责生产具体的产品,每个具体工厂对应一种具体产品,工厂方法具有唯一性。

抽象工厂模式(Abstract Factory Pattern)定义了一个接口用于创建相关或有依赖关系的对象族,而无需指明具体的类,其可以整合简单工厂模式和抽象工厂模式。

学习抽象工厂模式需要理解两个概念:产品等级结构指的是产品的继承结构,如抽象果汁和具体果汁之间构成一个产品等级结构;产品族指的是由同一个工厂生产的,位于不同等级结构中的一组产品,如同一个饮料工厂生产的酒和果汁构成一个产品族。

从上述概念上理解,工厂方法模式针对的是一个产品等级结构,而抽象工厂模式在工厂方法模式的基础上,覆盖了多个工厂的产品族。

实现方式

在上述简单工厂模式的基础上,下面使用果汁生产和酒生产来展示抽象工厂模式:

Alcohol 接口:描述生产酒的必要方法

public interface Alcohol {
    void make();
}

葡萄酒 Wine 类:详细的生产葡萄酒的类

public class Wine implements Alcohol {
    public Wine() {
        this.make();
    }

    @Override
    public void make() {
        System.out.println("This is Wine make!");
    }
}

啤酒 Beer 类:详细的生产啤酒的类

public class Beer implements Alcohol {
    public Beer() {
        this.make();
    }

    @Override
    public void make() {
        System.out.println("This is Beer make!");
    }
}

抽象工厂 AbstractFactory 接口:描述生产果汁和酒的必要方法

public interface AbstractFactory {
    FruitJuice creatFruitJuice();
    Alcohol createAlcohol();
}

具体工厂 ConcreteFactory 类:可生产果汁和酒的工厂的类

public class ConcreteFactory implements AbstractFactory {
    public ConcreteFactory() {}

    @Override
    public FruitJuice creatFruitJuice(String juiceType) {
        FruitJuice fruitJuice = null;
        if (juiceType.equals("AppleJuice")) {
            fruitJuice = new AppleJuice();
        } else if (juiceType.equals("OrangeJuice")) {
            fruitJuice = new OrangeJuice();
        }
        return fruitJuice;
    }

    @Override
    public Alcohol createAlcohol(String alcoholType) {
        Alcohol alcohol = null;
        if (alcoholType.equals("Wine")) {
            alcohol = new Wine();
        } else if (alcoholType.equals("Beer")) {
            alcohol = new Beer();
        }
        return alcohol;
    }
}

优点

抽象工厂模式的主要优点如下:

  • 当一个产品族中的多个对象被设计成一起工作时,它能够保证客户端始终只使用同一个产品族中的对象
  • 增加新的产品族很方便,无需修改已有代码,符合开闭原则

缺点

抽象工厂模式的主要缺点如下:

  • 增加新的产品等级结构麻烦,需要对原有系统进行较大的修改,甚至需要修改抽象层代码

适用场景

抽象工厂模式的适用场景如下:

  • 当需要创建的对象是一系列相互关联或相互依赖的产品族时,便可以使用抽象工厂模式
  • 产品等级结构稳定,设计完成之后,不会向系统中增加新的产品等级结构或者删除已有的产品等级结构

源码

在 JDK 中,java.util.Collection 接口可以看作一个抽象工厂。

Collection 接口定义了转换成 Iterator 实例的工厂方法,也定义了转换成 T[] 实例的工厂方法,可以认定其做了两个产品族的定义。

总结

使用标准

对于要不要使用工厂模式,其最本质的参考标准有以下四个:

  • 封装变化:创建逻辑有可能变化,封装成工厂类之后,创建逻辑的变更对调用者透明
  • 代码复用:创建代码抽离到独立的工厂类之后可以复用
  • 隔离复杂性:封装复杂的创建逻辑,调用者无需了解如何创建对象
  • 控制复杂度:将创建代码抽离出来,让原本的函数或类职责更单一,代码更简洁

应用场景

对于产品种类相对较少、且可预见性地不会修改的情况,可以使用简单工厂模式。使用简单工厂模式的客户端只需传入工厂类的参数,不需要关心如何创建对象的逻辑,可以很方便地创建所需产品。

由于简单工厂模式会将所有创建逻辑都放在一个工厂类中,会导致这个工厂类会变得很复杂,当产品的种类是可预见地会增加时,还需要对工厂类做更改,这种时候可以采用工厂方法模式以达到不需要修改原来代码的情况下引进新的产品。

抽象工厂模式最早的应用是用于创建属于不同操作系统的视窗构件。当需要创建的对象是一系列相互关联或相互依赖的产品族时,或者是只会新增新的产品族而不是新种类的产品时,可以使用抽象工厂模式。

有关初识设计模式 - 工厂模式的更多相关文章

  1. ruby-on-rails - Rails - 子类化模型的设计模式是什么? - 2

    我有一个模型:classItem项目有一个属性“商店”基于存储的值,我希望Item对象对特定方法具有不同的行为。Rails中是否有针对此的通用设计模式?如果方法中没有大的if-else语句,这是如何干净利落地完成的? 最佳答案 通常通过Single-TableInheritance. 关于ruby-on-rails-Rails-子类化模型的设计模式是什么?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.co

  2. ruby-on-rails - Railstutorial : db:populate vs. 工厂女孩 - 2

    在railstutorial中,作者为什么选择使用这个(代码list10.25):http://ruby.railstutorial.org/chapters/updating-showing-and-deleting-usersnamespace:dbdodesc"Filldatabasewithsampledata"task:populate=>:environmentdoRake::Task['db:reset'].invokeUser.create!(:name=>"ExampleUser",:email=>"example@railstutorial.org",:passwo

  3. ruby - 解析 RDFa、微数据等的最佳方式是什么,使用统一的模式/词汇(例如 schema.org)存储和显示信息 - 2

    我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i

  4. ruby - 如何在续集中重新加载表模式? - 2

    鉴于我有以下迁移:Sequel.migrationdoupdoalter_table:usersdoadd_column:is_admin,:default=>falseend#SequelrunsaDESCRIBEtablestatement,whenthemodelisloaded.#Atthispoint,itdoesnotknowthatusershaveais_adminflag.#Soitfails.@user=User.find(:email=>"admin@fancy-startup.example")@user.is_admin=true@user.save!ende

  5. ruby-on-rails - 使用 rails 4 设计而不更新用户 - 2

    我将应用程序升级到Rails4,一切正常。我可以登录并转到我的编辑页面。也更新了观点。使用标准View时,用户会更新。但是当我添加例如字段:name时,它​​不会在表单中更新。使用devise3.1.1和gem'protected_attributes'我需要在设备或数据库上运行某种更新命令吗?我也搜索过这个地方,找到了许多不同的解决方案,但没有一个会更新我的用户字段。我没有添加任何自定义字段。 最佳答案 如果您想允许额外的参数,您可以在ApplicationController中使用beforefilter,因为Rails4将参数

  6. ruby - 是否有用于序列化和反序列化各种格式的对象层次结构的模式? - 2

    给定一个复杂的对象层次结构,幸运的是它不包含循环引用,我如何实现支持各种格式的序列化?我不是来讨论实际实现的。相反,我正在寻找可能会派上用场的设计模式提示。更准确地说:我正在使用Ruby,我想解析XML和JSON数据以构建复杂的对象层次结构。此外,应该可以将该层次结构序列化为JSON、XML和可能的HTML。我可以为此使用Builder模式吗?在任何提到的情况下,我都有某种结构化数据-无论是在内存中还是文本中-我想用它来构建其他东西。我认为将序列化逻辑与实际业务逻辑分开会很好,这样我以后就可以轻松支持多种XML格式。 最佳答案 我最

  7. LC滤波器设计学习笔记(一)滤波电路入门 - 2

    目录前言滤波电路科普主要分类实际情况单位的概念常用评价参数函数型滤波器简单分析滤波电路构成低通滤波器RC低通滤波器RL低通滤波器高通滤波器RC高通滤波器RL高通滤波器部分摘自《LC滤波器设计与制作》,侵权删。前言最近需要学习放大电路和滤波电路,但是由于只在之前做音乐频谱分析仪的时候简单了解过一点点运放,所以也是相当从零开始学习了。滤波电路科普主要分类滤波器:主要是从不同频率的成分中提取出特定频率的信号。有源滤波器:由RC元件与运算放大器组成的滤波器。可滤除某一次或多次谐波,最普通易于采用的无源滤波器结构是将电感与电容串联,可对主要次谐波(3、5、7)构成低阻抗旁路。无源滤波器:无源滤波器,又称

  8. 计算机毕业设计ssm+vue基本微信小程序的小学生兴趣延时班预约小程序 - 2

    项目介绍随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱小学生兴趣延时班预约小程序的设计与开发被用户普遍使用,为方便用户能够可以随时进行小学生兴趣延时班预约小程序的设计与开发的数据信息管理,特开发了小程序的设计与开发的管理系统。小学生兴趣延时班预约小程序的设计与开发的开发利用现有的成熟技术参考,以源代码为模板,分析功能调整与小学生兴趣延时班预约小程序的设计与开发的实际需求相结合,讨论了小学生兴趣延时班预约小程序的设计与开发的使用。开发环境开发说明:前端使用微信微信小程序开发工具:后端使用ssm:VU

  9. ruby-on-rails - environment.rb 中设置的常量在开发模式中消失 - 2

    了解Rails缓存如何工作的人可以真正帮助我。这是嵌套在Rails::Initializer.runblock中的代码:config.after_initializedoSomeClass.const_set'SOME_CONST','SOME_VAL'end现在,如果我运行script/server并发出请求,一切都很好。然而,在我的Rails应用程序的第二个请求中,一切都因单元化常量错误而变得糟糕。在生产模式下,我可以成功发出第二个请求,这意味着常量仍然存在。我已通过将以上内容更改为以下内容来解决问题:config.after_initializedorequire'some_cl

  10. ruby-on-rails - 设计注册确认 - 2

    我在我的项目中有一个用户和一个管理员角色。我使用Devise创建了身份验证。在我的管理员角色中,我没有任何确认。在我的用户模型中,我有以下内容:devise:database_authenticatable,:confirmable,:recoverable,:rememberable,:trackable,:validatable,:timeoutable,:registerable#Setupaccessible(orprotected)attributesforyourmodelattr_accessible:email,:username,:prename,:surname,:

随机推荐