Java设计模式-内功修炼-创建型设计模式-工厂模式

创建型设计模式

知识补充

java反射机制

此处仅作最简单的应用型描述,因为下面会用到,所以在此补充,读者如果想深入了解java反射机制,请自行查资料。

举个简单例子:

Class c = Class.forName("String");
Object obj = c.newInstance();
return obj;

上面的代码能够返回一个String 对象(与new String()是一样的),比较神奇的是,我的String是出现在字符串中的。

所以可以做最简单的理解,java反射机制的一个最简单的应用就是从字符串到一个或者对象的转变,即给你一个"String",你能通过反射机制获取String对象。

索引

  • 简单工厂模式
  • 工厂方法模式
  • 抽象工厂模式
  • 单例模式
  • 原型模式
  • 建造者模式

1. 简单工厂模式

简述

  • 简单工厂模式不是GOF提出的23种模式中的一个,简单工厂仅仅是一个小弟,他大哥叫抽象工厂模式。
  • 思路很简单,一张图就能搞明白:在这里插入图片描述
  • 示例代码:
    package structure.SampleFactory;
    
    public class ConcreteProductB extends Product {
        @Override
        void display() {
            System.out.println("ConcreteProductB");
        }
    }
    
    ============分割线============
    package structure.SampleFactory;
    
    public class ConcreteProductA extends Product {
        @Override
        void display() {
            System.out.println("ConcreteProductA");
        }
    }
    
    ==============分割线=============
    package structure.SampleFactory;
    
    import org.junit.Test;
    
    public class Factory {
        static Product factoryMethod(String name) {
            Product product;
            if (name.equals("A")) {
                product = new ConcreteProductA();
            } else if (name.equals("B")) {
                product = new ConcreteProductB();
            } else {
                product = null;
            }
            return product;
        }
    
        @Test
        public void testFactory() {
            Product product = factoryMethod("A");
            product.display();
    
            Product product1 = factoryMethod("B");
            product1.display();
        }
    }
    

理解

  • 上图一共四个类,抽象产品类,具体产品A类,具体产品B类,工厂类,前三个类的作用不用多说了,提取出一个抽象类有利于开闭原则以及代码复用。工厂类的作用:看上图左边的代码,根据传的参数来判断返回哪一个具体产品类。
  • 上图仅仅是一个基本原理图,聪明的读者一定发现,这种代码还是不符合开闭原则。关于开闭原则,参考笔者的上一篇博客

个人体会:

  • 这里也有针对接口编程的体现,Factory类中的factoryMethod(String arg):Product表示返回类型是Product即抽象类,根据参数判断返回哪个具体类。
  • 上述方法的缺点:仅仅将复杂的逻辑移动到了工厂类而已,并不能削减复杂度,而且如果新增产品的话,还是需要修改代码,导致扩展困难。
    适用范围:
  • 适用于创建的对象比较少,而且比较固定,不会产生扩展的情况。

2. 工厂方法模式

简述

上面说到,简单工厂方法对扩展很不友好,需要修改代码,不符合开闭原则,所以引入了工厂方法模式。
在这里插入图片描述
示例代码,只贴出了核心调用代码,其余的没有贴,读者可以自行完善:

package structure.Factory;

import org.junit.Test;

public class ConcreteFactoryA extends Factory {
    @Override
    Product factoryMethod() {
        Product product = new ConcreteProduct();
        /*
         * 这里存在大量用于初始化产品的代码
         * */

        return product;
    }

    @Test
    public void testConcreteFactory() {
        Factory factory = new ConcreteFactoryA();
        Product product = factory.factoryMethod();
        product.display();
    }
}


理解

上图仅仅是最基本的原理图,读者可能还会有一些疑惑:当需要扩充新产品ConcreteProductB的时候怎么办?答案是:新建一个与之对应的ConcreteFactoryB,那么问题又来了:

每个产品都对应一个工厂,而且工厂里的factoryMethod()方法就执行了一行new语句,那要工厂有啥用呢?

  • 事情并不是这样,其实引入工厂的初衷是因为类或者对象需要大量初始化代码,我们将默认初始化代码全部放入factoryMethod中,这样对于客户端代码来说就友好了很多,也就是说 工厂方法创建的对象是已经做过一些初始化操作的。

这种方法能符合开闭原则的 对修改关闭对扩展开放吗?
能:当需要新增ConcreteProductB的时候,新建ConcreteProductB和ConcreteFactoryB,这就是对扩展开放。那么客户端在创建产品的时候还是要修改一些语句,比如需要将 ConcreteFactory.factoryMethod() 修改成 ConcreteFactoryB.factoryMethod() 那岂不是还是要修改代码,这个问题我们在下文解决。

使用反射解决上文的问题
关于反射的简单使用,看上文的知识补充。其实解决方案在上两篇文章中提到过:依赖注入。将类名写在xml文件中,然后读取xml文件,利用反射获取相应对象,下面给一个示例代码:

package util;

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 {
    //读取配置文件,并返回类型名
    private static String getClassType(String nodeName) {
        try {
            //创建文档对象
            DocumentBuilderFactory dFactory = DocumentBuilderFactory.newInstance();
            DocumentBuilder builder = dFactory.newDocumentBuilder();
            Document doc;
            doc = builder.parse(new File("config/config.xml"));
            //获取包含图表类型的文本结点
            NodeList nl = doc.getElementsByTagName(nodeName);
            Node classNode = nl.item(0).getFirstChild();
            //            System.out.println("getClassType name is:" + name);
            return classNode.getNodeValue();
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    public static Object getBean(String nodeName) {
        try {
            Class c = Class.forName("structure.Factory." + getClassType(nodeName));
            return c.newInstance();
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }


    }
}
==================分割线==================
<?xml version="1.0"?>
<config>
    <chartType>NewChart</chartType>
    <ConcreteFactory>ConcreteFactoryA</ConcreteFactory>
</config>
==================分割线=================
    @Test
    public void testConcreteFactory2() {
        Factory factory = (Factory) XMLUtil.getBean("ConcreteFactory");
        Product product = factory.factoryMethod();
        product.display();
    }

再次引入一个问题:
客户需要修改配置文件岂不是很麻烦,其实解决方案很简单,将修改配置文件部分做成图形界面就ok了。

总结

优点:
  • 用户无需关心创建产品的细节
  • 在系统中加入新产品的时候,无需修改代码,只需去修改配置文件,然后加入具体产品类和其对应的工厂方法。修改配置文件而非代码的一个优点就是不需要重新编译,在运行时确定要使用哪个对象。
缺点也很明显:
  • 增加了类的个数*,每次引入新产品都需要加具体产品类和对应的工厂方法。
  • 引入抽象层,增加了理解难度,而且还用到了反射,增加了系统实现难度。
适用场景:
  • 客户端不知道它所需要的对象的类,只需要知道对应的工厂即可。
  • 在程序运行时确定使用哪个子类,然后进行替换,覆盖,使系统更容易扩展。
  • 如果产品比较复杂, 初始化代码很多,那么加一层工厂是很好的。如果产品比较简单,那么可以省略工厂。

3. 抽象工厂模式

简述

抽象工厂模式又称为Kit模式,可以理解成是一种打包式的工厂模式,即工厂模式如果子工厂类无限扩充怎么办?那把他们打包一下,再增加一个抽象层,也就是抽象工厂。
在这里插入图片描述

理解

如果能够理解工厂方法模式的话,再理解抽象工厂方法模式就不难了,他们本质上是相同的。抽象工厂针对的是一个产品家族,而工厂方法针对的是一个产品,可以理解成一个抽象工厂包含多个工厂,这样抽象工厂生产的就是一堆产品,即产品族。

总结

优点

隔离了具体类的生成,生成了产品族,而且增加新的产品族很方便

缺点

增加新的产品结构很难:抽象工厂A内有A1, A2, A3工厂,抽象工厂B内有B1, B2, B3,我新增一个抽象工厂C内涵C1, C2, C3很简单。如果要将A中的A1换成B1呢?这就需要修改代码了。

适用场景
  • 系统中有产品需要组合成一个产品族。
  • 产品族中的产品是有约束的,即这些产品是一起生效的,比如一个皮肤中的背景+按钮样式他们是一起生效的,属于同一款主题中的不同控件。
  • 产品结构稳定,不会频繁更新产品结构。

下一节传送门 单例模式

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值