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结点的值即可。

  • 相关阅读:
    c语言提高学习笔记——02-c提高07day
    c语言提高学习笔记——02-c提高06day
    c语言提高学习笔记——02-c提高05day
    c语言提高学习笔记——02-c提高04day
    c语言提高学习笔记——02-c提高03day
    菜鸡的 分块 刷题记录
    是输入输出的小技巧和细节们
    蒟蒻的 线性基 刷题记录
    曼哈顿距离,欧几里得距离学习笔记
    用 Github.io 和 Hexo 创建你的第一个博客
  • 原文地址:https://www.cnblogs.com/dongkuo/p/4939628.html
Copyright © 2011-2022 走看看