设计模式02-概述及工厂模式

1.2.设计模式02-概述及工厂模式

1.2.1.设计模式总述

时长:57min---31min

计划:2020/4/27 14:59-16:00

1.学习设计模式的目的

  》帮助我们更好地编码,写出更加优雅的代码

  》帮助我们,更好地重构项目

2.回顾7大软件设计原则

1.开闭原则

2.单一职责原则

3.依赖倒置原则

4.接口隔离原则

5.迪米特法则---最少知道

6.里氏替换原则---父子类关系

7.合成复用原则---多组合,少继承

口诀:开单赖接口,迪里合

3.设计模式的好处

4.spring中使用到的设计模式

工厂模式 BeanFactory

装饰器模式 BeanWrapper

代理模式 AopProxy

单例模式 ApplicationContext

委派模式 DispatcherServlet

策略模式 HandlerMapping

适配器模式 HandlerAdapter

模板方法模式 JdbcTemplate

观察者模式 ContextLoaderListener

 

业务场景中,设计模式应用:

 1.2.2.工厂模式

1.工厂模式的由来

  设计模式,其实是编程者在编码时,针对某些特殊类型场景的提供的解决方案的总结,是经验的总结。

  

  软件设计,它的精髓在于设计思想,而设计思想是基于业务场景,即来源于生活场景,生活哲学。

 

  工厂模式的产生,和人类社会的变更历程,极其相似。

  人类社会的发展:原始社会【自给自足】---》农耕社会【小作坊】-----》工厂流水线生产--------》现代产业链代工厂

 

2.简单工厂的定义

  Simple Factory Pattern。

定义:

  是指由一个工厂对象决定哪一种产品类的实例。

  属于创建创建型模式【创建对象】,但不属于GOF,23种设计模式之一。

 

  简单工厂,就是小作坊。

3.示例代码

  这里以视频教学中,课程为业务场景。

3.1.普通创建对象
3.1.1.创建业务类---课程
package com.wf.simple_factory_pattern;

/**
 * @ClassName Course
 * @Description 课程
 * @Author wf
 * @Date 2020/4/27 16:39
 * @Version 1.0
 */
public class Course {
    //录制课程
    public void record(){
        System.out.println("录制课程");
    }

}
3.1.2.测试类
package com.wf.simple_factory_pattern;

/**
 * @ClassName CourseTest
 * @Description 测试类
 * @Author wf
 * @Date 2020/4/27 16:41
 * @Version 1.0
 */
public class CourseTest {
    public static void main(String[] args) {
        Course course = new Course();
        course.record();//录制课程
    }
}

测试结果说明:  

  测试类中,自己new Course【自己录制课程】,然后自己发布。

  

  但是,随着业务的增加,课程在不断增加,产生java课程,大数据,python,人工智能课程。

 

  如果要对其他课程进行录制,如果也同样采用new的方式,就会产生大量重复代码。

  通过设计原则,需要面向抽象编程。把所有的课程抽象出来一个接口ICourse.

3.2.面向抽象编程
3.2.1.抽象出公共接口ICourse
package com.wf.simple_factory_pattern;

/**
 * @ClassName Course
 * @Description 课程
 * @Author wf
 * @Date 2020/4/27 16:39
 * @Version 1.0
 */
public interface ICourse {
    //录制课程
    public void record();

}
3.2.2.实现各种课程
package com.wf.simple_factory_pattern;

/**
 * @ClassName JavaCourse
 * @Description TODO
 * @Author wf
 * @Date 2020/4/27 16:57
 * @Version 1.0
 */
public class JavaCourse implements ICourse {
    @Override
    public void record() {
        System.out.println("录制Java课程");
    }
}

再有其他课程扩展,类似实现。

3.2.3.测试类调用
package com.wf.simple_factory_pattern;

/**
 * @ClassName CourseTest
 * @Description 测试类
 * @Author wf
 * @Date 2020/4/27 16:41
 * @Version 1.0
 */
public class CourseTest {
    public static void main(String[] args) {
       ICourse course = new JavaCourse();
        course.record();//录制Java课程
    }
}

测试结果说明:

  使用多态的方式,创建实例,调用它的方法。

  但是,这种方式仍然后存在问题,客户端代码它依赖具体的课程类

  客户端真正想要的是课程的实例,它并不关心实例的创建细节。

 

  那该怎么处理呢?这里就需要使用到工厂模式,就工厂给客户端生产一个实例。

3.3.使用工厂来创建实例
3.3.1.扩展出一个新课程PythonCourse
package com.wf.simple_factory_pattern;

/**
 * @ClassName PythonCourse
 * @Description 新增一个Python课程
 * @Author wf
 * @Date 2020/4/27 17:30
 * @Version 1.0
 */
public class PythonCourse implements ICourse {
    @Override
    public void record() {
        System.out.println("录制Python课程");
    }
}
3.3.2.定义一个工厂创建实例
package com.wf.simple_factory_pattern;

/**
 * @ClassName CourseFactory
 * @Description 创建课程的工厂
 * @Author wf
 * @Date 2020/4/27 17:32
 * @Version 1.0
 */
public class CourseFactory {
    public ICourse create(String name){
        if("java".equals(name)){
            return new JavaCourse();
        }else if("java".equals(name)){
            return new PythonCourse();
        }else {
            return null;
        }
        
    }
}
3.3.3.客户端调用
public static void main(String[] args) {
        ICourse course = new CourseFactory().create("java");
        course.record();//录制Java课程

        ICourse python = new CourseFactory().create("python");
        python.record();//录制Python课程
    }

测试结果说明:

  在使用工厂来创建实例时,需要传递一个实例的标识【这里是字符串】。

  但是,很有可能这个传参,匹配不到实例【传了一个错误的值】,就会产生异常。

A.工厂逻辑改造

  内部实现,通过反射创建实例。代码改造如下:

public class CourseFactory {
    
    public ICourse create(String className){
//        if("java".equals(name)){
//            return new JavaCourse();
//        }else if("python".equals(name)){
//            return new PythonCourse();
//        }else {
//            return null;
//        }
        try {
            if (!(null == className || "".equals(className))) {
                //传参全路径类名
                return (ICourse) Class.forName(className).newInstance();
            }
        }catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }
}

客户调用:

 public static void main(String[] args) {
        ICourse javaCourse = new CourseFactory().create("com.wf.simple_factory_pattern.JavaCourse");
        javaCourse.record();//录制Java课程
    }

测试说明:

  这里的问题是,传参是全路径类名,也比较复杂。可以采用spring中传参方式,Class对象传参。

B.再次改造工厂类实现逻辑
package com.wf.simple_factory_pattern;

/**
 * @ClassName CourseFactory
 * @Description 创建课程的工厂
 * @Author wf
 * @Date 2020/4/27 17:32
 * @Version 1.0
 */
public class CourseFactory {

/*    public ICourse create(String className){
//        if("java".equals(name)){
//            return new JavaCourse();
//        }else if("python".equals(name)){
//            return new PythonCourse();
//        }else {
//            return null;
//        }
        try {
            if (!(null == className || "".equals(className))) {
                //传参全路径类名
                return (ICourse) Class.forName(className).newInstance();
            }
        }catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }*/

    public ICourse create(Class clazz){
        try {
            if(null != clazz){
                return (ICourse) clazz.newInstance();
            }
        }catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }
}

客户端调用:

public static void main(String[] args) {
        ICourse course = new CourseFactory().create(JavaCourse.class);
        course.record();//录制Java课程
    }

说明:

  客户端代码存在问题,因为这里又引入的具体课程的依赖。【增加了代码的耦合度

  此外,工厂实现中类型强转换,也是存在问题的。【给代码可读性带来困惑】

C.第三次改造工厂类逻辑

工厂要限定,其职责为创建课程实例,为遵守单一职责原则,这里使用泛型来改进:

package com.wf.simple_factory_pattern;

/**
 * @ClassName CourseFactory
 * @Description 创建课程的工厂
 * @Author wf
 * @Date 2020/4/27 17:32
 * @Version 1.0
 */
public class CourseFactory {

/*    public ICourse create(String className){
//        if("java".equals(name)){
//            return new JavaCourse();
//        }else if("python".equals(name)){
//            return new PythonCourse();
//        }else {
//            return null;
//        }
        try {
            if (!(null == className || "".equals(className))) {
                //传参全路径类名
                return (ICourse) Class.forName(className).newInstance();
            }
        }catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }*/

   /* public ICourse create(Class clazz){
        try {
            if(null != clazz){
                return (ICourse) clazz.newInstance();
            }
        }catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }*/
    //传参泛型,改进类型强转换
    public ICourse create(Class<? extends ICourse> clazz){
        try {
            if(null != clazz){
                return  clazz.newInstance();
            }
        }catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }
}

 

3.3.4.查看系统类图

最后的系统类图如下所示:

 

4.Jdk源码中简单工厂的使用

4.1.Calendar工具类

  这是一个日历类。它有一个getInstance方法:

 public static Calendar getInstance()
    {
        return createCalendar(TimeZone.getDefault(), Locale.getDefault(Locale.Category.FORMAT));//创建实例
    }

createCalendar方法,创建实例:

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;
    }

5.第三方依赖logback中简单工厂的应用

5.1.引入sl4j的依赖
    <!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-log4j12 -->
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-log4j12</artifactId>
      <version>1.7.2</version>
      <scope>test</scope>
    </dependency>
5.2.LoggerFactory工厂

 

6.简单工厂模式适用场景

  工厂类,负责创建的对象较少。

  客户端只需要传入工厂类的参数,对于如何创建对象的细节并不关心。

 

 

 简单工厂的优点:

  只需传入一个正确的参数,就可以获取你所需要的实例。

  无需知道创建实例的细节。

 

简单工厂 的缺点:

  工厂类的职责相对过重,增加新的产品时需要修改工厂类的判断逻辑,违背开闭原则。

  不易于扩展过于复杂的产品结构。

  

 1.2.3.工厂方法模式

   Factory Method Pattern。

定义:

  是指定义一个创建对象的接口,但让实现这个接口的类【工厂类】来决定实例化哪一个类。工厂方法让类的实例化推迟到

子类中进行。

  属于创建型设计模式,是GOF 23种模式之一。

 

   它是对简单工厂模式的缺点,进行优化的解决方案。

 

  简单工厂,只适用于产品结构少的场景。如果产品结构较为复杂,该怎么办呢?于是,工厂方法模式出现了。

 

  同样,需要定义工厂来创建产品实例。【这里面向接口编程,定义为接口】

1.示例代码

A.定义创建对象的工厂接口
package com.wf.factorymethod;


/**
 * @ClassName ICourseFactory
 * @Description TODO
 * @Author wf
 * @Date 2020/4/29 15:01
 * @Version 1.0
 */
public interface ICourseFactory {
    ICourse create();
}
B.产品实例的顶层接口ICourse
package com.wf.factorymethod;

/**
 * @ClassName ICourse
 * @Description 课程接口
 * @Author wf
 * @Date 2020/4/29 15:02
 * @Version 1.0
 */
public interface ICourse {
    //录制课程
    public void record();
}
C.实现JavaCourseFactory

  前面定义顶层的Factory接口,为了符合单一职责原则。实现不同的工厂。

package com.wf.factorymethod;

/**
 * @ClassName JavaCourseFactory
 * @Description TODO
 * @Author wf
 * @Date 2020/4/29 15:08
 * @Version 1.0
 */
public class JavaCourseFactory implements ICourseFactory {
    @Override
    public ICourse create() {
        return new JavaCourse();
    }
}

同样,可以扩展其他Factory,如PythonCourseFactory,如下所示:

package com.wf.factorymethod;

/**
 * @ClassName PythonCourseFactory
 * @Description TODO
 * @Author wf
 * @Date 2020/4/29 15:13
 * @Version 1.0
 */
public class PythonCourseFactory implements ICourseFactory {
    @Override
    public ICourse create() {
        return new PythonCourse();
    }
}

 

D.核心业务类
package com.wf.factorymethod;

/**
 * @ClassName JavaCourse
 * @Description TODO
 * @Author wf
 * @Date 2020/4/27 16:57
 * @Version 1.0
 */
public class JavaCourse implements ICourse {
    @Override
    public void record() {
        System.out.println("录制Java课程");
    }
}

同样,扩展产品类PythonCourse:

package com.wf.factorymethod;

/**
 * @ClassName PythonCourse
 * @Description TODO
 * @Author wf
 * @Date 2020/4/29 15:14
 * @Version 1.0
 */
public class PythonCourse implements ICourse{

    @Override
    public void record() {
        System.out.println("录制Python课程");
    }
}
E.客户端调用
package com.wf.factorymethod;

/**
 * @ClassName FactoryMethodTest
 * @Description TODO
 * @Author wf
 * @Date 2020/4/29 15:17
 * @Version 1.0
 */
public class FactoryMethodTest {
    public static void main(String[] args) {
        ICourseFactory factory = new PythonCourseFactory();
        ICourse course = factory.create();
        course.record();//录制Python课程
    }
}

客户端,要创建什么实例,只需要找到对应的工厂,工厂就会创建实例。

 

对比简单工厂模式,工厂类中创建产品实例。导致工厂类的职责过重。里面会出现大量的if-else分支,或switch分支语句。

改进为工厂方法模式,每个工厂类,只创建一个类型的实现。严格遵守单一职责原则。

F.系统类图

 

 2.工厂方法模式总结

A.适用场景

  创建对象需要大量重复的代码。

  客户端(应用层)不依赖于产品类实例如何被创建,实现等细节。

  一个类通过其子类来指定创建哪个对象。

 

  针对创建对象需要大量重复代码的情况,可以使用工厂方法模式的另一种方式。

  即工厂接口下面定义一个abstract工厂,把创建实例的一些公共代码提取到抽象工厂中,做一个预处理

 

B.工厂方法的缺点

类的个数容易过多,增加了代码结构的复杂度

增加了系统的抽象性理解程度。【对于可读性影响较大】

1.2.4.抽象工厂模式

Abastract Factory Pattern.

定义:

  是指提供一个创建一系列相关或相互依赖对象的接口,无须指定他们具体的类。

  属于创建型设计模式。

1.产品结构理解

 

如上图所示:

  横向来看,是同一种颜色。不同的形状排列。

  纵向来看,形状相同,颜色不同。

 

 假设,不同形状,代表不同产品。如:分别代表游衣机热水器,冰箱。

  不同颜色,代表不同商家或品牌,如:分别代表格利海尔,美地。

 

可以了解到:

  产品结构很是复杂。同一产品,可以有不同品牌。

  同样,同一品牌,又对应着不同产品。

 

  对于,如此复杂产品结构,简单工厂和工厂方法都适合处理这种问题。因此使用抽象工厂来进行处理。

抽象工厂的处理方式如下所示:

  针对每一个产品簇,创建一个具体的工厂。

 

 

 2.代码示例

  这里以课程为例。一个课程包括,大纲,录播视频,课件,课后作业,源码。

  课程又有分类:Java,Python,大数据,AI

A.定义一个顶层工厂接口

要求所有子工厂都实现该接口。这个顶层接口,可以抽象出所有产品结构,课程,笔记,录播视频。

package com.wf.abstractfactory;

/**
 * @ClassName ICourseFactory
 * @Description TODO
 * @Author wf
 * @Date 2020/4/29 16:17
 * @Version 1.0
 */
public interface ICourseFactory {
    /** 创建课程*/
    ICourse createCourse();
    /** 创建笔记*/
    INote createNote();
    /** 创建录播视频*/
    IVideo createVideo();
}
【1】添加产品结构接口
package com.wf.abstractfactory;

/**
 * @ClassName Course
 * @Description 课程
 * @Author wf
 * @Date 2020/4/27 16:39
 * @Version 1.0
 */
public interface ICourse {
    //录制课程
    public void record();

}
package com.wf.abstractfactory;

/**
 * @ClassName INote
 * @Description 笔记接口
 * @Author wf
 * @Date 2020/4/29 16:22
 * @Version 1.0
 */
public interface INote {

}
package com.wf.abstractfactory;

/**
 * @ClassName IVideo
 * @Description 录播视频接口
 * @Author wf
 * @Date 2020/4/29 16:23
 * @Version 1.0
 */
public interface IVideo {
}

 

 

 

B.针对每一产品簇,创建一个具体工厂
package com.wf.abstractfactory;

/**
 * @ClassName JavaCourseFactory
 * @Description TODO
 * @Author wf
 * @Date 2020/4/29 16:28
 * @Version 1.0
 */
public class JavaCourseFactory implements ICourseFactory {
    @Override
    public ICourse createCourse() {
        return new JavaCourse();
    }

    @Override
    public INote createNote() {
        return new JavaNote();
    }

    @Override
    public IVideo createVideo() {
        return new JavaVideo();
    }
}
【1】创建具体的产品类
package com.wf.abstractfactory;
/**
 * @ClassName JavaCourse
 * @Description TODO
 * @Author wf
 * @Date 2020/4/27 16:57
 * @Version 1.0
 */
public class JavaCourse implements ICourse {
    @Override
    public void record() {
        System.out.println("录制Java课程");
    }
}
package com.wf.abstractfactory;

/**
 * @ClassName JavaNote
 * @Description TODO
 * @Author wf
 * @Date 2020/4/29 16:29
 * @Version 1.0
 */
public class JavaNote implements INote {

}
package com.wf.abstractfactory;

/**
 * @ClassName JavaVideo
 * @Description TODO
 * @Author wf
 * @Date 2020/4/29 16:31
 * @Version 1.0
 */
public class JavaVideo implements IVideo {
}

 

 C.客户端调用
package com.wf.abstractfactory;

/**
 * @ClassName AbastractFactoryTest
 * @Description TODO
 * @Author wf
 * @Date 2020/4/29 16:35
 * @Version 1.0
 */
public class AbastractFactoryTest {
    public static void main(String[] args) {
        ICourseFactory factory = new JavaCourseFactory();
        factory.createCourse().record();
        factory.createNote();
        factory.createVideo();
    }
}

总结:

  抽象工厂是存在问题的,它不符合开闭原则。但是它易于扩展。

 

  假设产品结构进行扩展,顶层接口中需要创建源码。当顶层接口中增加一个方法时,它原来的子工厂都需要实现,这个方法。

这就导致代码大量修改。违背开闭原则。

 

  然而,现实中适合抽象工厂的场景很多,我们会大量使用抽象工厂模式。虽然,它不符合开闭原则。并不代表就不能使用它。

 

  我们知道,代码开发有一定迭代周期,代码并不是一沉不变的,在一定的迭代周期,我们允许对系统进行升级

  

 

如果产品结构经常变化,变化非常频繁,抽象工厂就不适用了。

 

 3.抽象工厂模式总结

A.适用场景

 客户端(应用层)不依赖产品实例如何被创建,实现等细节。【实例创建无关心创建细节】

 强调一系列相关的产品对象(属于同一产品簇),一起使用创建对象需要大量重复代码。

 提供一个产品类的库,所有的产品以同样的接口出现,从而使客户端不依赖具体实现。

 B.优点

  具体产品在应用层代码隔离,无须关心创建细节。

  将一系列的产品簇统一到一起创建。【由顶层接口统一设置】

C.缺点

   规定了所有的可能被创建的产品集合,产品簇中扩展新的产品困难,需要修改抽象工厂的接口。

不符合开闭原则。

  增加了系统的抽象性和理解难度。

 

  

  

 

posted @ 2020-04-27 14:54  我爱钻研  阅读(54)  评论(0)    收藏  举报