Java常用设计模式-简单工厂和抽象工厂模式

简介: Java常用设计模式-简单工厂和抽象工厂模式

众所周知,现实生活中工厂是用于生产各种产品的地方,而在程序设计中,工厂为生产实例的地方。我们平时在Java创建对象时都需要通过new来创建一个类型的实例。

不过在一个复杂的系统里面,创建的对象也需要根据外界需求来确定。

我们从一个例子开始:一个水果店供应水果,有西瓜、橙子、苹果,水果店会根据顾客的喜好来供应不同的水果。

我们首先先新建顾客口味的枚举类顾客类,表示需求参数。

顾客口味枚举:

packagecom.gitee.swsk33.simplefactory.param;
/*** 客户口味参数*/publicenumFlavor {
/*** 甜*/SWEET,
/*** 酸甜*/SWEET_ACID,
/*** 酸*/ACID}

顾客:

packagecom.gitee.swsk33.simplefactory.param;
importlombok.Getter;
importlombok.Setter;
/*** 客户类*/@Getter@SetterpublicclassCustomer {
/*** 客户口味*/privateFlavorflavor;
}

然后新建各个水果。

西瓜:

packagecom.gitee.swsk33.simplefactory.model.impl;
importlombok.Getter;
importlombok.Setter;
/*** 西瓜类*/@Getter@SetterpublicclassWatermelon {
/*** 是否保熟*/privatebooleancooked;
}

苹果:

packagecom.gitee.swsk33.simplefactory.model.impl;
importlombok.Getter;
importlombok.Setter;
/*** 苹果类*/@Getter@SetterpublicclassApple {
/*** 甜度*/privateintsweetness;
}

橙子:

packagecom.gitee.swsk33.simplefactory.model.impl;
importlombok.Getter;
importlombok.Setter;
/*** 橙子类*/@Getter@SetterpublicclassOrange {
/*** 酸度*/privateintacidity;
}

好了,现在水果店要卖水果了,建立水果店如下:

// 来了个顾客Customercustomer=newCustomer();
customer.setFlavor(Flavor.SWEET_ACID);
if (customer.getFlavor() ==Flavor.SWEET) {
Watermelonwatermelon=newWatermelon();
// ...} elseif (customer.getFlavor() ==Flavor.SWEET_ACID) {
Appleapple=newApple();
// ...} elseif (customer.getFlavor() ==Flavor.ACID) {
Orangeorange=newOrange();
// ...}

目前整体结构如下:

网络异常,图片无法展示
|

看起来是个简单的条件判断,但是这样有两个主要问题:

  1. 代码重复:其余水果店或者其余卖水果的商店也要这么写
  2. 耦合度高:水果类被很大程度地包含至水果店类,只要修改水果类,商店类也要被全部修改

耦合度:某个类的代码包含了其它类的逻辑,包含的多耦合度就高,也容易互相影响。

因此我们需要使用工厂模式来解决这个问题。

1,简单工厂模式

实现简单工厂主要分为两大步:

  1. 把我们具体的产品资源(西瓜、橙子)抽象为一种事物(水果)
  2. 新建工厂类,并把生产具体实例(具体的西瓜橙子等等水果)的工作交给工厂

在上述“水果供应”的例子中,我们可以将各种水果抽象为一个接口或者是抽象类,然后新建一个水果工厂类生产它们,做类图如下:

网络异常,图片无法展示
|

我们来改造上述的系统。首先新建水果抽象类,并让每个水果都继承这个水果抽象类。

packagecom.gitee.swsk33.simplefactory.model;
importlombok.Getter;
importlombok.Setter;
/*** 水果抽象类*/@Getter@SetterpublicabstractclassFruit {
/*** 水果名*/privateStringname;
}

然后新建水果工厂,这就是简单工厂模式最核心的部分了:

packagecom.gitee.swsk33.simplefactory.factory;
importcom.gitee.swsk33.simplefactory.model.*;
importcom.gitee.swsk33.simplefactory.model.impl.Apple;
importcom.gitee.swsk33.simplefactory.param.Customer;
importcom.gitee.swsk33.simplefactory.model.impl.Orange;
importcom.gitee.swsk33.simplefactory.model.impl.Watermelon;
/*** 水果工厂类*/publicclassFruitFactory {
/*** 根据客户的口味生产相应的水果** @param customer 客户实例* @return 对应的水果*/publicstaticFruitget(Customercustomer) {
Fruitfruit=null;
switch (customer.getFlavor()) {
caseSWEET:
Watermelonwatermelon=newWatermelon();
watermelon.setName("西瓜");
watermelon.setCooked(true);
fruit=watermelon;
break;
caseSWEET_ACID:
Appleapple=newApple();
apple.setName("苹果");
apple.setSweetness(100);
fruit=apple;
break;
caseACID:
Orangeorange=newOrange();
orange.setName("橙子");
orange.setAcidity(12);
fruit=orange;
break;
      }
returnfruit;
   }
}

然后在水果店类中,我们来试一下:

packagecom.gitee.swsk33.simplefactory;
importcom.gitee.swsk33.simplefactory.factory.FruitFactory;
importcom.gitee.swsk33.simplefactory.model.Fruit;
importcom.gitee.swsk33.simplefactory.param.Customer;
importcom.gitee.swsk33.simplefactory.param.Flavor;
/*** 水果店类,卖水果*/publicclassStore {
publicstaticvoidmain(String[] args) {
// 来了个顾客Customercustomer=newCustomer();
customer.setFlavor(Flavor.SWEET_ACID);
// 从工厂得到水果FruitgetFruit=FruitFactory.get(customer);
System.out.println("得到水果:"+getFruit.getName());
   }
}

网络异常,图片无法展示
|

可见简单工厂模式也是根据外部需求来创建对象,只不过现在职责更加明确,创建对象的任务以及需求判断,全部交给了工厂。水果店只需告诉工厂客人要吃什么,并从工厂取得相应水果给客人,而不需要知道具体要什么水果

由于所有的水果都有同一个属性“水果名”,因此水果使用抽象类表示,抽象类放不同水果共有的属性。否则可以使用接口表示水果,根据实际情况而定。

2,抽象工厂模式

上面简单工厂模式只是涉及到一类产品的生产,那多类产品怎么办呢?

譬如说水果店现在开始卖果茶了,我们还需要新建一个果茶工厂,并和水果店联系起来吗?当然不是。这会导致水果店和各个工厂耦合度太紧,不利于扩展。

顾名思义,抽象工厂模式就是把各个生成实例的工厂再进一步抽象。通俗地讲就是需要构建“生产工厂的工厂”。

在讲解抽象工厂模式之前需要了解两个概念:

  • 产品族:也就是一个系列的产品,上面的西瓜、苹果、橙子就属于一个产品族
  • 产品等级结构:一个产品可以衍生其相关的一系列产品,例如西瓜可以做成西瓜汁,那么西瓜和西瓜汁就属于西瓜这个产品结构

我们来看个图:

网络异常,图片无法展示
|

可见纵坐标是产品族,也就是我们所说的一系列产品,横坐标表示一个产品的产品等级结构。

好了,我们现在只做水果和水果饮料的生产,对上述进行修改。

首先,水果和果茶我们都称之为水果类食品(Fruit Food),因此我们要把两个工厂(水果工厂,果茶工厂)抽象为工厂接口或者抽象类(水果类食品工厂,FruitFoodFactory),并通过工厂构造类根据需求创建工厂然后生产产品。

做类图如下:

网络异常,图片无法展示
|

我们先创建需求商品类型枚举并在顾客类中加这个属性:

商品类型需求枚举:

packagecom.gitee.swsk33.abstractfactory.param;
/*** 客户需求商品类型*/publicenumType {
/*** 水果*/FRUIT,
/*** 饮料*/DRINK}

顾客类:

packagecom.gitee.swsk33.abstractfactory.param;
importlombok.Getter;
importlombok.Setter;
/*** 客户类*/@Getter@SetterpublicclassCustomer {
/*** 客户需求商品类型*/privateTypetype;
/*** 客户口味*/privateFlavorflavor;
}

然后就是抽象出每个产品族,这里就不进行演示了,和抽象水果是一样的,按照类图很简单。

然后新建水果食品工厂的接口,即为我们的抽象工厂:

packagecom.gitee.swsk33.abstractfactory.factory;
importcom.gitee.swsk33.abstractfactory.model.Drink;
importcom.gitee.swsk33.abstractfactory.model.Fruit;
importcom.gitee.swsk33.abstractfactory.param.Customer;
/*** 水果食品工厂抽象*/publicinterfaceFruitFoodFactory {
/*** 获取水果** @param customer 顾客实例* @return 根据顾客口味获得对应水果*/FruitgetFruit(Customercustomer);
/*** 获取饮料** @param customer 顾客实例* @return 根据顾客口味获得对应水果饮料*/DrinkgetDrink(Customercustomer);
}

然后建立水果工厂和水果饮料工厂实现抽象工厂:

水果工厂:

packagecom.gitee.swsk33.abstractfactory.factory.impl;
importcom.gitee.swsk33.abstractfactory.factory.FruitFoodFactory;
importcom.gitee.swsk33.abstractfactory.model.*;
importcom.gitee.swsk33.abstractfactory.model.impl.Apple;
importcom.gitee.swsk33.abstractfactory.param.Customer;
importcom.gitee.swsk33.abstractfactory.model.impl.Orange;
importcom.gitee.swsk33.abstractfactory.model.impl.Watermelon;
/*** 水果工厂类*/publicclassFruitFactoryimplementsFruitFoodFactory {
@OverridepublicFruitgetFruit(Customercustomer) {
Fruitfruit=null;
switch (customer.getFlavor()) {
caseSWEET:
Watermelonwatermelon=newWatermelon();
watermelon.setName("西瓜");
watermelon.setCooked(true);
fruit=watermelon;
break;
caseSWEET_ACID:
Appleapple=newApple();
apple.setName("苹果");
apple.setSweetness(100);
fruit=apple;
break;
caseACID:
Orangeorange=newOrange();
orange.setName("橙子");
orange.setAcidity(12);
fruit=orange;
break;
      }
returnfruit;
   }
@OverridepublicDrinkgetDrink(Customercustomer) {
returnnull;
   }
}

水果饮料工厂(果茶工厂):

packagecom.gitee.swsk33.abstractfactory.factory.impl;
importcom.gitee.swsk33.abstractfactory.factory.FruitFoodFactory;
importcom.gitee.swsk33.abstractfactory.model.Drink;
importcom.gitee.swsk33.abstractfactory.model.Fruit;
importcom.gitee.swsk33.abstractfactory.model.impl.AppleDrink;
importcom.gitee.swsk33.abstractfactory.model.impl.OrangeDrink;
importcom.gitee.swsk33.abstractfactory.model.impl.WatermelonDrink;
importcom.gitee.swsk33.abstractfactory.param.Customer;
publicclassDrinkFactoryimplementsFruitFoodFactory {
@OverridepublicFruitgetFruit(Customercustomer) {
returnnull;
   }
@OverridepublicDrinkgetDrink(Customercustomer) {
Drinkdrink=null;
switch (customer.getFlavor()) {
caseSWEET:
WatermelonDrinkwatermelonDrink=newWatermelonDrink();
watermelonDrink.setName("西瓜汁");
drink=watermelonDrink;
break;
caseSWEET_ACID:
AppleDrinkappleDrink=newAppleDrink();
appleDrink.setName("苹果汁");
drink=appleDrink;
break;
caseACID:
OrangeDrinkorangeDrink=newOrangeDrink();
orangeDrink.setName("橙汁");
drink=orangeDrink;
break;
      }
returndrink;
   }
}

因为水果工厂不生产果茶但是水果工厂实现了水果类食物工厂的接口,因此也有getDrink方法,将其留空返回null即可。

果茶工厂和水果工厂类似,同样是实现水果类食物工厂接口,并完成getDrink方法,但是把getFruit方法留空返回null

然后就是工厂构建类(生产工厂的工厂):

packagecom.gitee.swsk33.abstractfactory.builder;
importcom.gitee.swsk33.abstractfactory.factory.FruitFoodFactory;
importcom.gitee.swsk33.abstractfactory.factory.impl.DrinkFactory;
importcom.gitee.swsk33.abstractfactory.factory.impl.FruitFactory;
importcom.gitee.swsk33.abstractfactory.param.Customer;
/*** 工厂建造器*/publicclassFruitFoodFactoryBuilder {
/*** 根据客户的需求类型建造出对应的工厂** @param customer 顾客实例* @return 对应的工厂实例*/publicstaticFruitFoodFactorybuildFactory(Customercustomer) {
FruitFoodFactoryfactory=null;
switch (customer.getType()) {
caseFRUIT:
factory=newFruitFactory();
break;
caseDRINK:
factory=newDrinkFactory();
break;
      }
returnfactory;
   }
}

同样地,在水果店只需要调用工厂构造类先构建对应工厂,再调用对应工厂对应方法生成产品即可。

packagecom.gitee.swsk33.abstractfactory;
importcom.gitee.swsk33.abstractfactory.builder.FruitFoodFactoryBuilder;
importcom.gitee.swsk33.abstractfactory.factory.FruitFoodFactory;
importcom.gitee.swsk33.abstractfactory.model.Drink;
importcom.gitee.swsk33.abstractfactory.param.Customer;
importcom.gitee.swsk33.abstractfactory.param.Flavor;
importcom.gitee.swsk33.abstractfactory.param.Type;
/*** 商店类*/publicclassStore {
publicstaticvoidmain(String[] args) {
// 模拟客户来买东西Customercustomer=newCustomer();
// 客户要买饮料、口味酸customer.setType(Type.DRINK);
customer.setFlavor(Flavor.ACID);
// 首先建造对应的工厂FruitFoodFactoryfactory=FruitFoodFactoryBuilder.buildFactory(customer);
// 工厂生产物品Drinkdrink=factory.getDrink(customer);
System.out.println("顾客购买:"+drink.getName());
   }
}

结果:

网络异常,图片无法展示
|

可见抽象工厂看起来麻烦,其实逻辑很清晰,主要可以总结为以下几个大步骤:

  1. 将每个产品族进行抽象并创建对应的具体类
  2. 建立抽象工厂
  3. 实现抽象工厂,每个具体实现的工厂负责生产一个产品族
  4. 建立工厂建造器,根据需求实例化对应的工厂

可见抽象工厂模式,是先确定要生产的产品族并调用工厂建造器实例化对应的工厂,再确定要生产的具体产品利用得到的工厂生成对应的产品实例。

需要注意的是,在简单工厂模式中,由于只有一个工厂,因此工厂中的生产方法是static的,而抽象工厂模式中,由于有多个工厂,因此这时每个工厂的方法就不是static了,但是工厂建造器中建造工厂的方法可以是static的。

3,总结

可见简单工厂中,水果店和工厂相关联,因此降低了水果店和水果的耦合度,水果店无需知道需要什么水果,只需把相应水果取得即可;抽象工厂模式中水果店和工厂构造类相关联这时水果店不仅不用去知道自己需要什么水果,也不需要知道自己需要去找哪家工厂,进一步降低耦合。

可见,抽象工厂模式是基于简单工厂模式的进一步抽象。

示例仓库地址:

相关文章
|
2天前
|
设计模式 消息中间件 负载均衡
实现可扩展和可靠的分布式系统的Java设计模式
实现可扩展和可靠的分布式系统的Java设计模式
|
1天前
|
设计模式 安全 Java
从零开始:Java开发者的设计模式指南
从零开始:Java开发者的设计模式指南
|
1天前
|
设计模式 Java 数据安全/隐私保护
Java中的设计模式:从入门到精通
Java中的设计模式:从入门到精通
|
3天前
|
设计模式 缓存 算法
编写高效的Java工具类:实用技巧与设计模式
编写高效的Java工具类:实用技巧与设计模式
|
2天前
|
设计模式 Java 开发者
Java中设计模式的应用与实现详解
Java中设计模式的应用与实现详解
|
2天前
|
设计模式 监控 Java
Java中容错和恢复能力的设计模式
Java中容错和恢复能力的设计模式
|
3天前
|
设计模式 Java
设计模式在Java项目中的实际应用
设计模式在Java项目中的实际应用
|
7天前
|
设计模式 缓存 安全
Java设计模式的单例模式应用场景
Java设计模式的单例模式应用场景
21 4
|
2月前
|
设计模式 安全 Java
【JAVA】Java 中什么叫单例设计模式?请用 Java 写出线程安全的单例模式
【JAVA】Java 中什么叫单例设计模式?请用 Java 写出线程安全的单例模式
|
11天前
|
设计模式 缓存 安全
Java设计模式的单例模式应用场景
Java设计模式的单例模式应用场景
20 8