工厂模式
所谓工厂,就是将零件组装成产品的地方。
建一个对象常常需要复杂的过程,所以不适合包含在一个复合对象
中。创建对象可能会导致大量的重复代码,可能会需要复合对象访问不到的信息,也可能提供不了足够级别的抽象,还可能并不是复合对象概念的一部分。
在面向对象程序设计中,工厂通常是一个用来创建其他对象
的对象。工厂是构造方法的抽象,用来实现不同的分配方案。
一般情况下,工厂模式分为三种更加细分的类型: 简单工厂、工厂方法和抽象工厂 。不过,在GoF的《设计模式》一书中,它将简单工厂
看作是工厂方法模式
的一种特例,所以工厂模式
只被分成了工厂方法
和抽象工厂
两类。实际上,前一种分类方法更加常见。
实际上,简单工厂
、工厂方法
原理比较简单,实际的项目也比较常用。而抽象工厂的原理稍微复杂,在实际项目相对也不常用。对于抽象工厂方法
了解即可。
几个不同的设计模式都应用了工厂的概念,并可以使用在很多语言中。例如,在《设计模式》一书中,像工厂方法模式、抽象工厂模式、生成器模式,甚至是单例模式都应用了工厂的概念。
简单工厂(Simple Factory)
实现
1. 工厂类
/**
* Pizza 简单工厂类
*/
public class SimplePizzaFactory {
/**
* 根据不同pizza类型创建对应Pizza
* @param type
* @return
*/
public Pizza createPizza(String type) {
if (type.equals("cheese")) {
return new CheesePizza();
} else if (type.equals("veggie")) {
return new Veggie("veggiePizza");
} else {
return null;
}
}
}
2. 具体对象
public class Veggie extends Pizza {
public Veggie(String name) {
setName(name);
}
}
3. Main方法
public class T {
public static void main(String[] args) {
// 1. 创建Pizza简单工厂
SimplePizzaFactory simplePizzaFactory = new SimplePizzaFactory();
// 2.根据type创建Pizza
Pizza cheese = simplePizzaFactory.createPizza("veggie");
// 3.得到Pizza
cheese.toString();
}
}
通过简单的if/else
判断,创建对应的pizza
。如果要添加新的pizza
,势必要改动源码,那这违反开闭原则呢? 实际上,对于不需要频繁添加新的pizza
,也是完全可以接受的。
工厂方法
定义
工厂方法模式
定义了一个创建对象的接口,但由子类决定
要实例化的类是哪一个。工厂方法让类把实例化推迟到子类
。工厂方法模式帮助我们将产品的
实现
从使用
中解耦。如果增加产品或者改变产品的实现。Creator类
并不会受影响。
类图
- 两个类
层级平等
: 因为它们都有抽象类,而抽象类都有许多具体的子类,每个子类都有自己的实现。 - 所有的工厂都是用来封装对象的创建。
- 工厂方法使用继承,把对象的创建委托给子类,子类实现工厂方法的创建对象。
实现
1. 定义工厂
/**
* PizzaStore 作为超类,可以被其他
* 各个加盟店之间的区别在于风味不同,而其它流程我们认为是相同的
*/
public abstract class PizzaStore {
public Pizza orderPizza(String type) {
Pizza pizza;
pizza = createPizza(type);
System.out.println(pizza.getName());
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
/**
* ① 工厂方法是抽象的。所以依赖子类来处理对象的创建
* ② 必须返回一个产品
* ③ 工厂方法有可能需要参数,也有可能不需要参数来指定所需要的产品
* @param type
* @return
*/
protected abstract Pizza createPizza(String type);
}
2. 实现工厂
/**
* 芝加哥 Pizza风味店
*/
public class ChicagoStylePizzaStore extends PizzaStore{
@Override
protected Pizza createPizza(String type) {
if (type.equals("cheese")) {
return new ChicagoStyleCheesePizza();
} else {
return null;
}
}
}
3. Main方法
public class M {
public static void main(String[] args) {
System.out.println("The first customer order in Chicago pizza store");
PizzaStore chicagoStylePizzaStore = new ChicagoStylePizzaStore();
Pizza pizza = chicagoStylePizzaStore.orderPizza("cheese");
System.out.println("The second customer order in NY pizza store");
NYStylePizzaStore nyStylePizzaStore = new NYStylePizzaStore();
nyStylePizzaStore.orderPizza("cheese");
}
}
总结
优点:
- 调用者只需要知道名称即可,不需要了解具体实现。屏蔽具体实现细节。
- 扩展性高。如果想增加一个产品,只要扩展一个工厂类即可。
缺点:
- 每增加一个产品,都需要增加一个具体类和对象实现工厂,使得系统中类的个数成倍增加,一定程序上增加了系统的复杂度。
- 增加了系统具体类的依赖。
- 不符合
开闭-原则
。即对扩展开发、对修改关闭
。因为每增加一个产品类,需要在工厂代码里面增加if-else
判断。 - 简单工厂模式其实不是一个设计模式,反而比较像是一种编程习惯。
什么时候该用工厂方法模式,而非简单工厂模式?
之所以将某个代码块剥离独立为函数或类,原因是这个代码块的逻辑过于复杂,剥离之后能让代码更加清晰,更加可读、可维护。但是,如果代码块本身并不复杂,就几行代码而已,我们完全没必要将它拆分成单独的函数或者类。
基于这个设计思想,当对象的创建逻辑比较复杂,不只是简单的new
一下,而是要组合其他类对象,做各种初始化操作的时候,推荐使用工厂方法模式
,将复杂的创建逻辑拆分到多个工厂类中,让每个工厂类都不至于过于复杂。而使用简单工厂模式,将所有的创建逻辑都放到一个工厂类中,会导致这个工厂类变得复杂。
除此之外,在某些场景下,如果对象不可复用,那工厂类每次都要返回不同对象。如果我们使用简单工厂模式实现,就只能选择第一种包含if
分支逻辑的实现方式。如果我们还想避免烦人的if-else
分支逻辑,推荐使用工厂方法模式
。
抽象工厂模式
定义
抽象工厂模式 (英语: Abstract factory pattern )是一种软件开发设计模式。抽象工厂模式提供了一种方式,可以将一组具有同一主题的单独的工厂封装起来。在正常使用中,客户端程序需要创建抽象工厂的具体实现,然后使用抽象工厂作为接口来创建这一主题的具体对象。客户端程序不需要知道(或关心)它从这些内部的工厂方法中获得对象的具体类型,因为客户端程序仅使用这些对象的通用接口。抽象工厂模式将一组对象的实现细节与他们的一般使用分离开来。
工厂
是创建产品(对象)的地方,其目的是将产品的创建与产品的使用分离。抽象工厂模式的目的,是将若干抽象产品的接口与不同主题产品的具体实现分离开。这样就能在增加新的具体工厂的时候,不用修改引用抽象工厂的客户端代码。使用抽象工厂模式,能够在具体工厂变化的时候,不用修改使用工厂的客户端代码,甚至是在运行时。抽象工厂模式的实质是“提供接口,创建一系列相关或独立的对象,而不指定这些对象的具体类。
(引用维基百科)
抽象工厂为产品家族提供接口。通过抽象工厂所提供的接口,可以创建产品的家族,复用这个接口书写代码,我们的代码将从实际工厂解耦,以便在不同上下文中实现各式各样的工厂,制造出各种不同的产品。
抽象工厂模式提供一个接口,用于创建相关或依赖对象的家族,而不需要明确指定具体类。
抽象工厂允许客户使用抽象的接口来创建一组相关的产品,而不需要知道(或关心)实际产出的具体产品是什么。这样一来,客户就从具体的产品中被解耦。
抽象工厂的方法经常以工厂方法的方式实现。
(引用Head First 设计模式)
抽象工厂的工作就是将
抽象零件
组装为抽象产品
。抽象
是指不考虑具体怎么实现,而是仅仅只确定了方法的名字和签名
。
类图
-
抽象工厂
确定工厂的业务范围。
-
具体工厂
每个具体工厂对应一个产品族
。具体工厂决定生产哪个具体的产品对象。 -
抽象产品
同一产品等级结构的抽象类。
-
具体产品
可供生产的具体产品。
实现
1. 定义原料工厂接口
/**
* Pizza 原料工厂接口
* 定义如何产生一个相关产品的家族。这个家族包含了所有制作Pizza的原料
*/
public interface PizzaIngredientFactory {
Dough createDough();
Sauce createSauce();
Cheese createCheese();
}
2. 定义原料工厂实现类
/**
* 该工厂根据自身类型生产原料
*/
public class NYPizzaIngredientFactory implements PizzaIngredientFactory{
@Override
public Dough createDough() {
return new ThinCrustDough();
}
@Override
public Sauce createSauce() {
return new MarinaraSauce();
}
@Override
public Cheese createCheese() {
return new ReggianoCheese();
}
}
3. 定义原料抽象类
/**
* Cheese原料定义
*/
public abstract class Cheese {
}
/**
* 酱料
*/
public abstract class Sauce {
}
4. 定义原料实现类
public class MarinaraSauce extends Sauce {
}
public class ReggianoCheese extends Cheese {
}
5. 定义抽象工厂
/**
* Pizza店 抽象类
*/
public abstract class PizzaStore {
public Pizza orderPizza(String type) {
Pizza pizza = createPizza(type);
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
/**
* ① 工厂方法是抽象的。所以依赖子类来处理对象的创建
* ② 必须返回一个产品
* ③ 工厂方法有可能需要参数,也有可能不需要参数来指定所需要的产品
* @param type
* @return
*/
protected abstract Pizza createPizza(String type);
}
6. 定义抽象工厂实现类
/**
* Pizza店具体实例
* 是抽象工厂的客户
*/
public class NYPizzaStore extends PizzaStore{
/**
* 根据类型创建Pizza
* @param type
* @return
*/
@Override
protected Pizza createPizza(String type) {
Pizza pizza = null;
// 通过
PizzaIngredientFactory ingredientFactory = new NYPizzaIngredientFactory();
if (type.equals("cheese")) {
pizza = new CheesePizza(ingredientFactory);
pizza.setName("New York Style Cheese Pizza");
return pizza;
}
return null;
}
}
7. Main
public class T {
public static void main(String[] args) {
// 创建纽约Pizza店
PizzaStore pizzaStore = new NYPizzaStore();
// 接受订单
pizzaStore.orderPizza("cheese");
}
}
依赖到置原则
要依赖抽象,不要依赖具体类
。
指导方针
-
变量不可以持有具体类的引用
如果使用
new
关键字,就会持有具体类的引用。你可以改用工厂来避开这样的做法。 -
不要让类派生自具体类
如果派生自具体类,你就会依赖这个具体类。请派生自一个抽象(接口或抽象类)。
-
不要覆盖基类中已实现的方法。
如果覆盖基类已实现的方法,那么你的基类就不是一个真正适合被继承的抽象。基类中已实现的方法,应该由所有的子类共享。
当然,完全遵守这些指导方针似乎不太可能,应该尽量达到这个原则,而不是随时都要遵循这个原则。
总结
- 工厂方法使用
继承
实现对象的创建。而抽象方法使用组合
。工厂方法只负责将客户从具体类型中解耦。而抽象工厂提供一个用来创建一个产品家族的抽象类型,这个类型的子类定义了产品被产生的方法。 - 高层模块 只需要知道自己是哪个产品族的 ,藉此确定具体工厂。再通过具体工厂获取对象的时候 不需要知道这个对象是属于哪个具体类的 ,具体工厂决定使用哪个具体类。
Java 面试宝典是大明哥全力打造的 Java 精品面试题,它是一份靠谱、强大、详细、经典的 Java 后端面试宝典。它不仅仅只是一道道面试题,而是一套完整的 Java 知识体系,一套你 Java 知识点的扫盲贴。
它的内容包括:
- 大厂真题:Java 面试宝典里面的题目都是最近几年的高频的大厂面试真题。
- 原创内容:Java 面试宝典内容全部都是大明哥原创,内容全面且通俗易懂,回答部分可以直接作为面试回答内容。
- 持续更新:一次购买,永久有效。大明哥会持续更新 3+ 年,累计更新 1000+,宝典会不断迭代更新,保证最新、最全面。
- 覆盖全面:本宝典累计更新 1000+,从 Java 入门到 Java 架构的高频面试题,实现 360° 全覆盖。
- 不止面试:内容包含面试题解析、内容详解、知识扩展,它不仅仅只是一份面试题,更是一套完整的 Java 知识体系。
- 宝典详情:https://www.yuque.com/chenssy/sike-java/xvlo920axlp7sf4k
- 宝典总览:https://www.yuque.com/chenssy/sike-java/yogsehzntzgp4ly1
- 宝典进展:https://www.yuque.com/chenssy/sike-java/en9ned7loo47z5aw
目前 Java 面试宝典累计更新 400+ 道,总字数 42w+。大明哥还在持续更新中,下图是大明哥在 2024-12 月份的更新情况:
想了解详情的小伙伴,扫描下面二维码加大明哥微信【daming091】咨询
同时,大明哥也整理一套目前市面最常见的热点面试题。微信搜[大明哥聊 Java]或扫描下方二维码关注大明哥的原创公众号[大明哥聊 Java] ,回复【面试题】 即可免费领取。