zoukankan      html  css  js  c++  java
  • 工厂方法模式——创建型模式02

    1. 简单工厂模式

        在介绍工厂方法模式之前,先介绍一下简单工厂模式。虽然简单工厂模式不属于GoF 23种设计模式,但通常将它作为学习其他工厂模式的入门,并且在实际开发中使用的也较为频繁。

    (1) 定义

    简单工厂模式(Simple Factory Pattern):定义一个工厂类,他可以根据参数的不同返回不同类的实例,被创建的实例一般具有共同的父类。因为在简单工厂模式中,用于创建实例的方法是静态(static)方法,因此简单工厂模式又被称为静态工厂方法(Static Factory Method),它属于类创建型模式。

    (2) 问题

        在生活中,当我们需要某类产品时,我们的做法是去生产它的工厂(或销售它的商店)去购买它。比如我们需要一只笔来写字,我们需要到文具店去购买。在购买时,我们只需要告诉服务员我们需要一只钢笔或者毛笔,然后我们便可以付钱得到。至于笔是如何生产的,我们根本不关心(当然质量还是要考虑的)。同样在编程时,我们是否能事先设计出一些工厂类,当我们需求某个类时,我们找到相应的工厂类,调用它的生成产品方法,同时传进我们需要产品的名称,该方法就可以返回出我们需要的对象。

        例如,我们需要设计一套图表库供人调用。目前该图表库只包含折线图,饼状图,柱状图,以后再对其进行扩增。设计需要达到的效果是调用者只需要调用某个类的某一方法,并给出所需的图标的类型名称,该方法会自动创建该图表类,并返回给调用者。

    (3) 解决方案

        我们首先对所有的图表类抽象出一个公共的接口或抽象类,然后所有的具体的图表类需要实现该接口,最终只需要面向这个接口编程即可。

    ① 抽象产品类

    public abstract class Char {
    
        protected float[] data;
        
        public Char(float[] data) {
            this.data = data;
            System.out.println("初始化一些公共的操作...");
        }
        
        public void setData(float[] data) {
            this.data = data;
        }
    
        public abstract void display();
    }

    ② 实现具体产品类

    public class LineChar extends Char {
    
        public LineChar() {
            this(null);
        }
    
        public LineChar(float[] data) {
            super(data);
            System.out.println("初始化LineChar");
        }
    
        @Override
        public void display() {
            System.err.println("将Data以LineChar形式显示");
        }
    
    }

    其他的具体类也以同样的形式创建,这里省略。

    ③ 构建工厂类

    public class CharFactory {
    
        public static final int LINE_CHAR = 0;
        public static final int HISTOGRAM_CHAR = 1;
        public static final int PIE_CHAR = 2;
    
        public static Char getChar(int type) {
            switch (type) {
            case HISTOGRAM_CHAR:
                return new HistogramChar();
            case PIE_CHAR:
                return new PieChar();
            case LINE_CHAR:
            default:
                return new LineChar();
            }
        }
    }

    ④ 测试

    public static void main(String[] args) {
        Char c = CharFactory.getChar(CharFactory.LINE_CHAR);
        c.setData(new float[] { 1, 2, 3, 4 });
        c.display();
    }

    打印结果:

    初始化一些公共的操作...
    初始化LineChar
    将Data以LineChar形式显示

    2. 工厂方法模式

    (1) 定义

    工厂方法模式(Factory Method Pattern):定义一个用于创建对象的接口,让子类决定将哪一个类实例化。工厂方法模式让一个类的实例化延迟到其子类。工厂方法模式又称为工厂模式(Factory Pattern)虚拟构造器模式(Virtual Constructor Pattern)又或多态工厂模式(Polymorphic Factory Pattern)。工厂方法模式是一种类创建型模式。

    (2) 问题

        简单工厂模式虽然简单,但是存在一个严重的问题:当我们引入新的产品时,需要对工厂类的源码进行修改。例如上例中,如果我们又实现了一些图表类,需要将他们纳入工厂的生产范围,我们需要再增加几个case代码块来进行判断。一来,随着产品类的增加,工厂类的代码也会等比例增加,显得十分累赘;二来这种需要修改原先设计的做法违背了开闭原则。

        有没有一种方案能够解决这个问题呢?

        以日志器为例子。我们在开发或生产环境下经常需要打印一些日志,因此需要一个日志器来打印日志。根据日志输出的位置的不同,日志器又有很多种。目前实现了输出日志到文件和输出日志到数据库这两种日志器,以后再做扩展,现同样需要设计一个工厂类,但要解决掉简单工厂模式的缺陷问题。

    (3) 解决方案

        ①  抽象产品类和工厂类

    public interface Logger {
        void log();
    }
    public interface LoggerFactory {
        Logger createLogger();
    }

        ② 实现产品类和工厂类

    public class DatabaseLogger implements Logger {
    
        @Override
        public void log() {
            System.out.println("正在打印数据库日志...");
        }
    
    }
    public class DatabaseLoggerFactory implements LoggerFactory {
    
        @Override
        public Logger createLogger() {
            return new DatabaseLogger();
        }
    }

    这里均只给出Database的实现。

        ③ 测试

    @Test
    public void test1() {
        LoggerFactory factory = new DatabaseLoggerFactory();
        Logger logger = factory.createLogger();
        logger.log();
    }

    输出结果:

    正在打印数据库日志...

        ④ 改进

        我们可以引入配置文件来“动态”改变LoggerFactory的实例。

    编写一个读取xml配置文件的工具类:

    public class XMLUtil {
    
        public static Object getBeanFromXml(String beanName) {
            if (beanName == null || beanName.isEmpty()) {
                return null;
            }
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
            try {
                DocumentBuilder builder = factory.newDocumentBuilder();
                Document document = builder.parse(new File("config.xml"));
                NodeList beansList = document.getElementsByTagName("beans");
                if (beansList.getLength() == 0) {
                    return null;
                }
                NodeList beanList = beansList.item(0).getChildNodes();
                for (int i = 0; i < beanList.getLength(); i++) {
                    if (beanList.item(i).getNodeType() == Node.ELEMENT_NODE) {
                        if (beanName.equals(beanList.item(i).getNodeName())) {
                            return Class.forName(beanList.item(i).getTextContent()).newInstance();
                        }
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            return null;
        }
    }

    配置文件config.xml这样写

    <?xml version="1.0" encoding="UTF-8"?>
    
    <beans>
        <loggerFactory>factory.factory.FileLoggerFactory</loggerFactory>
    </beans>

    这样调用:

    @Test
    public void test2() {
        LoggerFactory factory = (LoggerFactory) XMLUtil.getBeanFromXml("loggerFactory");
        if (factory != null) {
            factory.createLogger().log();
        }
    }

    输出结果同上。如果我们需要切换日志工厂,只需修改配置文件中loggerFactory结点的值即可。

  • 相关阅读:
    将Nginx添加到windows服务中
    springboot使用redis管理session
    GIT常用命令
    阻止360、谷歌浏览器表单自动填充
    谈谈对Spring IOC的理解
    同一个Nginx服务器同一端口配置多个代理服务
    LeetCode 653. Two Sum IV
    109. Convert Sorted List to Binary Search Tree(根据有序链表构造平衡的二叉查找树)
    108. Convert Sorted Array to Binary Search Tree(从有序数组中构造平衡的BST)
    LeetCode 236. Lowest Common Ancestor of a Binary Tree(二叉树求两点LCA)
  • 原文地址:https://www.cnblogs.com/dongkuo/p/4939628.html
Copyright © 2011-2022 走看看