文章目录
前言
文章参考黑马的设计模式讲义以及c语言中文网教程C语言中文网教程,还有一些自己的理解,对于一些概念的东西还是很难自己总结一套出来的。黑马视频: 黑马设计模式。下面文章介绍工厂模式及其在源码中的作用
提示:以下是本篇文章正文内容,下面案例可供参考
1. 概述
在java中,万物皆对象,这些对象都需要创建,如果创建的时候直接new该对象,就会对该对象耦合严重,再给对象赋值等操作的时候都需要亲自从对象入手,这显然违背了开闭原则。我们使用工厂模式来帮我们创建对象,在创建对象的时候只需要和工厂打交道就可以了,这样就可以和对象进行了解耦,这也满足了创建型模式中 创建和使用分离 的特点。我们把被创建的对象称为“产品”,把创建产品的对象称为“工厂, 下面介绍三种工厂的使用
- 简单工厂模式(不属于GOF的23种经典设计模式)
- 工厂方法模式
- 抽象工厂模式
2. 简单工厂模式
简单工厂不是一种设计模式,反而比较像是一种编程习惯。在简单工厂模式中获取对象的方法是静态的,当然也可以设置为不静态,一般都是静态的。简单工厂又叫做静态工厂。
1. 结构
简单工厂分为三种结构,抽象产品、具体产品和简单工厂
- 简单工厂:简单工厂方法的核心,负责创建产品,用户只需要调用工厂的获取方法就可以拿到具体的产品
- 抽象产品:定义了产品的规范,描述了产品的主要特性和功能。
- 具体产品:实现或者继承抽象产品的子类,在工厂中被创建
2. 代码实现
下面来用代码实现一下,我们设计一个咖啡店,这个咖啡店可以卖两种咖啡:美式咖啡和摩卡咖啡,而咖啡工厂是负责创建咖啡的,用户到咖啡店点咖啡,然后咖啡店通过工厂创建咖啡,创建完咖啡后可以给咖啡加糖或者加奶返回给顾客。
1、抽象产品 - 咖啡抽象类,里面定义了加糖方法和加奶方法
public abstract class Coffee {
public abstract String getName();
//加糖
public void addSugger(){
System.out.println("加糖");
}
//加奶
public void addMilk(){
System.out.println("加奶");
}
}
2、具体产品 - 拿铁咖啡
public class LatteeCoffee extends Coffee {
@Override
public String getName() {
return "拿铁咖啡";
}
}
3、具体产品 - 美式咖啡
public class AmercianCoffee extends Coffee {
@Override
public String getName() {
return "美式咖啡";
}
}
4、咖啡店,负责调用工厂制作咖啡的,相当于用户和工厂之间的中介了
public class CafferStore {
public Coffee orderCoffer(String name){
//获取咖啡对象
Coffee factory = SimpleCoffeeFactory.createFactory(name);
factory.addMilk();
factory.addSugger();
return factory;
}
}
5、简单工厂 - 咖啡工厂,用于生产咖啡给咖啡店的
public class SimpleCoffeeFactory {
//简单咖啡工厂类用来生成咖啡
//好处就是不需要创建多余的对象
public static Coffee createFactory(String name){
//声明coffer类型的变量,根据不同类型创建不同coffee子类
Coffee coffee = null;
if("美式咖啡".equals(name)){
coffee = new AmercianCoffee();
}
else if("拿铁咖啡".equals(name)){
coffee = new LatteeCoffee();
}else {
throw new RuntimeException("对不起,你所点的咖啡没有");
}
return coffee;
}
}
6、执行结果
3. 优点和缺点
优点:
- 工厂和客户的职责分明,工厂只负责创建产品,而顾客负责通知工厂需要什么产品。上面的例子中,咖啡店通知工厂生产产品,然后咖啡店进行加奶加糖的操作返回给顾客,顾客是不需要了解具体的创建过程的,只需要把名字给咖啡店就可以拿到想要的产品了。把对象的创建和业务逻辑层分开,这样以后就避免了修改客户代码,更具有扩展性。在使用上感觉就是开发中的自己定义的一系列工具类。
- 对于简单工厂,还可以使用配置文件的方法在不改动代码的情况下进行对咖啡种类的修改
缺点:
- 简单工厂中只使用一个工厂就完成了所有产品的创建,而一旦产品过多就会造成工厂代码臃肿。违背高聚合原则。
- 简单工厂模式使用了 static 工厂方法,造成工厂角色无法形成基于继承的等级结构。
- 如果想要在原来的基础上添加一种产品,我们就得修改工厂类,改变工厂类的逻辑
3. 使用配置文件完成简单工厂模式
在上面的基础上我们把类首先写在配置文件中,然后工厂直接加载配置文件,先创建好类存起来,需要的时候直接返回就行了,这种方法好处就是一开始就读取了所有的咖啡类,然后只要需要咖啡店通过名字去获取就行了,如果新增了一种咖啡,只需要在配置文件中添加上新的咖啡类,然后修改咖啡店的代码即可。
这种方法在写一些小项目的时候也会常常用到,比如提前把一些类缓存到 map 中,又或者把一些关系缓存到 map 中。举个例子,在频繁使用到反射的项目中,我们可以提前把需要的反射类和反射方法缓存起来,要用的时候直接从缓存 map 中获取即可
1、咖啡接口
public abstract class Coffee {
public abstract String getName();
//加糖
public void addSugger(){
System.out.println("加糖");
}
//加奶
public void addMilk(){
System.out.println("加奶");
}
}
2、拿铁咖啡
public class LatteCoffee extends Coffee {
@Override
public String getName() {
return "拿铁咖啡";
}
}
3、美式咖啡
public class AmericanCoffee extends Coffee {
@Override
public String getName() {
return "美式咖啡";
}
}
4、咖啡店
public class CafferStore {
public Coffee orderCoffer(String name){
//声明coffer类型的变量,根据不同类型创建不同coffee子类
Coffee coffee = null;
//从工厂中直接获取
if(name.equals("拿铁咖啡")){
coffee = CoffeeFactory.createCoffee("latte");
}else if(name.equals("美式咖啡")){
coffee = CoffeeFactory.createCoffee("american");
}else{
throw new RuntimeException("没有这种类型的咖啡");
}
coffee.addMilk();
coffee.addSugger();
return coffee;
}
}
5、咖啡工厂
public class CoffeeFactory {
//加载配置文件,获取全类名,创建对象存储起来
private static Map<String,Coffee> map = new HashMap();
static {
Properties p = new Properties();
InputStream is = CoffeeFactory.class.getClassLoader().getResourceAsStream("bean.properties");
try {
p.load(is);
//遍历Properties集合对象
Set<Object> keys = p.keySet();
for (Object key : keys) {
//根据键获取值(全类名)
String className = p.getProperty((String) key);
//获取字节码对象
Class clazz = Class.forName(className);
//创建对象
Coffee obj = (Coffee) clazz.newInstance();
//存入map中
map.put(String.valueOf(key),obj);
}
} catch (Exception e) {
e.printStackTrace();
}
}
public static Coffee createCoffee(String name){
return map.get(name);
}
}
6、配置类 bean.properties
american=com.jianglianghao.factory.config_factory.AmericanCoffee
latte=com.jianglianghao.factory.config_factory.LatteCoffee
7、结果
4. 工厂方法模式
1. 概述
“工厂方法模式” 是对简单工厂模式的进一步抽象化,其好处是可以使系统在不修改原来代码的情况下引进新的产品,即满足开闭原则。我们只需要定义一个用于创建对象的接口,让子类决定实例化哪个产品类对象。举个例子,在上面的咖啡例子中,我们定义一个总的咖啡工厂接口,然后针对不同的产品去创建不同的工厂来创造。
2. 结构
和简单工厂不同的一点就在于抽象工厂是把工厂细化成不同种类的工厂,在不同的工厂中实现不同类型的咖啡的创建
- 抽象工厂(Abstract Factory):提供了创建产品的接口,调用者通过它访问具体工厂的工厂方法来创建产品。
- 具体工厂(ConcreteFactory):主要是实现抽象工厂中的抽象方法,完成具体产品的创建。
- 抽象产品(Product):定义了产品的规范,描述了产品的主要特性和功能。
- 具体产品(ConcreteProduct):实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂之间一一对应。
3. 代码
1、抽象工厂
public abstract class CoffeeFactory {
//创建咖啡对象的方法
abstract Coffee createCoffee();
}
2、具体工厂 - 拿铁咖啡工厂
public class LatteeCoffeeFactory extends CoffeeFactory {
@Override
Coffee createCoffee() {
return new LatteeCoffee();
}
}
3、具体工厂 - 美式咖啡工厂
public class AmercianCoffeeFactory extends CoffeeFactory{
@Override
Coffee createCoffee() {
return new AmercianCoffee();
}
}
4、抽象产品 - 咖啡抽象类
public abstract class Coffee {
public abstract String getName();
//加糖
public void addSugger(){
System.out.println("加糖");
}
//加奶
public void addMilk(){
System.out.println("加奶");
}
}
5、具体产品 - 美式咖啡
public class AmercianCoffee extends Coffee {
@Override
public String getName() {
return "美式咖啡";
}
}
6、具体产品 - 拿铁咖啡
public class LatteeCoffee extends Coffee {
@Override
public String getName() {
return "拿铁咖啡";
}
}
7、咖啡店,需要传入对应的咖啡工厂
public class CafferStore {
private CoffeeFactory factory;
public void setFactory(CoffeeFactory coffeeFactory){
this.factory = coffeeFactory;
}
//点咖啡功能
public Coffee orderCoffer(){
//生产咖啡
Coffee coffee = factory.createCoffee();
//加料
coffee.addMilk();
coffee.addSugger();
return coffee;
}
}
8、客户端类
public class Client {
public static void main(String[] args) {
//创建咖啡店对象
CafferStore store = new CafferStore();
//创建工厂对象
CoffeeFactory coffeeFactory = new AmercianCoffeeFactory();
store.setFactory(coffeeFactory);
Coffee coffee = store.orderCoffer();
System.out.println(coffee.getName());
}
}
9、输出结果
4. 优缺点
优点:
- 用户只需要具体的工厂就可以获取具体的产品,不需要直到产品的具体创建过程
- 如果需要添加一个新的产品,那么只需要新产品对应的工厂就可以了,而不需要再修改工厂类
- 典型的解耦框架。高层模块只需要知道产品的抽象类,无须关心其他实现类,满足迪米特法则、依赖倒置原则和里氏替换原则。
缺点:
- 每增加一个产品就要增加一个具体产品类和一个对应的具体工厂类,这增加了系统的复杂度。
- 当产品的数量过多的时候,使用这种方法会导致工厂类变多,程序变得更难理解
- 抽象产品工厂只能生产一种产品,但是实际上在现实生活中工厂都是会生产不同种类的产品的,这种可以使用抽象工厂解决
5. 抽象工厂模式
1. 产品族和产品等级
在前面的工厂方法中,只考虑了一种产品的创建,比如电视机工厂只生产电视机,手机工厂只生产手机,每一种产品都有对应的工厂来生产。我们把这一类的相同性质的产品叫做同等级的产品,比如小米手机和华为手机是一个等级的。
但是显然工厂一般都不会只生产一种产品,比如小米工厂不单单生产小米手机,还有电脑,电器等,而华为工厂也不单单生产华为手机,还生产华为的其他产品。否则一个工厂只生产一种产品,这也的使用率太低了,工厂数量容易发生爆炸。我们把同一个工厂生产的同一种品牌的产品叫做同一个产品族,比如小米手机和小米电脑叫做一个产品族。下面放几张图片来感受其中的不同:
2. 概念
抽象工厂方法:是一种为访问类提供一个创建一组相关或相互依赖对象的接口,且访问类无须指定所要产品的具体类就能得到同族的不同等级的产品的模式结构。
抽象工厂是工厂方法的升级版,工厂方法只能生产一个等级的产品,而抽象工厂方法可以生产多个等级的产品,比如在一个抽象工厂中可以定义生产手机的和生产电脑的方法。
3. 结构
- 抽象工厂(Abstract Factory):提供了创建产品的接口,它包含多个创建产品的方法,可以创建多个不同等级的产品。
- 具体工厂(Concrete Factory):主要是实现抽象工厂中的多个抽象方法,完成具体产品的创建。
- 抽象产品(Product):定义了产品的规范,描述了产品的主要特性和功能,抽象工厂模式有多个抽象产品。
- 具体产品(ConcreteProduct):实现了抽象产品角色所定义的接口,由具体工厂来创建,它 同具体工厂之间是多对一的关系。
4. 代码
现咖啡店业务发生改变,不仅要生产咖啡还要生产甜点,如提拉米苏、抹茶慕斯等,如果是使用工厂方法,那么每个新增的甜点都要创建一个工厂,这样很容易发生类爆炸,现在我们假设 拿铁咖啡、美式咖啡是一个产品等级,都是咖啡;拿铁咖啡和提拉米苏是同一产品族(也就是都属于意大利风味),美式咖啡和抹茶慕斯是同一产品族(也就是都属于美式风味)
这里我们只需要定义一个抽象工厂类,这个抽象工厂类里面实现了生产甜品和生产咖啡的接口,然后再定义两个工厂:一个意大利风味的工厂,一个美式风味的工厂。那么此时我们只需要创建两个工厂就可以完成这两种不同种类的产品的创建,而不需要每个产品都创建一个工厂。
1、抽象工厂:定义生产甜品和咖啡的方法
public abstract class DessertFactory {
//生产咖啡的功能
abstract Coffee createCoffee();
//生产甜品的功能
abstract Dessert createDessert();
}
2、具体工厂 - 意大利风味工厂,生产一个产品族
public class ItalyDessertFactory extends DessertFactory{
//生产拿铁咖啡
@Override
Coffee createCoffee() {
return new LatteeCoffee();
}
//生产提拉米苏
@Override
Dessert createDessert() {
return new Tiramisu();
}
}
3、具体工厂 - 美式风味工厂,生产一个产品族
public class AmericanDessertFactory extends DessertFactory{
//生产美式咖啡
@Override
Coffee createCoffee() {
return new AmercianCoffee();
}
//生产抹茶慕斯
@Override
Dessert createDessert() {
return new MatchaMousse();
}
}
4、抽象产品 - 咖啡类
//咖啡抽象类
public abstract class Coffee {
public abstract String getName();
//加糖
public void addSugger(){
System.out.println("加糖");
}
//加奶
public void addMilk(){
System.out.println("加奶");
}
}
5、具体产品 - 美式咖啡
public class AmercianCoffee extends Coffee {
@Override
public String getName() {
return "美式咖啡";
}
}
6、具体产品 - 拿铁咖啡
public class LatteeCoffee extends Coffee {
@Override
public String getName() {
return "拿铁咖啡";
}
}
7、抽象产品 - 甜品类
public abstract class Dessert {
abstract public void show();
}
8、具体产品 - 抹茶慕斯
public class MatchaMousse extends Dessert{
@Override
public void show() {
System.out.println("抹茶慕斯");
}
}
9、具体产品 - 提拉米苏
public class Tiramisu extends Dessert {
@Override
public void show() {
System.out.println("提拉米苏类");
}
}
10、用户类,可以从一个工厂获取咖啡和甜品,也就是一个产品族
public class Client {
public static void main(String[] args) {
//创建意大利风味的甜品工厂对象
ItalyDessertFactory italyDessertFactory = new ItalyDessertFactory();
//获取拿铁咖啡和提拉米苏
Dessert dessert = italyDessertFactory.createDessert();
Coffee coffee = italyDessertFactory.createCoffee();
System.out.println(coffee.getName());
dessert.show();
//拿铁咖啡
//提拉米苏类
}
}
11、输出结果
5. 优点和缺点
优点:
- 当一个产品族中的多个对象被设计成一起工作时,它能保证客户端始终只使用同一个产品族中的对象。
- 符合开闭原则,比如这时候又要添加一个中式口味的甜品和咖啡,那么只需要添加一个具体实现工厂实现抽象工厂的接口就可以了。
- 可以在类的内部对产品族中相关联的多等级产品共同管理,而不必专门引入多个新的类来进行管理。
缺点:
- 当产品族中需要增加一个新的产品时,所有的工厂类都需要进行修改。比如这时候需要添加奶茶产品,那么这时候抽象工厂和具体的实现工厂都需要修改才行
6. 使用场景
- 当需要创建的对象是一系列相关联的产品族的时候,比如电视、手机、电脑就是一个产品族。
- 系统中有多个产品族,但每次只使用其中的某一族产品。如有人只喜欢穿某一个品牌的衣服和鞋。
- 系统中提供了产品的类库,且所有产品的接口相同,客户端不依赖产品实例的创建细节和内部结构。
6. 工厂模式在源码中的使用
1. Calendar.getInstance
使用简单工厂的模式,内部使用了 switch - case 的方式:
2. DateForamt.getInstance
在下面的方法中就根据不同的时间类型来创建不同的对象
3. Collection.iterator
public class JDKSource {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("aaa");
list.add("bbb");
list.add("ccc");
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
//aaa
//bbb
//ccc
System.out.println(iterator.next());
}
}
}
到源码中去看,ArrayList 中实现了 Collection 接口,而 Collection 中定义了返回 Iterator 接口的 iterator 方法,ArrayList中也重写了这个方法,下面就是类图:
Collection 接口是抽象接口,ArrayList 是具体的工厂类,这个工厂是用来返回 Iterator 接口的实现子类的,Iterator 就是一个抽象产品类,而 ArrayList中的 Iterator 是具体的产品类。
如有错误,欢迎指出!!!