工厂模式。啥时候使用工厂方法?

Posted

技术标签:

【中文标题】工厂模式。啥时候使用工厂方法?【英文标题】:Factory Pattern. When to use factory methods?工厂模式。什么时候使用工厂方法? 【发布时间】:2010-09-09 08:23:58 【问题描述】:

什么时候在对象中使用工厂方法而不是工厂类是个好主意?

【问题讨论】:

见softwareengineering.stackexchange.com/questions/253254/… 对于一些不同类型的工厂的概述,请参阅:How does using the factory design pattern stop a class having to anticipate the class of objects it must create? 具体何时使用工厂方法,请参见:Applicability for the Factory Method Pattern。 【参考方案1】:

我喜欢从我的类是“人”的角度来思考设计模式,而模式是人们相互交谈的方式。

所以,对我来说,工厂模式就像一个招聘机构。你有一个需要可变数量的工人的人。此人可能知道他们所雇用的人员所需要的一些信息,但仅此而已。

因此,当他们需要新员工时,他们会致电招聘机构并告诉他们需要什么。现在,要真正雇用某人,您需要了解很多东西 - 福利、资格验证等。但雇用的人不需要知道任何这些 - 招聘机构处理所有那个。

同样,使用工厂允许消费者创建新对象,而不必知道它们是如何创建的,或者它们的依赖关系是什么的细节——他们只需要提供他们真正想要的信息。

public interface IThingFactory

    Thing GetThing(string theString);


public class ThingFactory : IThingFactory

    public Thing GetThing(string theString)
    
        return new Thing(theString, firstDependency, secondDependency);
    

所以,现在 ThingFactory 的消费者可以得到一个 Thing,而不必知道 Thing 的依赖关系,除了来自消费者的字符串数据。

【讨论】:

GetThing()的具体实现在哪里获取firstDependency和secondDependency的值? 有人能告诉我这如何回答 OP 的问题吗?这仅描述了“工厂模式”是什么,然后添加了“工厂方法”的示例,这只是三个“工厂模式”之一。换句话说,我在任何地方都看不到可比性。 OP 的问题明确提到了within an object instead of a Factory class。我认为他的意思是您将ctor设为私有并使用静态方法来实例化类(创建对象)。但要遵循这个例子,必须首先实例化 ThingFactory 类以获取 Thing 对象,这使得 Factory class 生效。 对不起,解释是废话,因为构造函数也可以写成隐藏依赖的方式。缺少您想要将依赖项创建信息与依赖项管理分开的关键背景信息。此外,问题是在同一个班级,答案与此无关。 OP 询问何时。 Kyoryyu回答了如何。虽然回答的风格值得称道,但在这个问题的背景下,它只是噪音。【参考方案2】:

应将工厂方法视为构造函数的替代方法 - 主要是在构造函数不够表达的情况下,即。

class Foo
  public Foo(bool withBar);

表达力不如:

class Foo
  public static Foo withBar();
  public static Foo withoutBar();

当您需要一个复杂的过程来构造对象时,当构造需要实际类不需要的依赖项时,当您需要构造不同的对象等时,工厂类很有用。

【讨论】:

Factory 类在哪里? @KorayTugay:没有工厂类,只有工厂方法。问题是何时使用工厂 methods 而不是工厂类。但是工厂方法更像是构造函数的替代品,而不是工厂类的替代品。 (尽管只谈论工厂类,但我不知道为什么最高答案的评价如此之高)。 需要注意的是,静态工厂方法与四人帮:工厂方法设计模式完全不同。【参考方案3】:

我个人认为单独的工厂类有意义的一种情况是,当您尝试创建的最终对象依赖于其他几个对象时。例如,在 php 中:假设您有一个 House 对象,该对象又具有一个 Kitchen 和一个 LivingRoom 对象,而 LivingRoom 对象内部也有一个 TV 对象。

实现这一点的最简单方法是让每个对象在其构造方法上创建它们的子对象,但如果属性相对嵌套,当您的 House 创建失败时,您可能会花一些时间尝试准确隔离失败的原因。

替代方法是执行以下操作(依赖注入,如果您喜欢花哨的术语):

$TVObj = new TV($param1, $param2, $param3);
$LivingroomObj = new LivingRoom($TVObj, $param1, $param2);
$KitchenroomObj = new Kitchen($param1, $param2);
$HouseObj = new House($LivingroomObj, $KitchenroomObj);

在这里,如果创建House 的过程失败,则只有一个地方可以查看,但是每次想要一个新的House 时都必须使用这个块很不方便。进入工厂:

class HouseFactory 
    public function create() 
        $TVObj = new TV($param1, $param2, $param3);
        $LivingroomObj = new LivingRoom($TVObj, $param1, $param2);
        $KitchenroomObj = new Kitchen($param1, $param2);
        $HouseObj = new House($LivingroomObj, $KitchenroomObj);

        return $HouseObj;
    


$houseFactory = new HouseFactory();
$HouseObj = $houseFactory->create();

感谢这里的工厂,抽象了创建House 的过程(因为当您只想创建House 时,您不需要创建和设置每个依赖项)同时集中的,这使得它更容易维护。使用单独的工厂可能是有益的还有其他原因(例如可测试性),但我发现这个特定的用例可以最好地说明工厂类如何有用。

【讨论】:

怎么会有人对此进行单元测试呢?我认为在类中使用“new”关键字被认为是不好的做法,因为它不能进行单元测试。还是说工厂是该规则的一个例外? @AgmLauncher 当我开始进行单元测试时,我也有同样的问题,请查看:***.com/questions/10128780/… 没听懂。用于创建不同对象的参数究竟是如何传递给HouseFactory 类的? @Mahn,你最后不会有很多参数吗? @Pacerier 您可以根据需要决定如何建模,但您不必总是必须传递 every 参数到create 方法。例如。如果您的House 将始终具有相同类型的LivingRoom,那么将其参数硬编码在工厂类中而不是作为参数传递可能是有意义的。或者,如果您有几种 LivingRooms 并且内部有一个带有硬编码每种类型的参数的开关,您可能希望为您的 HouseFactory::create 方法提供一个 type 参数。【参考方案4】:

明确区分使用工厂或工厂方法背后的想法很重要。 两者都旨在解决相互排斥的不同类型的对象创建问题。

让我们具体说一下“工厂方法”:

首先,当您开发库或 API 以用于进一步的应用程序开发时,工厂方法是创建模式的最佳选择之一。背后的原因; 我们知道何时创建具有所需功能的对象,但对象的类型将仍未确定,否则将由传递的动态参数决定

现在的重点是,使用工厂模式本身可以实现大致相同的效果,但是如果将工厂模式用于上述突出的问题,则会在系统中引入一个巨大的缺点,那就是您创建不同对象的逻辑(子类对象)将特定于某些业务条件,因此将来当您需要为其他平台扩展库的功能时(从技术上讲,您需要添加更多基本接口或抽象类的子类,因此工厂也会返回这些对象到基于一些动态参数的现有的)然后每次您需要更改(扩展)工厂类的逻辑,这将是昂贵的操作并且从设计角度来看并不好。 另一方面,如果“工厂方法”模式将用于执行相同的操作,那么您只需要创建额外的功能(子类)并通过注入动态注册它,这不需要更改您的基本代码。

interface Deliverable 

    /*********/


abstract class DefaultProducer 


    public void taskToBeDone() 
       
        Deliverable deliverable = factoryMethodPattern();
    
    protected abstract Deliverable factoryMethodPattern();


class SpecificDeliverable implements Deliverable 

 /***SPECIFIC TASK CAN BE WRITTEN HERE***/


class SpecificProducer extends DefaultProducer 

    protected Deliverable factoryMethodPattern() 
    
        return new SpecificDeliverable();
    


public class MasterApplicationProgram 

    public static void main(String arg[]) 
    
        DefaultProducer defaultProducer = new SpecificProducer();
        defaultProducer.taskToBeDone();
    

【讨论】:

【参考方案5】:

当您需要多个具有相同参数类型但行为不同的“构造函数”时,它们也很有用。

【讨论】:

【参考方案6】:

在以下情况下使用工厂方法是个好主意:

    对象的类不知道它必须创建哪些确切的子类 对象的类被设计成它创建的对象由子类指定 对象的类将其职责委托给辅助子类,但不知道具体哪个类将承担这些职责

在以下情况下使用抽象工厂类是个好主意:

    您的对象不应依赖于其内部对象的创建和设计方式 应一起使用一组链接对象,并且您需要满足此约束 对象应由几个可能的链接对象系列之一配置,这些链接对象将成为您的父对象的一部分 需要共享仅显示接口而不是实现的子对象

【讨论】:

【参考方案7】:

UML 来自

产品:它定义了工厂方法创建的对象的接口。

ConcreteProduct:实现产品接口

创建者:声明工厂方法

ConcreateCreator: 实现 Factory 方法以返回 ConcreteProduct 的实例

问题说明:使用定义游戏界面的工厂方法创建游戏工厂。

代码sn-p:

import java.util.HashMap;


/* Product interface as per UML diagram */
interface Game
    /* createGame is a complex method, which executes a sequence of game steps */
    public void createGame();


/* ConcreteProduct implementation as per UML diagram */
class Chess implements Game
    public Chess()

    
    public void createGame()
        System.out.println("---------------------------------------");
        System.out.println("Create Chess game");
        System.out.println("Opponents:2");
        System.out.println("Define 64 blocks");
        System.out.println("Place 16 pieces for White opponent");
        System.out.println("Place 16 pieces for Black opponent");
        System.out.println("Start Chess game");
        System.out.println("---------------------------------------");
    

class Checkers implements Game
    public Checkers()

    
    public void createGame()
        System.out.println("---------------------------------------");
        System.out.println("Create Checkers game");
        System.out.println("Opponents:2 or 3 or 4 or 6");
        System.out.println("For each opponent, place 10 coins");
        System.out.println("Start Checkers game");
        System.out.println("---------------------------------------");
    

class Ludo implements Game
    public Ludo()

    
    public void createGame()
        System.out.println("---------------------------------------");
        System.out.println("Create Ludo game");
        System.out.println("Opponents:2 or 3 or 4");
        System.out.println("For each opponent, place 4 coins");
        System.out.println("Create two dices with numbers from 1-6");
        System.out.println("Start Ludo game");
        System.out.println("---------------------------------------");
    


/* Creator interface as per UML diagram */
interface IGameFactory 
    public Game getGame(String gameName);


/* ConcreteCreator implementation as per UML diagram */
class GameFactory implements IGameFactory 

     HashMap<String,Game> games = new HashMap<String,Game>();
    /*  
        Since Game Creation is complex process, we don't want to create game using new operator every time.
        Instead we create Game only once and store it in Factory. When client request a specific game, 
        Game object is returned from Factory instead of creating new Game on the fly, which is time consuming
    */

    public GameFactory()

        games.put(Chess.class.getName(),new Chess());
        games.put(Checkers.class.getName(),new Checkers());
        games.put(Ludo.class.getName(),new Ludo());        
    
    public Game getGame(String gameName)
        return games.get(gameName);
    


public class NonStaticFactoryDemo
    public static void main(String args[])
        if ( args.length < 1)
            System.out.println("Usage: java FactoryDemo gameName");
            return;
        

        GameFactory factory = new GameFactory();
        Game game = factory.getGame(args[0]);
        if ( game != null )                    
            game.createGame();
            System.out.println("Game="+game.getClass().getName());
        else
            System.out.println(args[0]+  " Game does not exists in factory");
                   
    

输出:

java NonStaticFactoryDemo Chess
---------------------------------------
Create Chess game
Opponents:2
Define 64 blocks
Place 16 pieces for White opponent
Place 16 pieces for Black opponent
Start Chess game
---------------------------------------
Game=Chess

此示例通过实现FactoryMethod 来展示Factory 类。

    Game 是所有类型游戏的接口。它定义了复杂的方法:createGame()

    Chess, Ludo, Checkers 是游戏的不同变体,它们为createGame() 提供实现

    public Game getGame(String gameName)IGameFactory 类中是FactoryMethod

    GameFactory 在构造函数中预先创建不同类型的游戏。它实现了IGameFactory 工厂方法。

    游戏名称作为命令行参数传递给NotStaticFactoryDemo

    getGame in GameFactory 接受游戏名称并返回对应的Game 对象。

工厂:

创建对象而不向客户端公开实例化逻辑。

工厂方法

定义一个用于创建对象的接口,但让子类决定实例化哪个类。 Factory 方法允许类将实例化推迟到子类

用例:

何时使用:Client 不知道在运行时需要创建哪些具体的类,但只是想获得一个可以完成这项工作的类。

【讨论】:

谢谢你的主题演讲部分,它对我来说很简洁。但是,“getArea() 是 Shape 接口中的工厂方法”是不可想象的,因为 getArea 方法从不实例化任何类,它只是做请查看“定义用于创建对象的接口,但让子类决定要实例化哪个类”的计算。 getArea() 根本不是工厂方法 我有不同的意见 - 请专家验证并添加注释。 1. 客户端(或调用者)需要 Object of Interest ...因此不需要调用 new GameFactory() 而 Factory 类应该有一个静态 getInstance() 2.如果是这样,那么 games.put(Chess.class.getName(),new Chess()); 将始终返回 Chess 的相同引用 [如果实现为 static ] - 如何最有效地处理该场景? 我已经给出了非静态工厂的例子。如果需要,您可以使用静态块和方法来实现它。关于您的查询:1。客户将致电工厂获取游戏。 2. 我将对象放置一次,所有 Get 都将返回相同的实例 - 每次 get 都会返回相同的 Chess 引用【参考方案8】:

这真的是一个品味问题。工厂类可以根据需要进行抽象/接口,而工厂方法的重量更轻(并且也倾向于可测试,因为它们没有定义的类型,但它们需要一个众所周知的注册点,类似于服务定位器,但用于定位工厂方法)。

【讨论】:

【参考方案9】:

当它们返回的对象类型具有私有构造函数时,当不同的工厂类在返回的对象上设置不同的属性时,或者当特定的工厂类型与其返回的具体类型耦合时,工厂类很有用。

WCF 使用 ServiceHostFactory 类在不同情况下检索 ServiceHost 对象。 IIS 使用标准的 ServiceHostFactory 来检索 .svc 文件的 ServiceHost 实例,但 WebScriptServiceHostFactory 用于将序列化返回给 javascript 客户端的服务。 ADO.NET 数据服务有自己特殊的 DataServiceHostFactory,而 ASP.NET 有自己的 ApplicationServicesHostFactory,因为它的服务有私有构造函数。

如果您只有一个使用工厂的类,那么您可以在该类中使用工厂方法。

【讨论】:

【参考方案10】:

假设您必须设计一个 Order 和 Customer 类。为了简单和初始要求,您不需要 Order 类的工厂,并使用许多“new Order()”语句填充您的应用程序。事情进展顺利。

现在出现了一个新要求,即在没有客户关联的情况下无法实例化 Order 对象(新依赖项)。现在你有以下考虑。

1- 您创建仅适用于新实现的构造函数重载。 (不能接受的)。 2-您更改 Order() 签名并更改每个调用。 (不是一个好的做法和真正的痛苦)。

相反,如果您为 Order Class 创建了工厂,则只需更改一行代码即可。我建议几乎每个聚合关联都使用 Factory 类。希望对您有所帮助。

【讨论】:

【参考方案11】:

如果您想在使用方面创建不同的对象。很有用。

public class factoryMethodPattern 
      static String planName = "COMMERCIALPLAN";
      static int units = 3;
      public static void main(String args[]) 
          GetPlanFactory planFactory = new GetPlanFactory();
          Plan p = planFactory.getPlan(planName);
          System.out.print("Bill amount for " + planName + " of  " + units
                        + " units is: ");
          p.getRate();
          p.calculateBill(units);
      


abstract class Plan 
      protected double rate;

      abstract void getRate();

      public void calculateBill(int units) 
            System.out.println(units * rate);
      


class DomesticPlan extends Plan 
      // @override
      public void getRate() 
            rate = 3.50;
      


class CommercialPlan extends Plan 
      // @override
      public void getRate() 
            rate = 7.50;
      


class InstitutionalPlan extends Plan 
      // @override
      public void getRate() 
            rate = 5.50;
      


class GetPlanFactory 

      // use getPlan method to get object of type Plan
      public Plan getPlan(String planType) 
            if (planType == null) 
                  return null;
            
            if (planType.equalsIgnoreCase("DOMESTICPLAN")) 
                  return new DomesticPlan();
             else if (planType.equalsIgnoreCase("COMMERCIALPLAN")) 
                  return new CommercialPlan();
             else if (planType.equalsIgnoreCase("INSTITUTIONALPLAN")) 
                  return new InstitutionalPlan();
            
            return null;
      

【讨论】:

【参考方案12】:

任何将对象创建推迟到其需要处理的对象的子类的类都可以被视为工厂模式的示例。

我在https://***.com/a/49110001/504133的另一个答案中已经详细提到了

【讨论】:

【参考方案13】:

我认为这取决于您想要为代码带来的松散耦合程度。

工厂方法很好地解耦了事情,但是工厂类没有。

换句话说,使用工厂方法比使用简单工厂(称为工厂类)更容易更改。

看看这个例子:https://connected2know.com/programming/java-factory-pattern/。现在,想象一下你想带一个新的动物。在 Factory 类中,您需要更改 Factory,但在工厂方法中,不,您只需要添加一个新的子类。

【讨论】:

【参考方案14】:

工厂类更重量级,但给你一些优势。如果您需要从多个原始数据源构建对象,它们允许您仅将构建逻辑(可能还有数据的聚合)封装在一个地方。在那里可以进行抽象测试,而不用关心对象接口。

我发现这是一种有用的模式,特别是在我无法替换和不足的 ORM 并希望从 DB 表连接或存储过程中有效地实例化许多对象的情况下。

【讨论】:

【参考方案15】:

我将工厂比作图书馆的概念。例如,您可以有一个用于处理数字的库和另一个用于处理形状的库。您可以将这些库的函数存储在逻辑命名的目录中,如NumbersShapes。这些是通用类型,在形状的情况下可以包括整数、浮点数、dobules、long 或矩形、圆形、三角形、五边形。

工厂petter使用多态、依赖注入和控制反转。

工厂模式的目的是:Define an interface for creating an object, but let subclasses decide which class to instantiate. Factory Method lets a class defer instantiation to subclasses.

假设您正在构建一个操作系统或框架,并且您正在构建所有离散组件。

这里是 PHP 中工厂模式概念的一个简单示例。我可能不是 100% 的,但它的目的是作为一个简单的例子。我不是专家。

class NumbersFactory 
    public static function makeNumber( $type, $number ) 
        $numObject = null;
        $number = null;

        switch( $type ) 
            case 'float':
                $numObject = new Float( $number );
                break;
            case 'integer':
                $numObject = new Integer( $number );
                break;
            case 'short':
                $numObject = new Short( $number );
                break;
            case 'double':
                $numObject = new Double( $number );
                break;
            case 'long':
                $numObject = new Long( $number );
                break;
            default:
                $numObject = new Integer( $number );
                break;
        

        return $numObject;
    


/* Numbers interface */
abstract class Number 
    protected $number;

    public function __construct( $number ) 
        $this->number = $number;
    

    abstract public function add();
    abstract public function subtract();
    abstract public function multiply();
    abstract public function divide();

/* Float Implementation */
class Float extends Number 
    public function add() 
        // implementation goes here
    

    public function subtract() 
        // implementation goes here
    

    public function multiply() 
        // implementation goes here
    

    public function divide() 
        // implementation goes here
    

/* Integer Implementation */
class Integer extends Number 
    public function add() 
        // implementation goes here
    

    public function subtract() 
        // implementation goes here
    

    public function multiply() 
        // implementation goes here
    

    public function divide() 
        // implementation goes here
    

/* Short Implementation */
class Short extends Number 
    public function add() 
        // implementation goes here
    

    public function subtract() 
        // implementation goes here
    

    public function multiply() 
        // implementation goes here
    

    public function divide() 
        // implementation goes here
    

/* Double Implementation */
class Double extends Number 
    public function add() 
        // implementation goes here
    

    public function subtract() 
        // implementation goes here
    

    public function multiply() 
        // implementation goes here
    

    public function divide() 
        // implementation goes here
    

/* Long Implementation */
class Long extends Number 
    public function add() 
        // implementation goes here
    

    public function subtract() 
        // implementation goes here
    

    public function multiply() 
        // implementation goes here
    

    public function divide() 
        // implementation goes here
    


$number = NumbersFactory::makeNumber( 'float', 12.5 );

【讨论】:

我明白这里发生了什么,但我不明白它的意义何在。如果我知道我需要一个Float,那么NumbersFactory::makeNumber( 'float', 12.5 ); 是什么让我只说new Float(12.5);?这就是我对工厂的不理解......有什么意义? 它允许您选择不同的实现,而不是只将您绑定到一个。接口已建立,所有实现都必须保证并遵守它。【参考方案16】:

我的简短解释是,当我们没有足够的信息来创建具体对象时,我们会使用工厂模式。我们要么不知道依赖关系,要么不知道对象的类型。而且我们几乎总是不知道它们,因为这是在运行时获得的信息。

示例:我们知道我们必须创建一个车辆对象,但我们不知道它是飞行还是在地面上工作。

【讨论】:

【参考方案17】:

GOF定义:

定义一个用于创建对象的接口,但让子类决定实例化哪个类。工厂方法允许类将实例化推迟到子类。

通用示例:

public abstract class Factory<T> 

    public abstract T instantiate(Supplier<? extends T> supplier);


具体类

public class SupplierFactory<T> extends Factory<T> 

    @Override
    public T instantiate(Supplier<? extends T> supplier) 
        return supplier.get();
    

实现

public class Alpha implements BaseInterface 
    @Override
    public void doAction() 
        System.out.println("The Alpha executed");
    


public class Beta implements BaseInterface 
    @Override
    public void doAction() 
        System.out.println("The Beta executed");
    


public interface BaseInterface 
    void doAction();


public class Main 
    public static void main(String[] args) 
        Factory<BaseInterface> secondFactory = new SupplierFactory<>();
        secondFactory.instantiate(Beta::new).doAction();
        secondFactory.instantiate(Alpha::new).doAction();
    

简要优点

您正在分离可以变化的代码和不变的代码(即,使用简单工厂模式的优势仍然存在)。此技术可帮助您轻松维护代码。 您的代码没有紧密耦合;因此,您可以随时在系统中添加新的类,如 Lion、Beer 等,而无需修改现有架构。因此,您遵循了“封闭修改,开放扩展”的原则。

【讨论】:

【参考方案18】:

想象一下,您有不同的客户,他们有不同的偏好。有人需要大众另一辆奥迪等等。有一件事很常见 - 它是一辆车。

为了让我们的客户满意,我们需要一家工厂。工厂只应该知道客户想要哪辆汽车,并将这种汽车交付给客户。如果以后我们有另一辆车,我们可以轻松地扩展我们的停车场和我们的工厂。

您可以在下面看到一个示例 (ABAP):

现在我们将创建一个工厂实例并倾听客户的意愿。

我们只用一个 create() 方法创建了三辆不同的汽车。

结果:

如果你想使逻辑更清晰,程序更可扩展,工厂模式通常非常有用。

【讨论】:

以上是关于工厂模式。啥时候使用工厂方法?的主要内容,如果未能解决你的问题,请参考以下文章

java中啥情况下采用工厂类?工厂类的作用是啥?

设计模式-抽象工厂模式

23种设计模式——工厂方法模式对象创建

何时使用工厂方法模式?

工厂模式以及代理模式

嵌入式C语言设计模式 --- 工厂方法模式