热门问题
时间线
聊天
视角

工厂方法模式

来自维基百科,自由的百科全书

工厂方法模式
Remove ads

工厂方法模式(英语:factory method pattern),是一种实现了工厂概念的面向对象设计模式。就像其他创建型模式一样,它也是处理在不指定对象具体类型的情况下创建对象的问题。

Thumb
UML描述的工厂方法模式

概述

对象创建中的有些过程包括决定创建哪个对象、管理对象的生命周期,以及管理特定对象的建立和销毁的概念。工厂方法模式的实质是“定义一个创建对象的接口,但让实现这个接口的类来决定实例化哪个类。工厂方法让类的实例化推迟到子类中进行。”[1]

创建一个对象常常需要复杂的过程,所以不适合包含在一个复合对象中。创建对象可能会导致大量的重复代码,可能会需要复合对象访问不到的信息,也可能提供不了足够级别的抽象,还可能并不是复合对象概念的一部分。工厂方法模式通过定义一个单独的创建对象的方法来解决这些问题。由子类实现这个方法来创建具体类型的对象。

结构

Thumb
工厂方法模式的样例UML类图[2]

在上面的UML类图中,Creator类要求Product对象,但不直接的实例化Product1类。Creator转而提及一个独立的factoryMethod()来创建一个产品对象,这使得Creator独立于被实例化的确切的具体类。Creator的子类可以精制(redefine)出哪个类要被实例化。在这个例子中,Creator1子类通过实例化Product1类来实现抽象的factoryMethod()

代码举例

Java

下面的迷路园游戏Java例子,类似于《设计模式》中的例子,这里针对迷路园生成算法英语Maze generation algorithm而简化了对房间的四面、门和墙的处置,即一个房间与毗邻的另一个房间,如果二者连通则其间有开着的一个门,如果二者不连通则其间有一个墙;永远开着的门通常就表现为房间的这一面没有墙,如果要将门具象表现出来则附含着其所处在的墙。

Thumb

抽象类MazeGame连通多个Room对象,但是将创建Room对象的责任委托给了作为具体类的它的子类。MazeGame()构造器是增加一些公用逻辑的模板方法,其中涉及了作为虚方法makeRoom()工厂方法,它在MazeGame的子类中被覆写,从而可以使用上各种不同的房间:

public abstract class Room {
    abstract void connect(Room room);
}

public class MagicRoom extends Room {
    public void connect(Room room) {}
}

public class OrdinaryRoom extends Room {
    public void connect(Room room) {}
}

public abstract class MazeGame {
    private final List<Room> rooms = new ArrayList<>();
    abstract protected Room makeRoom();
    public MazeGame() {
        Room room1 = makeRoom();
        Room room2 = makeRoom();
        room1.connect(room2);
        rooms.add(room1);
        rooms.add(room2);
    }
}

public class MagicMazeGame extends MazeGame {
    @Override
    protected MagicRoom makeRoom() {
        return new MagicRoom();
    }
}

public class OrdinaryMazeGame extends MazeGame {
    @Override
    protected OrdinaryRoom makeRoom() {
        return new OrdinaryRoom();
    }
}

MazeGame ordinaryGame = new OrdinaryMazeGame();
MazeGame magicGame = new MagicMazeGame();
Remove ads

Python

Python迷路园游戏例子:

from abc import ABC, abstractmethod

class Room(ABC):
    descr = None
    params = {}
    def __init__(self, **kwargs):
        self.connected_rooms = []
        params = type(self).params | kwargs
        for key, value in params.items():
            self.__dict__[key] = value
    def __str__(self): 
        return type(self).descr
    @abstractmethod
    def connect(self, other, **kwargs): 
        self.connected_rooms.append(other)

class OrdinaryRoom(Room):
    descr = 'Ordinary room'
    params = {'kw1': ''}
    def connect(self, other):
        super().connect(other, kw0='')

class MagicRoom(Room):
    descr = 'Magic room'
    params = {'kw1': ''}
    def connect(self, other):
        super().connect(other, kw0='')

class MazeCreatorTrait(ABC):
    def __init__(self):
        self.rooms = []
        room1 = self.room_factory(kw2='')
        room2 = self.room_factory(kw2='')
        room1.connect(room2)
        self.rooms.append(room1)
        self.rooms.append(room2)
    @abstractmethod
    def room_factory(self): pass
    def play(self):
        print(f"Playing using {self.rooms[0]}")

class MazeCreator(MazeCreatorTrait):
    def __init__(self, creator_cls=OrdinaryRoom):
        self.creator_cls = creator_cls
        super().__init__()
    def room_factory(self, **kwargs):
        return self.creator_cls(**kwargs)

其执行:

>>> maze1 = MazeCreator()
>>> maze1.play()
Playing using Ordinary room
>>> maze2 = MazeCreator(MagicRoom)
>>> maze2.play()
Playing using Magic room
Remove ads

C++

设计模式》书中早于C++98的例子的C++23实现[3]

import std;

enum class ProductId {MINE, YOURS};

// defines the interface of objects the factory method creates.
class Product {
public:
    virtual void print() = 0;
    virtual ~Product() = default;
};

// implements the Product interface.
class ConcreteProductMINE: public Product {
public:
    void print() {
        std::println("this={} print MINE", this);
    }
};

// implements the Product interface.
class ConcreteProductYOURS: public Product {
public:
    void print() {
        std::println("this={} print YOURS", this);
    }
};

// declares the factory method, which returns an object of type Product.
class Creator {
public:
    virtual std::unique_ptr<Product> create(ProductId id) {
        if (ProductId::MINE == id) return std::make_unique<ConcreteProductMINE>();
        if (ProductId::YOURS == id) return std::make_unique<ConcreteProductYOURS>();
        // repeat for remaining products...

        return nullptr;
    }
    virtual ~Creator() = default;
};

int main() {
    // The unique_ptr prevent memory leaks.
    std::unique_ptr<Creator> creator = std::make_unique<Creator>();
    std::unique_ptr<Product> product = creator->create(ProductId::MINE);
    product->print();

    product = creator->create(ProductId::YOURS);
    product->print();
}

程序输出:

this=0x6e5e90 print MINE
this=0x6e62c0 print YOURS
Remove ads

C#

下面是C#例子:

public interface IProduct {
    string GetName();
    bool SetPrice(double price);
}

public class Phone : IProduct {
    private double _price;

    public string GetName() {
        return "Apple TouchPad";
    }

    public bool SetPrice(double price) {
        _price = price;
        return true;
    }
}

/* Almost same as Factory, just an additional exposure to do something with the created method */
public abstract class ProductAbstractFactory {
    protected abstract IProduct MakeProduct();

    public IProduct GetObject() // Implementation of Factory Method. {
        return this.MakeProduct();
    }
}

public class PhoneConcreteFactory : ProductAbstractFactory {
    protected override IProduct MakeProduct() {
        IProduct product = new Phone();
        // Do something with the object after receiving it
        product.SetPrice(20.30);
        return product;
    }
}
Remove ads

其他用例

  • ADO.NET中,IDbCommand.CreateParameter[4]使用工厂方法连接平行的类层次结构。
  • Qt中,QMainWindow::createPopupMenu[5]是在框架中定义的工厂方法,可以在应用代码中重写。
  • Java中,javax.xml.parsers[6]包使用了几个工厂方法。例如javax.xml.parsers.DocumentBuilderFactoryjavax.xml.parsers.SAXParserFactory
  • HTML5 DOM API中,文档接口包含createElement()工厂方法用来创建HTMLElement接口的特定元素。

适用性

工厂方法模式,适用于基于接口编程与实现依赖反转原则。 下列情况可以考虑使用工厂方法模式:

  • 创建对象需要大量重复的代码。可以把这些代码写在工厂基类中。
  • 创建对象需要访问某些信息,而这些信息不应该包含在复合类中。
  • 创建对象的生命周期必须集中管理,以保证在整个程序中具有一致的行为。 对象创建时会有很多参数来决定如何创建出这个对象。
  • 创建对象可能是一个pool里的,不是每次都凭空创建一个新的。而pool的大小等参数可以用另外的逻辑去控制。比如连接池对象,线程池对象
  • 业务对象的代码作者希望隐藏对象的真实类型,而构造函数一定要真实的类名才能用
  • 简化一些常规的创建过程。根据配置去创建一个对象也很复杂;但可能95%的情况只创建某个特定类型的对象。这时可以弄个函数直接省略那些配置过程。如Java的线程池的相关创建api(如Executors.newFixedThreadPool等)
  • 创建一个对象有复杂的依赖关系,比如Foo对象的创建依赖A,A又依赖B,B又依赖C……。于是创建过程是一组对象的的创建和注入。
  • 知道怎么创建一个对象,但是无法把控创建的时机。需要把“如何创建”的代码塞给“负责决定什么时候创建”的代码。后者在适当的时机,回调执行创建对象的函数。在支持用函数作为一等公民传参的语言,比如js,go等,直接用创建函数就行了。对于java需要搞个XXXXFactory的类去传。
  • 构造函数里不要抛出异常

工厂方法模式常见于工具包和框架中,在这些库中可能需要创建客户端代码实现的具体类型的对象。

平行的类层次结构中,常常需要一个层次结构中的对象能够根据需要创建另一个层次结构中的对象。

工厂方法模式可以用于测试驱动开发,从而允许将类放在测试中[7]。举例来说,Foo这个类创建了一个Dangerous对象,但是Dangerous对象不能放在自动的单元测试中(可能它需要访问产品数据库,而这个数据库不是随时能够访问到的)。所以,就可以把Dangerous对象的创建交由Foo类的一个方法(虚函数createDangerous完成。为了测试,再创建一个Foo的一个子类TestFoo,重写createDangerous方法,在方法中创建并返回一个FakeDangerousDangerous的子类),而这是一个模拟对象。这样,单元测试就可以使用TestFoo来测试Foo的功能,从而避免了使用Dangerous对象带来的副作用。

引用

参考文献

参见

Loading related searches...

Wikiwand - on

Seamless Wikipedia browsing. On steroids.

Remove ads