zoukankan      html  css  js  c++  java
  • 设计模式之工厂模式

    工厂模式的历史由来

    在现实生活中我们都知道,原始社会自给自足(没有工厂)、农耕社会小作坊(简单工 厂,民间酒坊)、工业革命流水线(工厂方法,自产自销)、现代产业链代工厂(抽象工厂, 富士康)。 我们的项目代码同样也是由简而繁一步一步迭代而来,但对于调用者来说确是越来越简单化。

    工厂模式是什么?

    工厂顾名思义就是创建产品,根据产品是具体产品还是具体工厂可分为简单工厂模式和工厂方法模式,根据工厂的抽象程度可分为工厂方法模式和抽象工厂模式。该模式用于封装和管理对象的创建,是一种创建型模式。

    使用工厂模式

    简单工厂模式

    在简单工厂模式中,可以根据参数的不同返回不同类的实例。简单工厂模式专门定义一个工厂类来负责创建其他类的实例,被创建的实例通常都具有共同的父类。

    我们可以先定义一个产品标准 Product 接口:

    public interface Product {
        /**
         * 产品价格
         * @return
         */
        String getPrice();
    
    }
    

    创建一个的篮球产品的实现类LanqiuProduct:

    public class LanqiuProduct implements Product {
        @Override
        public String getPrice() {
            return "99RMB";
        }
    }
    

    看客户端调用代码,我们会这样写:

    public static void main(String[] args) {
    	 Product lanqiu= new LanqiuProduct(); 
    	 Lanqiu.getPrice();
    }
    
    

    看上面的代码,父类 Product 指向子类 LanqiuProduct 的引用,应用层代码需要依赖 LanqiuProduct ,如果业务扩展,我继续增加 YagaoProduct 甚至更多,那么我们客户端 的依赖会变得越来越臃肿。

    因此,我们要想办法把这种依赖减弱,把创建细节隐藏。虽然目前的代码中,我们创建对象的过程并不复杂,但从代码设计角度来讲不易于扩展。 现在,我们用简单工厂模式对代码进行优化。先增加产品YagaoProduct 类:

    public class YagaoProduct  implements Product{
    
        @Override
        public String getPrice() {
            return "12RMB";
        }
    }
    
    

    创建产品工厂类 ProductFactory :

    public class ProductFactory {
    
        /**
         * 根据 name 创建产品
         * @param name
         * @return
         */
        Product create(String name){
            if("Yaogao".equals(name)){
                return new YagaoProduct();
            }else if("Lanqiu".equals(name)){
                return new LanqiuProduct();
            }else {
                return null;
            }
        }
    
    }
    
    

    客户端调用:

    public class Test {
        public static void main(String[] args) {
            ProductFactory factory = new ProductFactory();
            Product product = factory.create("Lanqiu");
            String price = product.getPrice();
            System.out.println(price);
        }
    }
    

    当然,为了调用方便,我们可以将 create(String name) 方法 修改为静态方法:

    public class ProductFactory {
    
        /**
         * 根据 name 创建产品
         * @param name
         * @return
         */
        public static Product create(String name){
            if("Yaogao".equals(name)){
                return new YagaoProduct();
            }else if("Lanqiu".equals(name)){
                return new LanqiuProduct();
            }else {
                return null;
            }
        }
    
    }
    

    客户端调用:

    public class Test {
        public static void main(String[] args) {
            Product product = ProductFactory.create("Lanqiu");
            String price = product.getPrice();
            System.out.println(price);
        }
    }
    
    

    客户端调用变得简单了,但是如果我们业务继续扩展,要增加更多新的产品,那样工厂中的create方法每次都要根据产品的增加修改逻辑代码,不符合开闭原则。因此,我们可以对简单工厂进行优化,采用反射技术:

       public class ProductFactory {
    
        /**
         * 根据 name 创建产品
         * @param className
         * @return
         */
        public static Product create(String className) {
    
            try {
                if (className != null && !"".equals(className)) {
                    Product product = (Product) Class.forName(className).newInstance();
                    return product;
                }
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InstantiationException e) {
                e.printStackTrace();
            }
            return null;
        }
    }
    

    客户端调用:

    public class Test {
        public static void main(String[] args) {
            Product product = ProductFactory.create("com.haan.factory.demo1_simplefactory.LanqiuProduct");
            System.out.println(product.getPrice());
    
        }
    }
    

    优化之后,产品不断增加,不需要修改ProductFactory,但还有一个问题,create 方法的参数是一个字符串,可控性较差,而且需要强类型转换,再修改一下代码:

    public class ProductFactory {
    
        /**
         * 根据 name 创建产品
         * @param className
         * @return
         */
        public static Product create(Class<? extends Product> clazz) {
            try {
                Product product = clazz.newInstance();
                return product;
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
            return null;
        }
    }
    

    客户端调用:

    public class Test {
        public static void main(String[] args) {
            Product product = ProductFactory.create(LanqiuProduct.class);
            System.out.println(product.getPrice());
    
        }
    }
    

    查看类图:

    简单工厂也有它的缺点:工厂类的职责相对过重,不易于扩展过于复杂的产品结构。所以我们引入工厂方法模式

    工厂方法模式

    工厂方法模式(Fatory Method Pattern)是指定义一个创建对象的接口,但让实现这 个接口的类来决定实例化哪个类,工厂方法让类的实例化推迟到子类中进行。在工厂 方法模式中用户只需要关心所需产品对应的工厂,无须关心创建细节,而且加入新的 产品符合开闭原则。

    工厂方法模式主要解决产品扩展的问题,在简单工厂中,随着产品链的丰富,如果每个产品的创建逻辑有区别的话,工厂的职责会变得越来越多,有点像万能工厂,并不便 维护。根据单一职责原则我们将职能继续拆分,专人干专事。牙膏工厂专门生产牙膏,篮球工厂专门生产篮球:

    首先创建工厂接口 IProductFactory:

    public interface IProductFactory {
        /**
         * 创建 Product
         * @return
         */
        Product create();
    }
    

    创建子工厂,YagaoProductFactory,LanqiuProductFactory:

    public class YagaoProductFactory implements IProductFactory{
        @Override
        public Product create() {
            return new YagaoProduct();
        }
    }
    
    public class LanqiuProductFactory implements IProductFactory{
        @Override
        public Product create() {
            return new LanqiuProduct();
        }
    }
    

    客户端测试调用:

    public class Test {
        public static void main(String[] args) {
            IProductFactory factory = new YagaoProductFactory();
            Product product = factory.create();
            System.out.println(product.getPrice());
        }
    }
    

    工厂方法适用于以下场景:

    1. 创建对象需要大量重复的代码。
    2. 客户端(应用层)不依赖于产品类实例如何被创建、实现等细节。
    3. 一个类通过其子类来指定创建哪个对象。

    工厂方法也有缺点:

    1. 类的个数容易过多,增加复杂度。
    2. 增加了系统的抽象性和理解难度。

    抽象工厂模式

    抽象工厂模式(Abastract Factory Pattern)是指提供一个创建一系列相关或相互依赖 对象的接口,无须指定他们具体的类。客户端(应用层)不依赖于产品类实例如何被创建、实现等细节,强调的是一系列相关的产品对象(属于同一产品族)一起使用创建对象需要大量重复的代码。需要提供一个产品类的库,所有的产品以同样的接口出现,从而使客户端不依赖于具体实现。

    以课程为例,咕泡学院第三期课程有了新的标准,每个课程不仅要提供课程的录播视频,还要提供老师的课堂笔记。相当于现在的业务变更为同一个课程不单纯包含一个课程信息,同时要包含录播视频、课堂笔记,甚至还要提供源码才能构成一个完整的课程。在产品等级中增加两个产品: IVideo 录播视频和INote课堂笔记。

    public interface IVideo {
    	void record();
    }
    

    INote接口如下:

    public interface INote {
    	void edit();
    }
    

    然后创建一个抽象 工厂类CourseFactory:

    /**
    *抽象工厂是用户的主入口
    *是Spring中应用得最广泛的一 种设计模式
    *易于扩展
    */
    public interface CourseFactory {
    	INote createNote();
    	IVideo createVideo();
    }
    

    接下来,创建Java产品族的Java视频类JavaVideo:

    public class JavaVideo implements IVideo {
    	public void record() {
    		System. out . print1n("录制Java视频");
    	}
    }
    
    扩展产品Java课堂笔记类JavaNote:
    public class JavaNote implements INote {
    	public void edit() {
    		System. out. println("编写Java笔记");
    	}
    }
    

    创建Java产品族的具体工厂JavaCourseFactory:

    public class JavaCourseFactory implements CourseFactory {
    	public INote createNote() {
    		return new JavaNote();
    	}
    	public IVideo createVideo() {
    		return new JavaVideo();
    	}
    

    来看客户端调用代码:

    public static void main(String[] args) {
    	JavaCourseFactory factory = new JavaCourseFactory() ;
    	factory.createNote().edit();
    	factory.createVideo().record();
    }
    

    抽象工厂也是有缺点的:

    1. 规定了所有可能被创建的产品集合,产品族中扩展新的产品等级困难,需要修改抽象工
      厂的接口
    2. 增加了系统的抽象性和理解难度

    参考

    • 《Spring 5 核心原理与30个类手写实战》
    • 《大话设计模式》
  • 相关阅读:
    centos 8 yum 重装
    Flask 和 Vue.js 开发及整合部署实例
    git报错fatal: unable to access 'https://****.com/c*****5/ecmall.git/': Could not resolve host: gitlab.***k.com
    CentOs服务器下安装两个个MySql数据库踩坑日记
    10-网络芯片CH395Q学习开发-模块使用Socket0作为UDP广播通信
    9-网络芯片CH395Q学习开发-模块使用Socket0作为UDP和电脑上位机UDP局域网通信
    8-网络芯片CH395Q学习开发-模块使用Socket0作为TCP服务器和电脑上位机TCP客户端局域网通信(单连接和多连接)
    7-网络芯片CH395Q学习开发-模块使用Socket0-5作为6路TCP客户端和电脑上位机TCP服务器局域网通信(Socket缓存区配置)
    6-网络芯片CH395Q学习开发-模块使用Socket0-3作为4路TCP客户端和电脑上位机TCP服务器局域网通信
    5-网络芯片CH395Q学习开发-模块使用Socket0作为TCP客户端和电脑上位机TCP服务器局域网通信
  • 原文地址:https://www.cnblogs.com/hanlk/p/14170605.html
Copyright © 2011-2022 走看看