设计模式详解——工厂模式
前言:
今天深入梳理工厂模式。
概述:
在Java中,对象需要被创建,如果创建的时候直接·new该对象,就会对该对象耦合严重,假如我们需要更换对象,所有new对象的地方都需要改下,这显然违背了软件设计的开闭原则。如果我们使用工厂来生产对象,我们就只需要和工厂打交道就可以了,彻底和对象解耦,如果要更换对象,直接在工厂内更换对象就可以了,达到了与对象解耦的目的。所以说,工厂模式最大的优点就是解耦。
工厂模式分为以下三类:
- 简单工厂模式
- 工厂方法模式
- 抽象工厂模式
下面会一一进行介绍。
1. 简单工厂模式
1.1 结构
简单工厂包含以下角色:
- 抽象产品:定义了产品的规范,描述了产品的主要特性和功能
- 具体产品:实现或继承抽象产品的子类
- 具体工厂:提供了创建产品的方法,调用者通过该方法来获取产品
1.2 实现
以点咖啡为例,UML图如下:
具体代码:
抽象产品(注意咖啡的主要特性):
package com.itheima.pattern.factory.factory_simple;
public abstract class Coffee {
public abstract String getName();
// 加糖
public void addsugar(){
System.out.println("加糖");
}// 加奶
public void addmilk(){
System.out.println("加奶");
}}
具体产品(咖啡的具体种类):
1. latte coffee
public class LatteCoffee extends Coffee {
@Override
public String getName() {
return "latte coffee";
}
}
2.American coffee
package com.itheima.pattern.factory.factory_simple;
public class AmericanCoffee extends Coffee {
@Override
public String getName() {
return "American Coffee";
}
}
具体工厂(定义造什么样的咖啡):
package com.itheima.pattern.factory.factory_simple;
public class SimpleCoffeeFactory {
public Coffee createcoffee(String coffeeType) {
Coffee coffee = null;
if ("american coffee".equals(coffeeType)) {
coffee = new AmericanCoffee();
}else if ("latte coffee".equals(coffeeType)) {
coffee = new LatteCoffee();
}else {
throw new RuntimeException("Unknown coffee type: " + coffeeType);
}
return coffee;
}
}
最后在咖啡店中使用:
package com.itheima.pattern.factory.factory_simple;
public class CoffeeStore {
public Coffee orderCoffee(String coffeeType) {
SimpleCoffeeFactory coffeeFactory = new SimpleCoffeeFactory();
Coffee coffee = coffeeFactory.createcoffee(coffeeType);
coffee.addsugar();
coffee.addmilk();
return coffee;
}
}
工厂处理创建对象的细节,一旦有了simpleCoffeeFactory,CoffeeStore类中的orderCoffee()就变成了此对象的客体,后期如果需要Coffee对象直接从工厂中获取即可。这也就解除了和Coffee实现类的耦合,同时也产生了新的耦合,CoffeeStore和SimpleCoffeeFactory的耦合,工厂和商品对象的耦合。
后期如果要添加新的品种咖啡,我们必要需求修改SimpleCoffeeFactory的代码,违法了开闭原则。工厂类的客户端可能有很多,比如创建其他类型的咖啡,这样只要修改工厂类的代码,省去了其他的修改操作。
1.3 优缺点
优点:
封装了创建对象的过程,可以通过参数直接获取对象,把对象的创建和业务逻辑分开,这样就避免了修改客户端代码,如果要实现新产品直接修改工厂类,而不需要在原代码中修改,这样就降低了客户代码修改的可能性。
缺点:
增加新产品还是需要修改工厂类的代码,违背了开闭原则(对扩展开放,对修改关闭)。
1.4 静态工厂类(扩展)
静态工厂也就是将工厂类中创建对象的功能定义为静态的,代码如下:
package com.itheima.pattern.factory.factory_static;
public class SimpleCoffeeFactory {
public static Coffee createcoffee(String coffeeType) {
Coffee coffee = null;
if ("american coffee".equals(coffeeType)) {
coffee = new AmericanCoffee();
}else if ("latte coffee".equals(coffeeType)) {
coffee = new LatteCoffee();
}else {
throw new RuntimeException("Unknown coffee type: " + coffeeType);
}
return coffee;
}
}
2. 工厂方法模式
2.1 概念
定义一个用来创建对象的接口,让子类决定实例化哪个产品类对象,工厂方法使一个产品的实例化延迟到其工厂的子类。
2.2 结构
工厂方法模式的主要角色:
- 抽象工厂:提供了创建产品的接口,调用者通过它访问具体工厂的工厂方法来创建产品。
- 具体工厂:主要实现抽象工厂中的抽象方法,完成具体产品的创建
- 抽象产品:定义了产品的规范,描述了产品的主要特性和功能
- 具体产品:实现了抽象产品角色所定义的接口,由具体工厂创建,它同具体工厂之间一一对应。
2.3 实现
UML类图如下:
具体代码如下:
抽象工厂(提供了创建产品的接口):
package com.itheima.pattern.factory.factory_method;
public interface CoffeeFactory {
// 创建咖啡对象的方法
Coffee createcoffee();
}
具体工厂(创建具体的产品):
1.american coffee
package com.itheima.pattern.factory.factory_method;
public class AmericanCoffeeFactory implements CoffeeFactory {
@Override
public Coffee createcoffee() {
return new AmericanCoffee();
}
}
2.latte coffee
package com.itheima.pattern.factory.factory_method;
public class LatteCoffeeFactory implements CoffeeFactory {
@Override
public Coffee createcoffee() {
return new LatteCoffee();
}
}
抽象产品(定义产品规范):
package com.itheima.pattern.factory.factory_method;
public abstract class Coffee {
public abstract String getName();
// 加糖
public void addsugar(){
System.out.println("加糖");
}// 加奶
public void addmilk(){
System.out.println("加奶");
}}
具体产品:
public class AmericanCoffee extends Coffee {
@Override
public String getName() {
return "American Coffee";
}
}public class LatteCoffee extends Coffee {
@Override
public String getName() {
return "latte coffee";
}
}
使用:
public class CoffeeStore {
private CoffeeFactory factory;public void setFactory(CoffeeFactory factory) {
this.factory = factory;
}
public Coffee orderCoffee() {
Coffee createcoffee = factory.createcoffee();
createcoffee.addmilk();
createcoffee.addsugar();
return createcoffee;
}}
从以上的编写就可以看出来,要增加产品类的时候要相应增加工厂类,而不需要修改工厂类的代码了,这样就解决了简单工厂模式的缺点,符合开闭原则。
工厂方法模式是简单工厂模式的进一步抽象,由于使用了多态性,工厂方法模式保持了简单工厂模式的优点,而克服了它的缺点。
2.4 优缺点
优点:
- 用户只需要知道具体工厂名称就可以得到所需要的产品,无需知道产品的具体创建过程
- 在系统增加新的产品是需要添加具体产品类和对应的具体工厂类,无需对原工厂进行任何修改,满足开闭原则。
缺点:
- 增加了系统的复杂性。
3. 抽象工厂模式
3.1 概念
是一种为访问类提供一个创建一组相关的或互相依赖对象的接口,且访问类无需指定所要产品的具体类就能得到同族额不同等级的产品的模式结构。
抽象工厂模式是工厂方法模式的升级版本,工厂方法模式只生产一个等级的产品,而抽象方法模式可生产多个产品族的产品,所谓的产品族即:
产品族 是指 一组在逻辑上相关联的产品,它们通常属于同一个风格、同一个品牌或同一个使用场景。
3.2 结构
抽象工厂模式的主要角色:
- 抽象工厂: 提供了创建产品的接口,它包含了多个创建产品的方法,可以创建多个不同等级的产品。
- 具体工厂: 主要是实现抽象工厂多个抽象方法,完成具体产品的创建。
- 抽象产品:定义了产品的规范,描述了产品的主要特性和功能,抽象工厂模式有多个抽象产品。
- 具体产品:实现了抽象产品角色所定义的接口,由具体工厂创建,它,同具体工厂是多对一的关系。
3.3 实现
UML类图如下:
代码如下:
抽象工厂(创建产品族所要包括的产品):
package com.itheima.pattern.factory.factory_abstract;
import com.itheima.pattern.factory.factory_method.Coffee;
public interface DessertFactory {
// 生产咖啡的功能
Coffee createCoffee();// 生产甜品的功能
Dessert createDessert();
}
具体工厂(创建不同产品族,比如美式咖啡糕点,意大利式咖啡糕点):
1.意大利式的
package com.itheima.pattern.factory.factory_abstract;
import com.itheima.pattern.factory.factory_method.Coffee;
import com.itheima.pattern.factory.factory_method.LatteCoffee;public class ItalyDessertFactory implements DessertFactory {
@Override
public Coffee createCoffee() {
return new LatteCoffee();
}@Override
public Dessert createDessert() {
return new Trimisu();
}
}
2.美式的
package com.itheima.pattern.factory.factory_abstract;
import com.itheima.pattern.factory.factory_method.AmericanCoffee;
import com.itheima.pattern.factory.factory_method.Coffee;public class AmericanDessertFactory implements DessertFactory {
@Override
public Coffee createCoffee() {
return new AmericanCoffee();
}@Override
public Dessert createDessert() {
return new MatchaMouse();
}}
抽象产品(产品族内所要包含的产品):
1.咖啡
package com.itheima.pattern.factory.factory_abstract;
public abstract class Coffee {
public abstract String getName();
// 加糖
public void addsugar(){
System.out.println("加糖");
}// 加奶
public void addmilk(){
System.out.println("加奶");
}}
2. 糕点
package com.itheima.pattern.factory.factory_abstract;
public abstract class Dessert {
public abstract void show();
}
具体产品(产品的具体种类):
1.coffee
package com.itheima.pattern.factory.factory_abstract;
public class AmericanCoffee extends Coffee {
@Override
public String getName() {
return "American Coffee";
}
}------------------------------------------------------------------------------------
package com.itheima.pattern.factory.factory_abstract;
public class LatteCoffee extends Coffee {
@Override
public String getName() {
return "latte coffee";
}
}
2.dessert
package com.itheima.pattern.factory.factory_abstract;
public class MatchaMouse extends Dessert {
@Override
public void show() {
System.out.println("MatchaMouse");
}
}
-------------------------------------------------------------------------------package com.itheima.pattern.factory.factory_abstract;
public class Trimisu extends Dessert{
@Override
public void show() {
System.out.println("Trimisu");
}
}
使用:
package com.itheima.pattern.factory.factory_abstract;
import com.itheima.pattern.factory.factory_method.Coffee;
public class Client {
public static void main(String[] args) {
// 创建意大利味甜品工厂
DessertFactory factory = new ItalyDessertFactory();
// 获取对应的拿铁咖啡
Coffee coffee = factory.createCoffee();
Dessert dessert = factory.createDessert();
dessert.show();
System.out.println(coffee.getName());
}
}
这种模式下,可以看到,相较于工厂方法模式,解决了可能造成的类爆炸问题,将只能创建一个产品的工厂(咖啡)转变为创建一个产品族(咖啡+糕点)。
如果要加同一个产品族的话(比如说德式的咖啡糕点),只需要在实现一个对应的工厂类即可,不需要修改其他的类。符合开闭原则。
3.4 优缺点
优点:
当一个产品族的多个对象被设计成一起工作时,它能确保客户端始终使用同一个产品族中的对象。
比如在这个案例中,如果创建的是意大利式的话,我们就只能使用意大利咖啡还有提拉米苏,不呢使用美式的。
缺点:
当产品族中需要增加一个新的产品的话,所有的工厂类都要修改。
4. 模式扩展
简单工厂+配置文件解除耦合:
可以通过工厂模式+配置文件的方式解除工厂对象和产品对象的耦合。在工厂类中加载配置文件的全类名,并创建对象进行储存,客户端如果需要对象,直接进行获取即可。
第一步:定义配置文件
在resources包下定义一个配置文件,名为bean.properties
american = com.itheima.pattern.factory.factory_config.AmericanCoffee
latte = com.itheima.pattern.factory.factory_config.LatteCoffee
第二步,改进工厂类,代码结构如下:
CoffeeFactory的具体代码逻辑:
package com.itheima.pattern.factory.factory_config;
import com.itheima.pattern.factory.factory_method.CoffeeFactory;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Properties;
import java.util.Set;public class CofffeeFactory {
// 加载配置文件,获取配置文件中配置的全类名,并创建该类的对象进行储存
// 1.定义容器对象出差呢咖啡对象
private static HashMap<String, Coffee> map = new HashMap<>();// 加载配置文件,只需要加载一次
static {
// 创建properties对象(用于加载配置类)
Properties properties = new Properties();
InputStream is = CoffeeFactory.class.getClassLoader().getResourceAsStream("bean.properties");
try {
properties.load(is);
// 从p中获取全类名称并创建对象(获取到的object:american,latte)
Set<Object> objects = properties.keySet();
for (Object object : objects) {
// 根据object获取对象全类名
String className = properties.getProperty((String) object);
// 通过反射技术创建对象
Class clazz = Class.forName(className);
Coffee o = (Coffee) clazz.newInstance();
map.put((String) object, o);
}
} catch (Exception e) {
e.printStackTrace();
}
}// 根据名称来获取对象
public static Coffee createCoffee(String name){
return map.get(name);
}}
使用该工厂:
package com.itheima.pattern.factory.factory_config;
public class CLient {
public static void main(String[] args) {
Coffee american = CofffeeFactory.createCoffee("american");
System.out.println(american);
}
}
在该方法中,静态成员变量用来储存创建的对象(键储存的是名称,值储存的是对应的对象),而读取配置文件以及创建对象在静态代码块中,目的就是只需要执行一次。
最后:
今天的分享就到这里。如果我的内容对你有帮助,请点赞,评论,收藏。创作不易,大家的支持就是我坚持下去的动力!(๑`・ᴗ・´๑)