前言
公司这一阵时间忙过去了,有了几天的空闲时间,近期想要学习下SpringCloud(分布式框架),通过之前下载的一个某培训机构的学习项目来学习,今天简单的跟着文档把框架搭起来了。不得不说,这培训机构的老师还是很有东西的,单看架构这一方面就让人觉得很优雅,让我的心中也不由得小小的震撼了一把,各种设计模式在项目中用的是手到擒来,系统搭建的非常具有扩展性,这样的代码看起来简直就像是艺术品一样,值得让人细细的体会了解。优秀的编码风格以及深切贴合面向对象思想的代码在我们看来是非常舒服的,希望大家写代码可以注意简单优雅,不要生产出垃圾代码,这样早晚有一天我们也能够达到老师的水准。(项目git地址)
好了,进入正题,工厂方法模式是简单工厂模式的延申,他继承了简单工厂模式的优点,弥补了简单工厂模式的缺点,更好的符合开闭原则,在新增加具体产品对象时不需要对已有的代码进行任何修改。
正文
-
概述
定义一个用于创建对象的接口,但是让子类决定是哪一个类被实例化。工厂方法模式让一个类的实例化延申到其子类。
工厂方法模式的设计思想如下:
我们在简单工厂模式中如果想要新增一个产品类就需要修改工厂类中的创建对象的方法,这并不符合开闭原则,而在工厂方法模式中,我们将生产对象的方法提取出来作为一个抽象的类或接口,然后让具体的工厂实现类来实现该方法产生对象,打个比方也就是工厂A生产A产品,工厂B生产B产品,如果我们现在需要新增一个C产品,那么也只需要在新增一个工厂C实现生产对象的接口即可,不需要对已有的带谬进行修改,这很好的符合了开闭原则。
工厂方法模式被直接称为工厂模式,又可称作虚拟构造器模式(Virtual Constructor Pattern)或多态工厂模式(Polymorphic Factory Pattern)。真正产生对象的是抽象工厂类的子类。 -
结构与实现
-
模式结构
- Factory(抽象工厂类): 抽象工厂类中声明一个方法,用于返回产品,具体工厂类实现该方法返回产品对象。
- ConcreteFactory(具体工厂类): 实现了抽象工厂类中产生对象的方法,生产对象,客户端真正调用的是此方法来产生对象。
- Product(抽象产品角色): 产品的抽象定义,所有产品对象的公共父类。
- ConcreteProduct(具体产品类): 实现了抽象产品类,每一种具体产品类都对应一个具体工厂类,该具体工厂类负责生产该具体产品类。
-
实现Demo
与简单工厂模式不同的是工厂方法模式引入了抽象工厂角色。
目录结构:
简单而典型且优雅的抽象产品类代码:public abstract class Product { public abstract void sayHello(); }
具体产品类: 具体产品类中实现抽象产品类中定义的抽象方法来做具体的业务逻辑操作。
public class ProductA extends Product { @Override public void sayHello() { System.out.println("A say hello"); } }
public class ProductB extends Product { @Override public void sayHello() { System.out.println("B say hello"); } }
抽象工厂类: 抽象工厂类可以是接口,也可以是抽象类或者具体类。
public interface Factory { Product getProduct(); }
具体工厂类: 具体工厂类继承或实现抽象工厂类,与具体产品类一一对应,负责生产产品,在具体环境中,具体工厂类一般还要负责对象一些复杂的初始化过程。
public class ProductAFactory implements Factory { @Override public Product getProduct() { System.out.println("产生了产品类A"); return new ProductA(); } }
public class ProductBFactory implements Factory { @Override public Product getProduct() { System.out.println("产生了产品类B"); return new ProductB(); } }
客户类: 模拟消费产品类
public class Client { /** * 模拟客户类 * @param args */ public static void main(String[] args) { Factory factory = new ProductAFactory(); // 可以通过配置文件与反射实现 factory.getProduct().sayHello(); Factory factory1 = new ProductBFactory(); // 可以通过配置文件与反射实现 factory1.getProduct().sayHello(); } }
-
可以通过配置文件来配置具体的工厂类,然后通过反射机制创建具体的工厂类,从而使在更换具体的工厂时无需修改代码,只需要修改配置文件。
-
超真实案例
需求: 现有一个日志记录器,要求可以通过多种途径保存日志,如: 通过文件记录\通过数据库记录,日志记录器需要一些初始化工作,且这些初始化参数的设置过程较为复杂,而且某些参数设置有严格的现后顺序,否则可能发生记录失败,为了更好的封装记录器的初始化过程;
结构: 首先需要将记录日志的方法提取出来作为接口( 抽象产品类Product ),各种日志记录方式实现该接口的记录日志方法( 具体产品ConcreteProduc t),一个可以生产各种不同记录日志的工厂接口(抽象工厂类Factory),具体生产对象的类(具体工厂类ConcreteFactory)。
客户端通过修改配置文件修改存储日志的方式上代码
目录结构:
首先是日志抽象类: 它需要一个存储日志的方法。
public interface Logger { void writeLog(); }
具体的实现类: 需要实现抽象产品类中的方法(真实环境中需要巨多的业务逻辑以及属性,由于我们是学习环境,搞得太复杂了不好理解,这里简化下)
public class FileLogger implements Logger { @Override public void writeLog() { System.out.println("日志记录到了文件"); } }
public class DatabaseLogger implements Logger { @Override public void writeLog() { System.out.println("日志记录到了数据库"); } }
public class ConsoleLogger implements Logger { @Override public void writeLog() { System.out.println("日志输出到了控制台"); } }
抽象工厂类: 定义了一个抽象的创建各种图表的方法
public interface LoggerFactory { // 创建日志记录器的方法,在实现类中可以通过重载来创建出属性不同的Logger Logger createLogger(); }
具体工厂类: 实现了抽象工厂类,负责创建以及初始化Logger类,与Logger类一一对应
public class FileLoggerFactory implements LoggerFactory { @Override public Logger createLogger() { System.out.println("经过了非常复杂的创建过程创建出了FileLogger!"); Logger logger = new FileLogger(); return logger; } }
public class DatabaseLoggerFactory implements LoggerFactory { @Override public Logger createLogger() { System.out.println("经过了非常复杂的创建过程创建出了DatabaseLogger!"); Logger logger = new DatabaseLogger(); return logger; } }
public class ConsoleLoggerFactory implements LoggerFactory { @Override public Logger createLogger() { System.out.println("经过了非常复杂的创建过程创建出了ConsoleLogger!"); Logger logger = new ConsoleLogger(); return logger; } }
config配置文件: 配置了具体的工厂类,代码中通过解析该配置文件获取具体工厂类的类名,然后通过反射获取具体工厂对象;
<?xml version="1.0" ?> <config> <className>com.xz.example.factory.impl.ConsoleLoggerFactory</className> </config>
XMLUtil: 用来解析具体的config配置文件
public class XMLUtil { public static Object getBean(){ try { // 创建DOM文档对象 DocumentBuilderFactory dFactory = DocumentBuilderFactory.newInstance(); DocumentBuilder documentBuilder = dFactory.newDocumentBuilder(); Document doc = documentBuilder.parse(new File("FactoryMethodPattern/src/com/xz/example/config.xml")); // 获取配置bean NodeList nl = doc.getElementsByTagName("className"); Node node = nl.item(0).getFirstChild(); String clazzName = node.getNodeValue(); // 利用反射生成对象并返回 Object obj = Class.forName(clazzName).newInstance(); return obj; } catch (Exception e) { e.printStackTrace(); return null; } } }
客户端模拟消费:
public class Client { /** * 模拟客户类 * 需求: * 现有一个日志记录器,要求可以通过多种途径保存日志,如: 通过文件记录\通过数据库记录,日志记录器需要一些初始化工作, * 且这些初始化参数的设置过程较为复杂,而且某些参数设置有严格的现后顺序,否则可能发生记录失败,为了更好的封装记录器的初始化过程 * 并保证多种记录器切换的灵活性,要求使用工厂方法模式设计改系统,常见的日志记录工具有SLF4J,Log4j等. * <p> * 分析: * 首先需要将记录日志的方法提取出来作为接口(抽象产品类Product),各种日志记录方式实现该接口的记录日志方法(具体产品ConcreteProduct), * 一个可以生产各种不同记录日志的工厂接口(抽象工厂类Factory),具体生产对象的类(具体工厂类ConcreteFactory). * * @param args */ public static void main(String[] args) { Object bean = XMLUtil.getBean(); if (bean != null && bean instanceof LoggerFactory){ LoggerFactory loggerFactory = (LoggerFactory) bean; Logger logger = loggerFactory.createLogger(); logger.writeLog(); } } }
如果我们要增加并且使用新的日志记录器,只需要在新增一个新的具体工厂类与具体产品类,后在客户端配置上工厂类的类名即可,原有的代码不需要做任何修改,这样更加符合开闭原则,同时也让系统更加灵活。
如果我们需要控制具体产品类的属性,那么可以在抽象工厂类中通过重载获取产品的抽象方法来实现,通过传入不同的参数初始化不同的具体产品类值,从而获得不同参数的具体产品。工厂方法的隐藏:
有时候为了进一步的简化 客户端的使用,我们可以将创建对象的方法向客户端隐藏,直接在工厂中提供一个方法实现对具体产品的调用,从而实现业务操作。public abstract class LoggerFactory { public void writeLog(){ Logger logger = this.createLogger(); logger.writeLog(); } // 创建日志记录器的方法,在实现类中可以通过重载来创建出属性不同的Logger public abstract Logger createLogger(); }
这样,客户端就可以不用调用工厂中创建对象的方法,直接调用工厂类中记录日志的方法即可,使用起来更加的简化。
-
优缺点分析
-
优点
- 工厂方法用来创建客户所需要的产品,并且向客户隐藏了哪种具体产品类被实例化,用户只需要关心生产产品的工厂,甚至不需要知道产品的类名。
- 抽象工厂与抽象产品类使工厂方法模式更加具有多态性。
- 系统在加入新的产品时无需修改已有的代码,只需增加一个具体产品类以及对应的具体工厂类即可,客户端的代码也无需修改,只需要修改配置文件即可。
-
缺点
- 在增加新的产品类时必须要新增一个具体的工厂类,这样导致扩展时类的个数是成对增加的。增加了系统的复杂度,会有更多的类需要被编译和运行,给系统带来额外的开销;
- 为了考虑系统的可扩展性,引入了抽象层,在客户端中均使用抽象层进行定义,增加了系统的抽象性和理解难度。
-
适用环境
在以下情况下可以考虑使用工厂方法模式:
1. 客户端不知道他所需要的对象的类,只需要知道对应的工厂即可;
2. 抽象工厂类通过其子类来指定创建哪个对象,其自身只需要提供一个抽象的创建对象的方法,让子类实现,这利用了面向对象的多态性和里氏转换原则。
-
-
自练习习题
- 使用工厂方法模式设计一个程序用来读取各种不同类型的图片,针对每一种图片格式都设计一个图品读取器(ImageReader),例如GIF图品读取器(GifReader)用于读取GIF图、JPG图品读取器(JpgReader)用于读取JPG图。注意:要考虑系统的灵活性和可扩展性。
自律即自由!!!
半原创博客,用以记录学习,希望可以帮到您,不喜可喷。