• 一文学会如何使用工厂模式


    工厂模式属于创建型设计模式,由简而繁可以分为简单工厂模式、工厂模式以及抽象工厂模式,虽然代码相对来说逐渐复杂,但是对于调用者而言却是越来越简单。

    简单工厂模式

    简单工厂模式是指由一个工厂对象决定创建出哪一种产品类的实例,但它不属于GOF23种设计模式。简单工厂适用于工厂类负责创建的对象较少的情况,且客户端只需要传入工厂类的参数,对于如何创建对象的逻辑不需要关心。

    接下来我们直接以代码为例,这里我们按照课程来处理。

    因为我们可能由多门课程,所以我们需要一个课程的接口。

    public interface ICourse {
    
        void record();
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    这个时候我们来创建一个JAVA课程实现课程接口。

    public class JavaCourse  implements  ICourse{
        @Override
        public void record() {
            System.out.println("JAVA学习");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    调用课程方法也很简单,直接实例化即可。

    public class Main {
    
        public static void main(String[] args) {
            ICourse course = new JavaCourse();
            course.record();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    上面的代码,父类ICourse指向子类JavaCourse的引用,应用层代码需要依赖JavaCourse,如果我们的课程越来越多,我们这里会增加很多课程代码,导致代码变得很臃肿,因此我们需要把这种依赖减弱,把创建的细节隐藏。我们先通过简单工厂模式来优化代码。

    首先再次创建一个课程类。

    public class PythonCourse implements ICourse{
        @Override
        public void record() {
            System.out.println("PYTHON学习");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    然后创建工厂类,用来创建我们所需要的课程。

    public class CourseFactory {
    
        public ICourse create(String name) {
            if ("java".equals(name)) {
                return new JavaCourse();
            } else if ("python".equals(name)) {
                return new PythonCourse();
            } else {
                return null;
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    这时候的客户端调用只需要传入课程名就可以获取对应实例。

    public class Main {
    
        public static void main(String[] args) {
            ICourse course = new CourseFactory().create("java");
            course.record();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    从类图种我们可以看出现在的调用时简单了,但是随着业务的继续拓展,每次添加课程我们都需要修改代码,因此我们的代码还有优化的空间。

    在这里插入图片描述

    这里我们可以采用反射技术,根据传入的类名,我们直接反射出对应的实例。

    public ICourse createByReflect(String className) {
        if (className == null) {
            return null;
        }
        try {
            return (ICourse) Class.forName(className).newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    修改客户端调用代码:

    public class Main {
    
        public static void main(String[] args) {
            ICourse course = new CourseFactory().createByReflect("com.example.javadesign.factory.simplefactory.JavaCourse");
            course.record();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    优化之后,就算添加新的课程我们也不需要去修改代码了,但是我们传递的参数太过复杂,很容易搞错,同时还需要进行类型强转,所以我们还需要进行优化。

    public ICourse createByClass(Class<? extends ICourse> clazz) {
        if (clazz == null) {
            return null;
        }
        try {
            return clazz.newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    修改调用方法:

    public class Main {
    
        public static void main(String[] args) {
            ICourse course = new CourseFactory().createByClass(JavaCourse.class);
            course.record();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    现在我们再看这个创建方法是不是就很好了,我们只需要传递对应的类型就可以直接创建实例了,简单快捷。

    简单工厂模式在JDK源码种用的也很多,比如Calendar类,我们来看他获取实例的方法。Calendar.getInstance();

    public static Calendar getInstance()
    {
        return createCalendar(TimeZone.getDefault(), Locale.getDefault(Locale.Category.FORMAT));
    }
    
     private static Calendar createCalendar(TimeZone zone,
                                               Locale aLocale)
        {
            CalendarProvider provider =
                LocaleProviderAdapter.getAdapter(CalendarProvider.class, aLocale)
                                     .getCalendarProvider();
            if (provider != null) {
                try {
                    return provider.getInstance(zone, aLocale);
                } catch (IllegalArgumentException iae) {
                    // fall back to the default instantiation
                }
            }
    
            Calendar cal = null;
    
            if (aLocale.hasExtensions()) {
                String caltype = aLocale.getUnicodeLocaleType("ca");
                if (caltype != null) {
                    switch (caltype) {
                    case "buddhist":
                    cal = new BuddhistCalendar(zone, aLocale);
                        break;
                    case "japanese":
                        cal = new JapaneseImperialCalendar(zone, aLocale);
                        break;
                    case "gregory":
                        cal = new GregorianCalendar(zone, aLocale);
                        break;
                    }
                }
            }
            if (cal == null) {
                // If no known calendar type is explicitly specified,
                // perform the traditional way to create a Calendar:
                // create a BuddhistCalendar for th_TH locale,
                // a JapaneseImperialCalendar for ja_JP_JP locale, or
                // a GregorianCalendar for any other locales.
                // NOTE: The language, country and variant strings are interned.
                if (aLocale.getLanguage() == "th" && aLocale.getCountry() == "TH") {
                    cal = new BuddhistCalendar(zone, aLocale);
                } else if (aLocale.getVariant() == "JP" && aLocale.getLanguage() == "ja"
                           && aLocale.getCountry() == "JP") {
                    cal = new JapaneseImperialCalendar(zone, aLocale);
                } else {
                    cal = new GregorianCalendar(zone, aLocale);
                }
            }
            return cal;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55

    还有大家经常使用的logback里面也有多个重载的方法。

    在这里插入图片描述

    总结

    • 优点

      只需要传入一个正确参数即可获取所需对象,无需知道创建的细节。

    • 缺点

      工厂类的职责相对过重,增加新的产品时需要修改工厂类的判断逻辑,违背开闭原则;不易于拓展过于复杂的产品结构

    工厂模式

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

    工厂模式主要解决产品拓展的问题,在简单工厂中,随着产品链的丰富如果每个产品的创建逻辑有区别的话,工厂的职责会变得越来越多,有点像万能工厂,但不便于维护。根据单一职责原则我们需要将其职能进行拆分,专人干专事。

    代码如下所示:我们首先创建工厂接口:

    public interface ICourseFactory {
    
        public ICourse create();
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    然后创建Java工厂:

    public class JavaCourseFactory implements ICourseFactory{
        @Override
        public ICourse create() {
            return new JavaCourse();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    这个时候我们创建实例的时候只需要通过指定工厂就可以直接获取实例了。

    public class Main {
    
        public static void main(String[] args) {
            ICourseFactory courseFactory= new JavaCourseFactory();
            ICourse course = courseFactory.create();
            course.record();
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

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

    • 创建对象需要大量重复的代码
    • 客户端不依赖于产品类如何被创建,实现等细节。
    • 一个类通过其子类来指定创建哪个对象。

    但也有相关缺点:

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

    抽象工厂模式

    抽象工厂模式是指提供一个创建一系列相关或相互依赖对象的接口,无需指定他们具体的类。客户端不依赖于产品类实例如何被创建、实现等细节。需要提供一个产品类的库,所有的产品以同样的接口出现,从而使客户端不依赖于具体实现。

    可能说的太抽象,我们按照一个案例来看。

    • 产品:课程

    • 产品族:Java课程

    • 产品等级:课程视频、课程笔记

    以课程为例,我们上网课的时候需要看视频,同时可能老师还会提供相关的笔记。相当于现在的业务变为一个课程不单纯是一个课程信息,需要同时包含视频,笔记才算一个完整的课程。我们之前只有一个课程接口,现在还需要新增视频以及笔记接口。

    public interface INote {
    
        void edit();
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    public interface IVideo {
    
        void play();
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    然后实现两个产品等级:视频以及笔记:

    public class JavaVideo implements IVideo{
        @Override
        public void play() {
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    public class JavaNode implements INote{
        @Override
        public void edit() {
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    这个时候我们来创建一个抽象工厂CourseFactory类,我们也可以使用接口来实现,但是使用抽象工厂我们可以写一些公有方法。

    public abstract class CourseFactory {
    
        public void init(){
            System.out.println("初始化基础数据");
        }
    
        protected abstract INote createNote();
    
        protected abstract IVideo createVideo();
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

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

    public class JavaCourseFactory extends CourseFactory{
        @Override
        protected INote createNote() {
            super.init();
            return new JavaNode();
        }
    
        @Override
        protected IVideo createVideo() {
            super.init();
            return new JavaVideo();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    调用:

    public class Main {
    
        public static void main(String[] args) {
            CourseFactory factory = new JavaCourseFactory();
            factory.createNote().edit();
            factory.createVideo().play();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    上面的代码描述了产品族Java课程,也描述了两个产品等级:视频和笔记。

    抽象工厂非常完美清晰的描述这样一层复杂的关系。但是不知道大家有没有发现,如果我们继续扩展产品等级,比如加入作业,那么我们的抽象工厂从抽象工厂到具体工厂都需要全部调整,很显然这不符合开闭原则。因此抽象工厂的缺点也很明显:

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

    但是在实际应用中,产品等级结构升级时非常正常的,只要不是频繁升级,我们可以不遵守开闭原则。

    项目地址

    设计模式案例

  • 相关阅读:
    服务器神秘挂起:一场惊心动魄的内核探案
    vue父子组件传值与兄弟传值的注意点
    【Java】Spring scurity + JWT 前后端分离
    商用车市场「跌跌不休」,主动安全「让位」智能驾驶?
    OpenGL之纹理过滤(Texture Filtering)、MipMap方法、纹理坐标
    12 款小众宝藏AI工具,90% 的开发者不了解
    学校上课,是耽误我学习了。。
    python:pyqt5案例(简易浏览器)
    【C/C++】函数指针与指针函数
    Behave介绍和快速示例
  • 原文地址:https://blog.csdn.net/qq_41432730/article/details/126332446