一般情况下工厂模式分为三种类型,简单工厂(Simple Factory),工厂方法(Factory Method),抽象工厂(Abstract Factory)。这三种模式从上到下逐步抽象。
简单工厂模式(Simple Factory)
简单工厂模式又称静态工厂方法模式。重命名上就可以看出这个模式一定很简单。它存在的目的很简单:定义一个用于创建对象的接口。
简单工厂模式由三部分组成:
1. 工厂类角色:这是本模式的核心,含有一定的商业逻辑和判断逻辑。
2. 抽象产品角色:它一般是具体产品继承的父类或者实现的接口。
3. 具体产品角色:工厂类所创建的对象就是此角色的实例。
用类图来表示下的它们之间的关系如下:
简单工厂的作用是实例化对象,而不需要客户了解这个对象属于哪个具体的子类
简单工厂实例化的类具有相同的接口或者基类,在子类比较固定并不需要扩展时,可以使用简单工厂。
采用简单工厂的优点是可以使用户根据参数获得对应的类实例,避免了直接实例化类,降低了耦合性;缺点是可实例化的类型在编译期间已经被确定,如果增加新类型,则需要修改工厂,不符合OCP(开闭原则)的原则。简单工厂需要知道所有要生成的类型,当子类过多或者子类层次过多时不适合使用。
让我们来看一个例子: 加入你现在正在开发一个物流管理应用。项目初期,这个项目只支持卡车运输。因此大部分代码都在位于名为 卡车的类中。当项目一段时间后, 这款应用变得极受欢迎。 你每天都能收到十几次来自海运公司的请求, 希望应用能够支持海上物流功能。但是代码问题该如何处理呢? 目前, 大部分代码都与 卡车类相关。 在程序中添加 轮船类需要修改全部代码。 更糟糕的是, 如果你以后需要在程序中支持另外一种运输方式, 很可能需要再次对这些代码进行大幅修改。
现在我们以简单工厂模式对项目进行修改.
代码如下: 抽象产品角色 Transport.php
/** * 抽象产品角色 * 运输接口 * Interface Transport */ interface Transport{ public function deliver(); }
具体产品角色 卡车 Truck.php
/** * 具体产品角色 * 卡车运输类 * Class Truck */ class Truck implements Transport{ public function deliver() { return "使用卡车运输"; } }
轮船 Ship.php
/** * 具体产品角色 * 轮船运输类 * Class Ship */ class Ship implements Transport { public function deliver() { return "使用轮船运输"; } }
工厂 Factory.php
/** * 工厂角色 * Class Factory */ class Factory{ /** * @param $type * @return Ship|Truck */ public static function createTransport($type) { switch ($type){ case 'truck': return new Truck(); case 'ship': return new Ship(); default: throw new \Exception("暂不支持的运输方式"); } } }
运行 client.php
class Client{ public function run(){ //client $logistics = Factory::createTransport('truck'); echo $logistics->deliver(); echo "<br/>"; $logistics = Factory::createTransport('ship'); echo $logistics->deliver(); } } $client = new Client(); $client->run();
这便是简单工厂模式了。使用了简单工厂模式后,客户端不在负责创建产品对象的责任,将创建产品对象交给了工厂实现,客户端只负责使用工厂消费产品。但简单工厂模式却违反了开闭原则(对扩展开放,对修改关闭)。当我们新增加一种运输方式的时候,都要在工厂类中增加相应的业务逻辑或者判断逻辑,这显然是违背开闭原则的。可想而知对于新产品的加入,工厂类是很被动的。在实际应用中,产品很可能是一个多层次的树状结构。而简单工厂模式只有一个工厂类来对应这些产品。每新增一个产品我们都要修改我们的工厂类方法的逻辑,使得我们疲于奔命,而工厂方法模式恰恰就能解决这些问题。
工厂方法模式(Factory Method)
工厂方法模式定义了一个创建对象的接口,但由子类决定要实例化的类是哪一个。工厂方法让类把实例化推迟到子类,工厂方法模式去掉了简单工厂模式中工厂方法的静态属性,使得它可以被子类继承。这样在简单工厂模式里集中在工厂方法上的压力可以由工厂方法模式里不同的工厂子类来分担。工厂方法模式主要由以下几部分组成:
1.抽象工厂角色: 这是工厂方法模式的核心,它与应用程序无关。是具体工厂角色必须实现的接口或者必须继承的父类
2.具体工厂角色:它含有和具体业务逻辑有关的代码。由应用程序调用以创建对应的具体产品的对象。
3.抽象产品角色:它是具体产品继承的父类或者是实现的接口。
4.具体产品角色:具体工厂角色所创建的对象就是此角色的实例。
用类图来表示下的它们之间的关系:
工厂方法模式使用继承自抽象工厂角色的多个子类来代替简单工厂模式中的“上帝类”。正如上面所说,这样便分担了对象承受的压力;而且这样使得结构变得灵活起来——当有新的产品(即运输方式)产生时,只要按照抽象产品角色、抽象工厂角色提供的合同来生成,那么就可以被客户使用,而不必去修改任何已有的代码。可以看出工厂角色的结构也是符合开闭原则的!
工厂方法模式代码如下:
抽象产品角色 Transport.php
/** * 抽象产品角色 * 运输接口 * Interface Transport */ interface Transport{ public function deliver(); }
具体产品角色
卡车 Truck.php
/** * 具体产品角色 * 卡车运输类 * Class Truck */ class Truck implements Transport{ public function deliver() { return "使用卡车运输"; } }
轮船 Ship.php
/** * 具体产品角色 * 轮船运输类 * Class Ship */ class Ship implements Transport{ public function deliver() { return "使用轮船运输"; } }
抽象工厂 Factory.php
/** * 抽象工厂角色 * Interface Factory */ interface Factory{ public function createTransport(); }
具体工厂角色:
卡车工厂 TruckFactory.php
/** * 具体工厂角色 * Class TruckFactory */ class TruckFactory implements Factory{ public function createTransport() { return new Truck(); } }
轮船工厂 ShipFactory.php
/** * 具体工厂角色 * Class ShipFactory */ class ShipFactory implements Factory{ public function createTransport() { return new Ship(); } }
运行 Client.php
/** * Class Client */ class Client{ public function run(){ $logistics = new TruckFactory(); $deliver = $logistics->createTransport(); echo $deliver->deliver(); echo "<br>"; $logistics = new ShipFactory(); $deliver = $logistics->createTransport(); echo $deliver->deliver(); } } $client = new Client(); $client->run();
可以看出工厂方法的加入,使得对象的数量成倍增长。当产品种类非常多时,会出现大量的与之对应的工厂对象,这不是我们所希望的。因为如果不能避免这种情况,可以考虑使用简单工厂模式与工厂方法模式相结合的方式来减少工厂类:即对于产品树上类似的种类(一般是树的叶子中互为兄弟的)使用简单工厂模式来实现。
什么时候该用工厂模式,什么时候该用简单工厂模式呢?
当我们的每个对象的创建逻辑都比较简单的时候,将多个对象的创建逻辑放到一个工厂类中。即使用简单工厂模式,尽管它不符合我们的开闭原则。但权衡扩展性和可读性,这样的代码实现在大多数情况下是没有问题的。
当对象的创建逻辑比较复杂,不只是简单的 new 一下就可以,而是要组合其他类对象,做各种初始化操作的时候,我们推荐使用工厂方法模式,将复杂的创建逻辑拆分到多个工厂类中,让每个工厂类都不至于过于复杂。而使用简单工厂模式,将所有的创建逻辑都放到一个工厂类中,会导致这个工厂类变得很复杂。除此之外,在某些场景下,如果对象不可复用,那工厂类每次都要返回不同的对象。如果我们使用简单工厂模式来实现,就只能选择第一种包含 if 分支逻辑的实现方式。如果我们还想避免烦人的 if-else 分支逻辑,这个时候,我们就推荐使用工厂方法模式