工厂模式
Define an interface for creating an object, but let the classes that implement the interface decide which class to instantiate. The Factory method lets a class defer instantiate. The Factory method lets a class defer instantiation to subclasses.
——GoF
意即: 在基类中定义创建对象的一个接口,让子类决定实例化哪个类。工厂方法让一个类的实例化延迟到子类中进行
简单工厂模式
从设计模式的类型上来说,简单工厂模式属于创建型模式,又叫作静态工厂模式,但不属于GoF的23种设计模式之一。简单工厂模式是由一个工厂对象决定创建出哪一种产品类的实例,被创建的实例通常都具有共同的父类。简单工厂模式是工厂模式家族中最简单实用的模式,可以理解为是工厂方法模式和抽象工厂的基础和初步实现。其主要目的是:
- 不向客户透露对象实例化的细节
- 通过通用接口创建对象
简单工厂模式的实质是由一个工厂类根据传入的参数,动态决定应该创建哪一个产品类(这些产品类继承自一个父类或接口)的实例。
简单工厂模式包括如下三种角色:
- 简单工厂:负责实现创建所有实例的内部逻辑,具体构造细节是在一个个
if/else
分支或者switch/case
分支里面 - 抽象产品:负责描述所有实例所共有的公共接口
- 具体产品:简单工厂模式的创建目标
简单工厂模式的UML类图如下:
下面给出一个例子说明简单工厂模式。
假设有一个蛋糕店,可以生产水果蛋糕、巧克力蛋糕、芝士蛋糕等,只要你提前预定蛋糕的类型,蛋糕店就可以给你生产出来。这里的蛋糕店就是一个简单工厂,水果蛋糕、巧克力蛋糕、芝士蛋糕就是具体产品。
程序代码如下:
/**
* 抽象产品
*/
public interface Cake {
void cooking();
void packing();
}
/**
* 具体产品
*/
public class FruitCake implements Cake {
@Override
public void cooking() {
System.out.println("水果蛋糕制作中...");
}
@Override
public void packing() {
System.out.println("水果蛋糕打包中...");
}
}
public class ChocolateCake implements Cake {
@Override
public void cooking() {
System.out.println("巧克力蛋糕制作中...");
}
@Override
public void packing() {
System.out.println("巧克力蛋糕打包中...");
}
}
public class CheeseCake implements Cake {
@Override
public void cooking() {
System.out.println("芝士蛋糕制作中...");
}
@Override
public void packing() {
System.out.println("芝士蛋糕打包中...");
}
}
/**
* 简单工厂
*/
public class Bakery {
public static Cake makeCake(String type) {
if ("水果".equals(type)) {
return new FruitCake();
} else if ("巧克力".equals(type)) {
return new ChocolateCake();
} else if ("芝士".equals(type)) {
return new CheeseCake();
} else {
System.out.println("抱歉!蛋糕房无法生产您所需的蛋糕");
return null;
}
}
}
/**
* 测试类
*/
public class FactoryTest {
public static void main(String[] args) {
Cake fruitCake = Bakery.makeCake("水果");
if (fruitCake != null) {
fruitCake.cooking();
fruitCake.packing();
}
Cake chocolateCake = Bakery.makeCake("巧克力");
if (chocolateCake != null) {
chocolateCake.cooking();
chocolateCake.packing();
}
Cake cheeseCake = Bakery.makeCake("芝士");
if (cheeseCake != null) {
cheeseCake.cooking();
cheeseCake.packing();
}
}
}
测试结果:
简单工厂模式具有如下优点:
- 简单工厂模式将对象的创建与对象的使用分离,有利于功能的复用和系统的维护
- 客户端无需知道所创建的具体产品的类名,只需知道具体产品类所对应的参数即可,对于一些复杂的类名,通过简单工厂模式可以减少使用者的记忆量
- 可以通过引入配置文件,可以在不修改任何客户端代码的情况下跟换和增加新的具体产品类,在一定程度上提高了系统的灵活性
缺点:
- 工厂类集中了所有产品逻辑,一旦不能工作,整个系统都会收到影响
- 系统扩展困难,一旦添加新产品就不得不修改工厂逻辑,违背了“开闭原则”
工厂方法模式
工厂方法模式的定义:在基类中定义创建对象的一个接口,让子类决定实例化哪个类。工厂方法让一个类的实例化延迟到子类中。
工厂方法模式又称为工厂模式,也叫虚拟构造器或者多态工厂模式,属于创建型模式。
工厂方法模式包含的角色如下:
- 抽象工厂:将具体工厂的实现隐藏起来,如果具体工厂实现类内部发生变化,对外部客户端的调用没有任何影响。抽象工厂类具有多态性,所以不能实例化,需要根据具体工厂类的不同实现来完成产品对象的创建。
- 具体工厂:具体工厂类时依赖于具体的产品的,如果具体产品增加或创建方式改变,只需对具体工厂类做修改,而抽象工厂不需修改。
- 抽象产品:抽象产品类向任何发出产品对象创建的客户端屏蔽了产品的细节,而这些细节有具体产品来实现。
- 具体产品:具体产品具有产品对象的数据和服务,客户端通过获取所需要的产品对象,来完成特定的业务流程。
工厂方法模式的UML类图如下:
下面通过一个例子来说明工厂方法模式
OEM制造商可以制造Dell、Sony、Acer和Lenovo等品牌笔记本,如果一次同时生产多个品牌的笔记本的话,有些不利于后期管理,比如加入了新的品牌后,就可能有许多设备上的改动。对此成立了一些子厂,每个子厂只做一种品牌的电脑,而OEM制造商作为核心总部。核心工厂不再负责创建等具体产品,把这些分派给自己的子厂,而核心工厂只需负责制定规范即可。
使用工厂模式模拟笔记本生产的UML类图如下:
示例代码如下:
1.抽象产品类——Laptop.java
public abstract class Laptop {
private String brand;
public Laptop() {
}
public Laptop(String brand) {
this.brand = brand;
}
public abstract void printId();
public void getDescription() {
System.out.println("电脑品牌:" + this.brand);
}
}
2.具体实现类——DellLaptop.java、AcerLaptop.java、LenovoLaptop.java
public class DellLaptop extends Laptop {
private static final String brand = "DELL";
private static int id;
public DellLaptop() {
super(brand);
id = 1000;
}
@Override
public void printId() {
System.out.println("序列号:" + id++);
}
}
public class AcerLaptop extends Laptop {
private static final String brand = "ACER";
private static int id;
public AcerLaptop() {
super(brand);
id = 2000;
}
@Override
public void printId() {
System.out.println("序列号:" + id++);
}
}
public class LenovoLaptop extends Laptop {
private static final String brand = "LENOVO";
private static int id;
public LenovoLaptop() {
super(brand);
id = 3000;
}
@Override
public void printId() {
System.out.println("序列号:" + id++);
}
}
3.抽象工厂类——LaptopFactory.java
public interface LaptopFactory {
Laptop produce();
void afterSaleService();
}
4.具体工厂类——DellFactory.java、AcerFactory.java、LenovoFactory.java
public class DellFactory implements LaptopFactory {
@Override
public Laptop produce() {
return new DellLaptop();
}
@Override
public void afterSaleService() {
System.out.println("戴尔售后为您服务!");
}
}
public class AcerFactory implements LaptopFactory {
@Override
public Laptop produce() {
return new AcerLaptop();
}
@Override
public void afterSaleService() {
System.out.println("宏基售后为您服务!");
}
}
ublic class LenovoFactory implements LaptopFactory {
@Override
public Laptop produce() {
return new LenovoLaptop();
}
@Override
public void afterSaleService() {
System.out.println("联想售后为您服务!");
}
}
5.测试类
public class FactoryTest {
public static void main(String[] args) {
LaptopFactory f1 = new DellFactory(); // 生产两台戴尔电脑
Laptop p1 = f1.produce();
Laptop p2 = f1.produce();
p1.getDescription();
p1.printId();
p2.getDescription();
p2.printId();
LaptopFactory f2 = new AcerFactory(); // 生产两台宏基电脑
Laptop p3 = f2.produce();
Laptop p4 = f2.produce();
p3.getDescription();
p3.printId();
p4.getDescription();
p4.printId();
LaptopFactory f3 = new LenovoFactory(); // 生产两台联想电脑
Laptop p5 = f3.produce();
Laptop p6 = f3.produce();
p5.getDescription();
p5.printId();
p6.getDescription();
p6.printId();
}
}
测试结果:
工厂方法模式的应用
在电子商务系统中,为了有效增加客户的满意度、提升服务质量、快速达成交易,当买家购买商品生产订单后,系统在商品出库及发货的时间节点上,免费发送消息通知给买家。消息通知的方式包括短信通知、邮件通知、站内信通知。不同的通知方式还可包括多种类型,如短信通知可能包括发货提醒、到达提醒、签收提醒等类型。业务发展的需求,消息通知方式可能还会增加,如QQ通知、微信通知等。
我们先尝试使用简单工厂模式实现上述功能,UML类图如下:
主要代码如下:
抽象消息通知类——Sender.java
public interface Sender {
public void send();
}
具体消息通知类——MailSender.java、SmsSender.java、InternalMailSender.java
public class MailSender implements Sender { // 实现邮件发送
@Override
public void send() {
System.out.println("发送邮件通知");
}
}
public class SmsSender implements Sender { // 实现短信发送
@Override
public void send() {
System.out.println("发送短信通知");
}
}
public class InternalMailSender implements Sender { // 实现站内信发送
@Override
public void send() {
System.out.println("发送站内信通知");
}
}
工厂类——SendFactory.java
public class SendFactory {
public final static int MAIL = 1;
public final static int SMS = 2;
public final static int InternalMail = 3;
public Sender createSender(int sendType) {
Sender sender = null;
switch (sendType) {
case SendFactory.MAIL:
sender = new MailSender();
break;
case SendFactory.SMS:
sender = new SmsSender();
break;
case SendFactory.InternalMail:
sender = new InternalMailSender();
break;
default:
sender = new MailSender();
break;
}
return sender;
}
}
客户端测试类
public class FactoryTest {
public static void main(String[] args) {
SendFactory factory = new SendFactory();
Sender sender = factory.createSender(SendFactory.InternalMail);
sender.send(); // 发送站内信通知
}
}
在上述简单工厂模式的工厂类上,集中了所有实例的创建逻辑,违反了高内聚的责任分配原则,当系统的具体产品类不断增多时,工厂类中条件判断过多,不利于扩展即维护。简单工厂模式的这些缺点,利用工厂方法模式可以得到一定的克服。
使用工厂方法模式实现消息通知发送的UML类图如下:
代码示例:
抽象产品接口——MsgNotification.java
public interface MsgNotification {
void send();
}
具体产品类——EmailNotification.java、SmslNotification.java、InternalMsgNotification.java
public class EmailNotification implements MsgNotification {
@Override
public void send() {
System.out.println("使用email发送通知");
}
}
public class SmsNotification implements MsgNotification {
@Override
public void send() {
System.out.println("使用短信发送通知");
}
}
public class InternalMsgNotification implements MsgNotification {
@Override
public void send() {
System.out.println("使用站内信发送通知");
}
}
抽象工厂类——SenderFactory.java
public interface SenderFactory {
MsgNotification create();
}
具体工厂类——EmailFactory.jav、SmsFactory.java、InternalMsgFactory.java
public class EmailFactory implements SenderFactory {
@Override
public MsgNotification create() {
return new EmailNotification();
}
}
public class SmsFactory implements SenderFactory {
@Override
public MsgNotification create() {
return new SmsNotification();
}
}
public class InternalMsgFactory implements SenderFactory {
@Override
public MsgNotification create() {
return new InternalMsgNotification();
}
}
显示订单页面的jsp
<%@ page import="com.test.Counter" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>$Title$</title>
</head>
<body>
<h4>商品订单</h4>
<table width="900" border="1">
<tr>
<td>物品名称</td>
<td>订单号</td>
<td>买家用户名</td>
<td>物品价格</td>
<td>数量</td>
<td>成交金额</td>
<td>下单日期</td>
<td>状态</td>
<td>可执行操作</td>
</tr>
<tr>
<td>设计模式</td>
<td>202101071538</td>
<td>lxg</td>
<td>¥50</td>
<td>1</td>
<td>¥50</td>
<td>2021-02-07</td>
<td>24小时内发货</td>
<td><a href="deliveryConfirm">发货</a></td>
</tr>
</table>
</body>
</html>
模拟向用户发送通知
@WebServlet("/deliveryConfirm")
public class DeliveryConfirm extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
SenderFactory factory = new EmailFactory();
MsgNotification msg = factory.create();
msg.send();
}
}
点击发货后,后台打印日志
总结
工厂方法模式的使用情况
当遇到下面的情况时,开发人员可以考虑采用工厂方法模式:
- 在编码时不能预见需要创建哪一种类的实例
- 一个类使用它的子类来创建对象
- 开发人员不希望创建了哪个类的实例以及如何创建实例的信息暴露给外部程序
工厂方法模式的优缺点
简单工厂模式使用一个类负责所有产品的创建,虽然使得客户端和服务端相互分离;使得客户端不用关心产品的具体创建过程,客户端唯一 所要做的就是调用简单工厂的静态方法获得想要的产品即可,但是,简单工厂模式违背了严格意义上的“开放封闭原则”,这就使得一旦有一个新的产品增加就必须修改工厂类的源代码才能将新的产品的创建逻辑加人简单工厂中供客户端调用。工厂方法模式正是在简单工厂模式的基础上进一步抽象而来的。由于工厂方法模式的核心是抽象工厂角色,使用了面向对象的多态性,这就使得工厂方法模式既保持了简单工厂模式的优点,又克服了简单工厂模式违背“开放封闭原则”的缺点。例如,在上面的例子中,当系统扩展需要添加新的消息发送方式时,仅仅需要添加一个新的消息发送方式具体对象以及一个创建该发送方式的具体工厂对象,原有工厂对象不需要进行任何修改,很好地符合了“对扩展开放对修改封闭”的原则。
优点:
- 在工厂方法模式中,工厂方法用来创建客户所需要的产品,同时还向客户隐藏了哪种具体产品类将被实例化这一细节,用户只需要关心所需产品对应的工厂,无须关心创建细节,甚至无须知道具体产品类的类名。
- 基于工厂角色和产品角色的多态性设计是工厂方法模式的关键。它能够使工厂可以自主确定创建何种产品对象, 而如何创建这个对象的细节则完全封装在具体工厂内部。工厂方法模式之所以又被称为多态工厂模式,正是因为所有的具体工厂类都具有同一抽象父类。
- 使用工厂方法模式的另一个优点是在系统中加人新产品时,无须修改抽象工厂和抽象产品提供的接口,无须修改客户端,也无须修改其他的具体工厂和具体产品,而只要添加一个具体工厂和具体产品就可以了,这样,系统的可扩展性也就变得非常好,完全符合“开闭原则”。
缺点:
- 在添加新产品时,需要编写新的具体产品类,而且还要提供与之对应的具体工厂类,系统中类的个数将成对增加,在一定程度上增加了系统的复杂度,有更多的类需要编译和运行,会给系统带来一 些额外的开销。
- 由于考虑到系统的可扩展性,需要引入抽象层,在客户端代码中均使用抽象层进行定义,增加了系统的抽象性和理解难度。