zoukankan      html  css  js  c++  java
  • Java设计模式-工厂方法模式

    工厂方法模式概述

    在简单工厂模式中只提供一个工厂类,该工厂类处于对产品类进行实例化的中心位置,它需要知道每一个产品对象的创建细节,并决定何时实例化哪一个产品类。简单工厂模式最大的缺点是当有新产品要加入到系统中时,必须修改工厂类,需要在其中加入必要的业务逻辑,这违背了“开闭原则”。此外,在简单工厂模式中,所有的产品都由同一个工厂创建,工厂类职责较重,业务逻辑较为复杂,具体产品与工厂类之间的耦合度高,严重影响了系统的灵活性和扩展性,而工厂方法模式则可以很好地解决这一问题。

    工厂方法模式定义

    在工厂方法模式中,我们不再提供一个统一的工厂类来创建所有的产品对象,而是针对不同的产品提供不同的工厂,系统提供一个与产品等级结构对应的工厂等级结构。工厂方法模式定义如下:

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

    工厂方法模式结构

    1、Product(抽象产品):它是定义产品的接口,是工厂方法模式所创建出来的对象的超类型,也就是产品对象的公共父类。

    2、ConcreteProduct(具体产品):它实现了抽象产品接口,某种类型的具体产品由专门的具体工厂创建,具体工厂和具体产品一一对应。

    3、Factory(抽象工厂):在抽象工厂中,声明了工厂方法,用于返回一个产品。抽象工厂是工厂方法模式的核心,所有创建对象的工厂类都需要实现这个接口。

    4、ConcreteFactory(具体工厂):它是抽象工厂类的子类,实现抽象工厂类中的定义的工厂方法,并由客户端调用,返回一个具体的产品实例。

    工厂方法模式代码

    1、声明一个抽象产品类:

    /**
     * Author: YiFan
     * Date: 2018/12/10 21:09
     * Description: 抽象产品
     */
    public abstract class Car {
        
        // 抽象方法-汽车名称描述
        public abstract void desc();
    }
    

    2、声明具体产品类:

    /**
     * Author: YiFan
     * Date: 2018/12/10 21:11
     * Description: 具体产品类
     */
    public class BMWCar extends Car {
    
        @Override
        public void desc() {
            System.out.println("宝马汽车");
        }
    }
    
    /**
     * Author: YiFan
     * Date: 2018/12/10 21:11
     * Description: 具体产品类
     */
    public class AuDiCar extends Car {
    
        @Override
        public void desc() {
            System.out.println("奥迪汽车");
        }
    }
    

    3、声明抽象工厂类:

    /**
     * Author: YiFan
     * Date: 2018/12/10 21:13
     * Description: 抽象工厂
     */
    public abstract class CarFacotry {
    
        // 抽象工厂方法-返回产品
        public abstract Car produceMethod();
    }
    

    5、声明具体工厂类:

    /**
     * Author: YiFan
     * Date: 2018/12/10 21:15
     * Description: 具体工厂类
     */
    public class BMWCarFactory extends CarFacotry {
    
        @Override
        public Car produceCar() {
            return new BMWCar();
        }
    }
    
    /**
     * Author: YiFan
     * Date: 2018/12/10 21:17
     * Description: 具体工厂类
     */
    public class AuDiCarFactory extends CarFacotry {
    
        @Override
        public Car produceCar() {
            return new AuDiCar();
        }
    }
    

    6、客户端代码:

    /**
     * Author: YiFan
     * Date: 2018/12/10 21:18
     * Description: 客户端代码-测试类
     */
    public class CarTest {
    
        public static void main(String[] args) {
            // 创建一个具体工厂实例
            CarFactory bmwCarFactory = new BMWCarFactory();
            // 调用具体工厂的produceCar()方法创建具体产品
            car.desc();
            CarFactory auDiCarFactory = new AuDiCarFactory();
            Car car1 = auDiCarFactory.produceCar();
            car1.desc();
        }
    }
    

    执行结果为:

    宝马汽车
    奥迪汽车
    

    在客户端代码中只关心工厂类即可,首先创建一个具体的工厂类实例,为了使用它创建具体的产品,然后调用其工厂方法创建具体的产品。

    工厂方法模式总结

    工厂方法模式是简单工程模式的延伸,它继承了简单工厂模式的优点,同时还弥补了简单工厂模式的不足。工厂方法模式是使用频率最高的设计模式之一。

    主要优点

    1、工厂方法模式中,工厂方法用来创建客户所需要的产品,同时向客户隐藏了哪种具体产品类将被实例化这一细节,用户只需关心所需产品对应的工厂(上述代码示例中汽车生产工厂,实际上我们不需要知道宝马汽车工厂,也就是我们不需要自己创建宝马工厂的对象,只需要在汽车店中调用预定汽车的方法即可),无需关心细节,甚至无需知道具体产品类的类名。

    2、基于工厂角色和产品角色的多态性设计是工厂方法模式的关键。它能够让工厂可以自主确定创建何种产品对象,而如何创建这个对象的细节则完全封装在具体工厂内部。工厂方法模式之所以又被称为多态工厂模式,正是因为所有的具体工厂类都具有同一个抽象父类工厂。

    3、在系统添加新产品时,无需修改抽象工厂和抽象产品提供的接口,无需修改客户端,也无需修改其它的具体工厂和具体产品,只需要添加一个具体产品和具体工厂即可。这样系统的扩展性变的非常好,完全符合“开闭原则”;

    主要缺点

    1、在添加新产品时,需要编写新的具体产品类,而且还要提供与之对应的具体工厂类,系统中类的个数将成对增加,在一定程度上增加了系统的复杂度,有更多的类需要编译和运行,会给系统带来一些额外的开销。

    2、由于考虑到系统的可扩展性,需要引入抽象层,在客户端代码中均使用抽象层进行定义,增加了系统的抽象性和理解难度,且在实现时可能需要用到DOM、反射等技术,增加了系统的实现难度。

    适用场景

    1、客户端不知道它所需要的对象的类。在工厂方法模式中,客户端不需要知道具体产品类的类名,只需要知道所对应的工厂即可,具体的产品对象由具体工厂类创建,可将具体工厂类的类名存储在配置文件或数据库中。

    2、 抽象工厂类通过其子类来指定创建哪个对象。在工厂方法模式中,对于抽象工厂类只需要提供一个创建产品的接口,而由其子类来确定具体要创建的对象,利用面向对象的多态性和里氏代换原则,在程序运行时,子类对象将覆盖父类对象,从而使得系统更容易扩展。

    练习

    使用工厂方法模式设计一个程序来读取各种不同类型的图片格式,针对每一种图片格式都设计一个图片读取器,如 GIF 图片读取器用于读取 GIF 格式的图片、JPG 图片读取器用于读取 JPG 格式的图片。需充分考虑系统的灵活性和可扩展性。

    练习代码

    代码结构如下:

    ImgReader-抽象产品类

    GifReader-具体产品类 JpgReader-具体产品类

    ImgReaderFactory-抽象工厂类

    GifReaderFactory-抽象工厂类 JpgReaderFactory-抽象工厂类

    Test-客户端代码

    具体代码如下:

    /**
     * Author: YiFan
     * Date: 2018/12/11 09:14
     * Description: 抽象产品类
     */
    public interface ImgReader {
    
        void desc();
    }
    
    /**
     * Author: YiFan
     * Date: 2018/12/11 09:15
     * Description: 具体产品类-Gif阅读器
     */
    public class GifReader implements ImgReader {
    
        @Override
        public void desc() {
            System.out.println("读取GIF格式图片");
        }
    }
    
    /**
     * Author: YiFan
     * Date: 2018/12/11 09:16
     * Description: 具体产品类-Jpg阅读器
     */
    public class JpgReader implements ImgReader {
    
        @Override
        public void desc() {
            System.out.println("读取JPG格式图片");
        }
    }
    
    /**
     * Author: YiFan
     * Date: 2018/12/11 09:17
     * Description: 抽象工厂类
     */
    public interface ImgReaderFactory {
    
        ImgReader createImgReader();
    }
    
    /**
     * Author: YiFan
     * Date: 2018/12/11 09:18
     * Description: 具体工厂类-Gif阅读器工厂
     */
    public class GifReaderFactory implements ImgReaderFactory {
    
        @Override
        public ImgReader createImgReader() {
            return new GifReader();
        }
    }
    
    /**
     * Author: YiFan
     * Date: 2018/12/11 09:20
     * Description: 具体工厂类-Jpg阅读器工厂
     */
    public class JpgReaderFactory implements ImgReaderFactory {
    
        @Override
        public ImgReader createImgReader() {
            return new JpgReader();
        }
    }
    
    /**
     * Author: YiFan
     * Date: 2018/12/11 09:31
     * Description: 客户端
     */
    public class Test {
    
        public static void main(String[] args) {
            ImgReader reader;
            ImgReaderFactory factory;
            // (1)创建GIF阅读器工厂-创建GIF阅读器
            factory = new GifReaderFactory();
            reader = factory.createImgReader();
            reader.desc();
        }
    }
    

    代码(1)中在客户端中需要创建具体的阅读器工厂,所以如果换一个阅读器工厂则需要对客户端代码进行改动,那么如何做到不改动客户端代码呢?可以利用 Java 的反射与配置文件进行扩展。

    添加一个 XMLUtil 类,负责读取 XMl 配置文件:

    /**
     * Author: YiFan
     * Date: 2018/12/11 09:23
     * Description: 文件读取类
     */
    public class XMLUtil {
    
        public static Object getBean(String configFileName, String packageName) {
            try {
                //创建DOM文档对象
                DocumentBuilderFactory dFactory = DocumentBuilderFactory.newInstance();
                DocumentBuilder dBuilder = dFactory.newDocumentBuilder();
                Document doc;
                doc = dBuilder.parse(new File(configFileName));
    
                //获取包含类名的文本节点
                NodeList nl = doc.getElementsByTagName("imageType");
                Node node = nl.item(0).getFirstChild();
                String cName = node.getNodeValue().trim();
                
                //通过类名生成实例对象并将其返回
                Class c = Class.forName(packageName + "." + cName);//生成的是工厂名
                Object object = c.newInstance();
                return object;
            } catch (Exception e) {
                e.printStackTrace();
                return null;
            }
        }
    }
    
    /**
     * Author: YiFan
     * Date: 2018/12/11 09:31
     * Description: 客户端
     */
    public class Test {
    
        // 获取当前类所在的包名
        private static String packageName = Test.class.getPackage().getName();
    
        public static void main(String[] args) {
            ImgReader reader;
            ImgReaderFactory factory;
    
            // 强制转换,将Object对象转换成工厂对象
            factory = (ImgReaderFactory) XMLUtil.getBean("config.xml", packageName);
            reader = factory.createImgReader();
            reader.desc();
        }
    }
    

    配置文件放置项目根目录下,配置文件如下:

    <?xml version="1.0"?>
    <config>
        <imageType>GifReaderFactory</imageType>
    </config>
    

    直接结果为:

    读取GIF格式图片
    

    这样一来以后如果需要使用其它的阅读器工厂类创建阅读器时,就不需要在客户端代码中进行修改,只需要修改 config.xml 配置文件即可。

  • 相关阅读:
    Installing — pylibmc 1.2.3 documentation
    Python-memcached的基本使用
    kindeditor在sae上传文件修改,适合php
    Kindeditor+web.py+SAE Storage 实现文件上传
    使用sae定时执行Python脚本
    Ueditor文本编辑器(新浪SAE平台版本)
    NicEdit
    Lind.DDD.LindMQ的一些想法
    Redis学习笔记~关于空间换时间的查询案例
    微信扫码支付~官方DEMO的坑~参数不能自定义
  • 原文地址:https://www.cnblogs.com/shanyingwufeng/p/10184084.html
Copyright © 2011-2022 走看看