工厂模式。

Factory Pattern. When to use factory methods?

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


我喜欢从我的课程"人"的角度来思考设计模式,而这些模式是人们互相交谈的方式。

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

所以,当他们需要新员工时,他们会打电话给招聘机构,告诉他们需要什么。现在,要真正雇佣一个人,你需要知道很多东西——福利、资格认证等等,但是雇佣者不需要知道其中的任何一个——雇佣机构负责处理所有这些。

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

1
2
3
4
5
6
7
8
9
10
11
12
public interface IThingFactory
{
    Thing GetThing(string theString);
}

public class ThingFactory : IThingFactory
{
    public Thing GetThing(string theString)
    {
        return new Thing(theString, firstDependency, secondDependency);
    }
}

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


工厂方法应该被看作是一种构造函数的替代方法——主要是当构造函数表达能力不够时,即。

1
2
3
class Foo{
  public Foo(bool withBar);
}

表现力不如:

1
2
3
4
class Foo{
  public static Foo withBar();
  public static Foo withoutBar();
}

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


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

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

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
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时,不需要创建和设置每个单独的依赖项),同时集中了,这使得维护更容易。使用单独的工厂可能有益(例如,可测试性)还有其他原因,但我发现这个特定的用例最好地说明了工厂类如何有用。


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

让我们具体谈谈"工厂方法":

首先,当您开发库或API时,这些库或API又将用于进一步的应用程序开发,那么工厂方法是创建模式的最佳选择之一。原因;我们知道什么时候创建一个具有所需功能的对象,但对象的类型将保持未决定状态,或者将决定传递的对象动态参数。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
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();
    }
}

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


在以下情况下,最好在对象内部使用工厂方法:

  • 对象的类不知道它必须创建什么确切的子类
  • 对象的类被设计成它创建的对象由子类指定。
  • 对象的类将其职责委托给辅助子类,不知道哪些类将承担这些职责
  • 在以下情况下,最好使用抽象工厂类:

  • 您的对象不应该依赖于内部对象的创建和设计方式。
  • 链接对象组应一起使用,并且您需要满足此约束
  • 对象应该由作为父对象一部分的几个可能的链接对象族之一配置。
  • 需要共享仅显示接口但不显示实现的子对象

  • UML从

    enter image description here

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

    具体产品:实现产品接口

    创建者:声明工厂方法

    ConcreteCreator:实现工厂方法以返回ConcreteProduct的实例

    问题陈述:使用工厂方法创建游戏工厂,定义游戏界面。

    代码片段:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    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");
            }          
        }
    }

    输出:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    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

  • GameFactorypre在构造函数中创建不同类型的游戏。实现了IGameFactory工厂法。

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

  • GameFactory中的getGame接受一个游戏名并返回对应的Game对象。

  • 工厂:

    Creates objects without exposing the instantiation logic to the client.

    因子法

    Define an interface for creating an object, but let the subclasses decide which class to instantiate. The Factory method lets a class defer instantiation to subclasses

    用例:

    何时使用:Client不知道在运行时需要创建什么具体类,只想得到一个可以完成任务的类。


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


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

    WCF使用ServiceHostfactory类在不同情况下检索ServiceHost对象。标准ServiceHostfactory由IIS用于检索.svc文件的ServiceHost实例,但WebscriptsServiceHostfactory用于将序列化返回到javascript客户端的服务。ADO.NET数据服务有自己的专用DataServiceHostfactory,ASP.NET有自己的applicationServiceShostFactory,因为它的服务有私有构造函数。

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


    当您必须设计订单和客户类时,考虑一个场景。为了简单和初始需求,您不需要在工厂中输入order类,而是在应用程序中填充许多"new order()"语句。事情进展顺利。

    现在出现了一个新的需求,即没有客户关联(新的依赖关系),订单对象不能被实例化。现在您有以下考虑。

    1-您创建的构造函数重载只适用于新的实现。(不可接受)。2-您可以更改order()签名并更改每个调用。(不是很好的练习和真正的痛苦)。

    相反,如果您已经为订单类创建了一个工厂,那么您只需要更改一行代码就可以了。我建议对几乎所有聚合关联使用工厂类。希望有帮助。


    对于需要使用的对象,任何将对象创建延迟到其子类的类都可以看作工厂模式的一个例子。

    我在https://stackoverflow.com/a/49110001/504133上的另一个答案中详细提到了这个问题。


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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    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;
          }
    }

    抽象工厂示例。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
        TypeImpl<String> type = new TypeImpl<>();
        type.addType("Condition");
        type.addType("Hazardous");

        AbstractTypeFactory<String, Tag> tags = new AbstractTypeFactory<String, Tag>(type) {

            @Override
            public Tag create(String string) {
                String tp = type.find(string);

                switch (tp) {
                    case"Hazardous":
                        return new HazardousTag();
                    case"Condition":
                        return new ConditionTag();
                    default:
                        return null;
                }
            }
        };

        Tag tagHazardous = tags.create("Hazardous");
        Tag tagCondition = tags.create("Condition");

    }

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

    工厂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%的,但它只是一个简单的例子。我不是专家。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    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 );


    工厂课程比较重,但给你一定的优势。在需要从多个原始数据源构建对象的情况下,它们允许您在一个地方只封装构建逻辑(可能还包括数据的聚合)。在那里,可以抽象地测试它,而不涉及对象接口。

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