万字解析设计模式之工厂方法模式与简单工厂模式
一、概述
1.1简介
在java中,万物皆对象,这些对象都需要创建,如果创建的时候直接new该对象,就会对该对象耦合严重,假如我们要更换对象,所有new对象的地方都需要修改一遍,这显然违背了软件设计的开闭原则。如果我们使用工厂来生产对象,我们就只和工厂打交道就可以了,彻底和对象解耦,如果要更换对象,直接在工厂里更换该对象即可,达到了与对象解耦的目的;所以说,工厂模式最大的优点就是:解耦。
在本教程中会介绍三种工厂的使用
- 简单工厂模式(不属于GOF的23种经典设计模式)
- 工厂方法模式
- 抽象工厂模式
1.2简单工厂模式
概述
简单工厂不是一种设计模式,反而比较像是一种编程习惯。
简单工厂模式(Simple Factory Pattern)是一种创建型设计模式,它提供了一个工厂类,该类专门用于创建其他对象。简单工厂模式属于静态工厂模式,即在工厂类中提供静态方法,根据传入的参数不同返回不同的对象实例。
结构
简单工厂模式包含三个角色:
- 工厂类(Creator):提供了创建产品的方法,调用者通过该方法来获取产品。
- 抽象产品类(Product):定义了产品的规范,描述了产品的主要特性和功能。
- 具体产品类(Concrete Product):定义具体的产品实现类,实现抽象产品类中的抽象方法。
实现
原先逻辑
SimpleCoffeeFactory.java
package com.yanyu.SimpleFactory;
public class SimpleCoffeeFactory {
public Coffee createCoffee(String type) {
Coffee coffee = null;
if("americano".equals(type)) {
coffee = new AmericanoCoffee();
} else if("latte".equals(type)) {
coffee = new LatteCoffee();
}
return coffee;
}
}
Coffeestore.java
package com.yanyu.SimpleFactory;
public class Coffeestore {
public Coffee orderCoffee(String type) {
Coffee coffee = null;
if ("american".equals(type)) {
coffee = new AmericanoCoffee();
} else if ("latte".equals(type)) {
coffee = new LatteCoffee();
} else {
throw new RuntimeException("对不起,您所点的咖啡没有");
}
// 加配料
coffee.addMilk();
coffee.addSugar();
return coffee;
}
}
Coffee.java
package com.yanyu.SimpleFactory;
public abstract class Coffee {
public abstract String getName();
// 加糖
public void addSugar() {
System.out.println("加糖");
}
// 加奶
public void addMilk() {
System.out.println("加奶");
}
}
AmericanoCoffee.java
package com.yanyu.SimpleFactory;
public class AmericanoCoffee extends Coffee{
public String getName(){
return"美式咖啡";
}
}
LatteCoffee.java
package com.yanyu.SimpleFactory;
public class LatteCoffee extends Coffee{
public String getName(){
return"拿铁咖啡";
}
}
test
package com.yanyu.SimpleFactory;
public class Client {
public static void main(String[] args) {
// 创建咖啡店类对象
Coffeestore store = new Coffeestore();
Coffee coffee = store.orderCoffee("latte");
System.out.println(coffee.getName());
}
}
SimpleCoffeeFactory是工厂类,负责生产不同类型的咖啡,Coffeestore是客户端,通过调用工厂类的方法来获取咖啡。简单工厂模式属于创建型模式,通过将对象的创建工作交给工厂类来实现客户端与具体产品类的解耦,可以方便地扩展新的产品类型,同时也便于管理和维护。在这个例子中,Coffeestore只需要调用工厂类的createCoffee方法即可获取不同类型的咖啡,而不需要关心具体如何创建。
工厂(factory)处理创建对象的细节,一旦有了SimpleCoffeeFactory,CoffeeStore类中的orderCoffee()就变成此对象的客户,后期如果需要Coffee对象直接从工厂中获取即可。这样也就解除了和Coffee实现类的耦合,同时又产生了新的耦合,CoffeeStore对象和SimpleCoffeeFactory工厂对象的耦合,工厂对象和商品对象的耦合。
后期如果再加新品种的咖啡,我们势必要需求修改SimpleCoffeeFactory的代码,违反了开闭原则。工厂类的客户端可能有很多,比如创建美团外卖等,这样只需要修改工厂类的代码,省去其他的修改操作。
扩展静态工厂
静态工厂是一种创建对象的方式,它通过一个静态方法来获取对象,有时也被称为静态工厂方法。静态工厂方法通常不需要创建对象,所以可以在不创建对象的情况下直接返回对象。
通过扩展静态工厂,我们可以让工厂方法更加灵活,以便能够生产不同类型的对象。它也不是23种设计模式中的
public class SimpleCoffeeFactory {
public static Coffee createCoffee(String type) {
Coffee coffee = null;
if("americano".equals(type)) {
coffee = new AmericanoCoffee();
} else if("latte".equals(type)) {
coffee = new LatteCoffee();
}
return coffe;
}
}
优缺点
优点:
封装了创建对象的过程,可以通过参数直接获取对象。把对象的创建和业务逻辑层分开,这样以后就避免了修改客户代码,如果要实现新产品直接修改工厂类,而不需要在原代码中修改,这样就降低了客户代码修改的可能性,更加容易扩展。
缺点:
增加新产品时还是需要修改工厂类的代码,违背了“开闭原则”。
1.3工厂方法模式
针对上例中的缺点,使用工厂方法模式就可以完美的解决,完全遵循开闭原则。
概述
工厂方法模式(Factory Method Pattern)又称为工厂模式,是一种创建型设计模式。在工厂方法模式中,定义一个用于创建对象的接口,但让子类决定将哪一个类实例化。工厂方法把类的实例化推迟到子类中进行,从而实现了解耦。
结构
工厂方法模式的主要角色:
- 抽象工厂(Abstract Factory):提供了创建产品的接口,调用者通过它访问具体工厂的工厂方法来创建产品。
- 具体工厂(ConcreteFactory):主要是实现抽象工厂中的抽象方法,完成具体产品的创建。
- 抽象产品(Product):定义了产品的规范,描述了产品的主要特性和功能。
- 具体产品(ConcreteProduct):实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂之间一一对应。
实现
抽象工厂:
package com.yanyu.FactoryMethod;
public interface CoffeeFactory {
Coffee createCoffee();
}
具体工厂:
public class LatteCoffeeFactory implements CoffeeFactory {
public Coffee createCoffee() {
return new LatteCoffee();
}
}
public class AmericanCoffeeFactory implements CoffeeFactory {
public Coffee createCoffee() {
return new AmericanoCoffee();
}
}
咖啡店类:
package com.yanyu.FactoryMethod;
public class CoffeeStore {
private CoffeeFactory factory;
public void SetFactory(CoffeeFactory factory) {
this.factory = factory;
}
public Coffee orderCoffee() {
Coffee coffee = factory.createCoffee();
coffee.addMilk();
coffee.addSugar();
return coffee;
}
}
测试
package com.yanyu.FactoryMethod;
public class client {
public static void main(String[] args) {
// 创建咖啡店对象
CoffeeStore store = new CoffeeStore();
//创建工厂对象
CoffeeFactory factory = new AmericanCoffeeFactory();
store.SetFactory(factory);
// 点咖啡
Coffee coffee = store.orderCoffee();
System.out.println(coffee.getName());
}
}
这就是实现开闭原则的核心:一旦转换成功,我们拓展功能就只需要增加相应类的or接口即可,不需要再动原来的代码
工厂方法模式是简单工厂模式的进一步抽象。由于使用了多态性,工厂方法模式保持了简单工厂模式的优点,而且克服了它的缺点。
优缺点
优点:
- 用户只需要知道具体工厂的名称就可得到所要的产品,无须知道产品的具体创建过程;
- 在系统增加新的产品时只需要添加具体产品类和对应的具体工厂类,无须对原工厂进行任何修改,满足开闭原则;
缺点:
- 每增加一个产品就要增加一个具体产品类和一个对应的具体工厂类,这增加了系统的复杂度。
工厂方法模式增加了系统抽象性和理解难度,需要对工厂类和具体产品类进行抽象和设计。
常见应用
常见的使用场景包括:
数据库连接池的实现。
日志处理器的实现。
图形界面控件库的开发。
任何需要创建对象的场景,特别是对象创建过程复杂或需要隐藏细节的场景。
1.4抽象工厂模式
产品族和同一级别产品
产品族是指一组具有相似特征和用途的产品,它们通常共享相同的品牌名称、市场定位和推广策略。比如,苹果公司的iPhone产品族包括iPhone 12、iPhone 11、iPhone SE 等,它们都是苹果公司的智能手机产品,具有相似的功能和设计。
同一级别产品是指在市场上竞争的,具有相似功能和特点的产品。这些产品可能来自不同的品牌,但是它们都是在同一细分市场上竞争的产品。举例来说,苹果公司的iPhone和三星公司的Galaxy手机都是智能手机,它们在市场上是同一级别的产品。
概述
在抽象工厂模式中,我们定义一个抽象工厂接口,该接口具有多个工厂方法,每个工厂方法负责创建一个产品族中的一种产品。然后我们创建一个具体的工厂类来实现抽象工厂接口,并在其中实现工厂方法,以便能够创建具体的产品。
抽象工厂模式是工厂方法模式的升级版本,工厂方法模式只生产一个等级的产品,而抽象工厂模式可生产多个等级的产品。
结构
- 抽象工厂(Abstract Factory):提供了创建产品的接口,它包含多个创建产品的方法,可以创建多个不同等级的产品。
- 具体工厂(Concrete Factory):主要是实现抽象工厂中的多个抽象方法,完成具体产品的创建。
- 抽象产品(Product):定义了产品的规范,描述了产品的主要特性和功能,抽象工厂模式有多个抽象产品。
- 具体产品(ConcreteProduct):实现了抽象产品角色所定义的接口,由具体工厂来创建,它 同具体工厂之间是多对一的关系。
实现
抽象工厂:
package com.yanyu.AbstractFactory;
public interface DessertFactory {
Coffee createCoffee();
Dessert createDessert();
}
抽象类
package com.yanyu.AbstractFactory;
public abstract class Dessert {
public abstract void show();
}
具体工厂:
//美式甜点工厂
public class AmericanDessertFactory implements DessertFactory {
public Coffee createCoffee() {
return new AmericanCoffee();
}
public Dessert createDessert() {
return new MatchaMousse();
}
}
//意大利风味甜点工厂
public class ItalyDessertFactory implements DessertFactory {
public Coffee createCoffee() {
return new LatteCoffee();
}
public Dessert createDessert() {
return new Tiramisu();
}
}
具体类:
package com.yanyu.AbstractFactory;
public class Trimisu extends Dessert {
public void show() {
System.out.println("提拉米苏");
}
}
package com.yanyu.AbstractFactory;
public class MatchaMousse extends Dessert {
public void show() {
System.out.println("抹茶慕斯");
}
}
test
package com.yanyu.AbstractFactory;
public class Client {
public static void main(String[] args) {
// 创建的是意大利风味甜品工厂对象
// ItalyDessertFactory factory = new ItalyDessertFactory();
AmericanDessertFactory factory = new AmericanDessertFactory();
// 获取拿铁咖啡和提拉米苏甜品
Coffee coffee = factory.createCoffee();
Dessert dessert = factory.createDessert();
System.out.println(coffee.getName()); dessert.show();
}
}
如果要加同一个产品族的话,只需要再加一个对应的工厂类即可,不需要修改其他的类。
优缺点
优点:
当一个产品族中的多个对象被设计成一起工作时,它能保证客户端始终只使用同一个产品族中的对象。
缺点:
由于抽象工厂只能创建某个产品族中的全部产品,所以加入新产品时需要修改所有的具体工厂类,增加了维护成本
常见应用
抽象工厂模式常见应用包括以下几个方面:
1. 操作系统的UI界面设计:操作系统常常提供多种不同的UI界面风格,如Windows提供的经典风格、XP风格、Win7风格等,可以使用抽象工厂模式来实现对不同风格的UI界面进行定制和管理。
2. 游戏开发:游戏中的物品、角色、道具等都可以使用抽象工厂模式来进行设计,不同的游戏可以使用不同的抽象工厂来创建不同的游戏元素。
3. 数据库连接:数据库连接器可以使用抽象工厂模式来设计,通过使用不同的工厂来创建不同类型的数据库连接器,从而支持多种不同的数据库类型。
4. 跨平台开发:不同的操作系统和不同的硬件架构需要不同的代码实现,使用抽象工厂模式可以通过实现不同的工厂来创建适配不同平台的代码。
5. GUI开发:GUI框架常常需要支持多种不同的控件和主题,使用抽象工厂模式可以通过不同的工厂来创建不同的控件和主题。
1.5JDK源码解析-Collection.iterator方法
public class Demo {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("令狐冲");
list.add("风清扬");
list.add("任我行");
//获取迭代器对象
Iterator<String> it = list.iterator();
//使用迭代器遍历
while(it.hasNext()) {
String ele = it.next();
System.out.println(ele);
}
}
}
Collection接口是抽象工厂类,ArrayList是具体的工厂类;Iterator接口是抽象商品类,ArrayList类中的Iter内部类是具体的商品类。在具体的工厂类中iterator()方法创建具体的商品类的对象。
二、实验
简单工厂模式
任务描述
本关任务:某电视机厂专为各知名电视机品牌代工生产各类电视机,当需要海尔牌电视机时只需要在调用该工厂的工厂方法时传入参数“Haier”,需要海信电视机时只需要传入参数“Hisense”,工厂可以根据传入的不同参数返回不同品牌的电视机。现使用简单工厂模式来模拟,程序将会自动从配置文件中读取参数,请根据以下类图来补全代码。
相关知识
为了完成本关任务,你需要掌握:
- 简单工厂模式包含的角色;
- 简单工厂模式缺点。
简单工厂模式包含的角色
- Factory:工厂角色
- Product:抽象产品角色
- ConcreteProduct:具体产品角色
简单工厂模式缺点
- 工厂类集中了所有产品的创建逻辑,职责过重,一旦不能正常工作,整个系统都要受到影响;
- 增加系统中类的个数(引入了新的工厂类),增加了系统的复杂度和理解难度;
- 系统扩展困难,一旦添加新产品不得不修改工厂逻辑;
- 由于使用了静态工厂方法,造成工厂角色无法形成基于继承的等级结构,工厂类不能得到很好地扩展。
编程要求
根据提示,给“HaierTV.java,HisenseTV.java,TVFactory.java”三个代码文件内注释需要填空地方补充代码。
Product:抽象产品角色
package step1;
public interface TV {
void play();
}
ConcreteProduct:具体产品角色
package step1;
public class HaierTV implements TV/**填空——————————**/{
@Override
public void play() {
System.out.println("海尔电视机播放中......");
}
}
package step1;
public class HisenseTV implements TV/**填空——————————**/{
@Override
public void play() {
System.out.println("海信电视机播放中......");
}
}
Factory:工厂角色
package step1;
public class TVFactory {
public static TV produceTV(String brand) throws Exception
{
if(brand.equalsIgnoreCase("Haier"))
{
System.out.println("电视机工厂生产海尔电视机!");
/******填空******/
return new HaierTV();
/************/
}
else if(brand.equalsIgnoreCase("Hisense"))
{
System.out.println("电视机工厂生产海信电视机!");
/******填空******/
return new HisenseTV();
/************/
}
else
{
throw new Exception("对不起,暂不能生产该品牌电视机!");
}
}
}
顾客类
package step1;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.File;
public class XMLUtilTV {
public static String getBrandName()
{
try
{
//创建文档对象
DocumentBuilderFactory dFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = dFactory.newDocumentBuilder();
Document doc;
doc = builder.parse(new File("/data/workspace/myshixun/src/SimpleFactoryconfigTV.xml"));
//获取包含品牌名称的文本节点
NodeList nl = doc.getElementsByTagName("brandName");
Node classNode=nl.item(0).getFirstChild();
String brandName=classNode.getNodeValue().trim();
return brandName;
}
catch(Exception e)
{
e.printStackTrace();
return null;
}
}
}
这段代码实现了从指定的XML配置文件中获取电视品牌名称的方法。具体步骤如下:
1. 导入所需要的Java类库,包括org.w3c.dom、javax.xml.parsers和java.io.File。
2. 创建XMLUtilTV类,并定义静态的getBrandName方法,返回类型为String,用于获取配置文件中的品牌名称。
3. 在getBrandName方法中,使用DocumentBuilderFactory类和DocumentBuilder类来创建文档对象,即读取XML配置文件。
4. 获取文档对象中的品牌名称节点,通过doc.getElementsByTagName("brandName")获取节点列表,然后使用nl.item(0)获取第一个节点,再使用getFirstChild()获取节点的第一个子节点。
5. 最后通过classNode.getNodeValue().trim()获取节点的文本值,并去掉开头结尾的空格。
6. 如果有异常发生,打印堆栈信息,并返回null。
package step1;
public class Client {
public static void main(String args[])
{
try
{
TV tv;
String brandName= XMLUtilTV.getBrandName();
tv= TVFactory.produceTV(brandName);
tv.play();
}
catch(Exception e)
{
System.out.println(e.getMessage());
}
}
}
工厂方法模式
任务描述
本关任务:将原有的电视机工厂进行分割,为每种品牌的电视机提供一个子工厂,海尔工厂专门负责生产海尔电视机,海信工厂专门负责生产海信电视机,如果需要生产 TCL 电视机或创维电视机,只需要对应增加一个新的 TCL 工厂或创维工厂即可,原有的工厂无须做任何修改,使得整个系统具有更加的灵活性和可扩展性。 现使用工厂方法模式来模拟,程序将会自动从配置文件中读取参数,请根据以下类图来修补代码。
相关知识
为了完成本关任务,你需要掌握:
- 工厂方法模式包含的角色;
- 实现要点;
- 工厂方法模式缺点。
工厂方法模式包含的角色
- Product:抽象产品
- ConcreteProduct:具体产品
- Factory:抽象工厂
- ConcreteFactory:具体工厂
实现要点
- 让所有产品都遵循同一接口。 该接口必须声明对所有产品都有意义的方法。
- 在创建类中添加一个空的工厂方法。 该方法的返回类型必须遵循通用的产品接口。
- 在创建者代码中找到对于产品构造函数的所有引用。 将它们依次替换为对于工厂方法的调用, 同时将创建产品的代码移入工厂方法。你可能需要在工厂方法中添加临时参数来控制返回的产品类型。工厂方法的代码看上去可能非常糟糕。 其中可能会有复杂的 switch 分支运算符, 用于选择各种需要实例化的产品类。 但是不要担心, 我们很快就会修复这个问题。
- 为工厂方法中的每种产品编写一个创建者子类, 然后在子类中重写工厂方法, 并将基本方法中的相关创建代码移动到工厂方法中。
- 如果应用中的产品类型太多, 那么为每个产品创建子类并无太大必要, 这时你也可以在子类中复用基类中的控制参数。
- 如果代码经过上述移动后, 基础工厂方法中已经没有任何代码, 你可以将其转变为抽象类。 如果基础工厂方法中还有其他语句, 你可以将其设置为该方法的默认行为。
工厂方法模式缺点
- 系统中类的个数将成对增加,在一定程度上增加了系统的复杂度,会给系统带来一些额外的开销;
- 增加了系统的抽象性和理解难度。
编程要求
根据提示,给“HaierTVFactory.java,HisenseTVFactory.java”两个代码文件内注释需要填空地方补充代码。
Product:抽象产品
package step3;
public interface TV {
void play();
}
ConcreteProduct:具体产品、
package step3;
public class HaierTV implements TV{
@Override
public void play() {
System.out.println("海尔电视机播放中......");
}
}
package step3;
public class HisenseTV implements TV{
@Override
public void play() {
System.out.println("海信电视机播放中......");
}
}
Factory:抽象工厂
package step3;
public interface TVFactory {
TV produceTV();
}
ConcreteFactory:具体工厂
package step3;
public class HaierTVFactory implements TVFactory{
@Override
public TV produceTV() {
/*填空*/
return new HaierTV();
}
}
package step3;
public class HisenseTVFactory implements TVFactory{
@Override
public TV produceTV() {
/*填空*/
return new HisenseTV();
}
}
顾客
package step3;
public class Client {
public static void main(String args[])
{
try
{
TV tv;
TVFactory factory;
factory=(TVFactory)XMLUtil.getBean();
tv=factory.produceTV();
tv.play();
}
catch(Exception e)
{
System.out.println(e.getMessage());
}
}
}
package step3;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.File;
public class XMLUtil {
public static Object getBean()
{
try
{
//创建文档对象
DocumentBuilderFactory dFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = dFactory.newDocumentBuilder();
Document doc;
doc = builder.parse(new File("./src/FactoryMethodconfig.xml"));
//获取包含类名的文本节点
NodeList nl = doc.getElementsByTagName("className");
Node classNode=nl.item(0).getFirstChild();
String cName=classNode.getNodeValue();
//通过类名生成实例对象并将其返回
Class c=Class.forName(cName);
Object obj=c.newInstance();
return obj;
}
catch(Exception e)
{
e.printStackTrace();
return null;
}
}
}