设计模式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.缺点
规定了所有的可能被创建的产品集合,产品簇中扩展新的产品困难,需要修改抽象工厂的接口。
不符合开闭原则。
增加了系统的抽象性和理解难度。
浙公网安备 33010602011771号