工厂方法模式-日志工厂_implements logger_程序员小潘的博客-程序员宅基地

技术标签: 设计模式  

1. 日志工厂

一个软件,不管是本地开发调试,还是线上生产运行,日志的收集都是非常重要的。如何设计一个灵活的日志系统呢?

将日志的功能抽象出来,封装成接口:

public interface Logger {
    

	// 调试信息
	void debug(String message);

	// 错误信息
	void error(String message);
}

每个日志记录器都应该有自己的名称,不然你怎么知道是谁输出的日志呢?

public abstract class AbstractLogger implements Logger {
    
	protected String name;// 日志记录器名称

	public AbstractLogger(String name) {
    
		this.name = name;
	}
}

本地开发调试,只需要将日志输出到控制台就好了,无需持久化存储。

public class ConsoleLogger extends AbstractLogger {
    

	public ConsoleLogger(String name) {
    
		super(name);
	}

	@Override
	public void debug(String message) {
    
		System.out.println(name + " -- " + message);
	}

	@Override
	public void error(String message) {
    
		System.err.println(name + " -- " + message);
	}
}

线上环境,没人会盯着日志去看,所以需要将日志写入磁盘文件,后面排查错误时你就知道它的重要性了。

public class FileLogger extends AbstractLogger{
    
	private File debugLogFile = new File(System.getProperty("user.dir"), "logs/debug.log");
	private File errorLogFile = new File(System.getProperty("user.dir"), "logs/error.log");

	public FileLogger(String name) {
    
		super(name);
	}

	@Override
	public void debug(String message) {
    
		FileUtil.appendUtf8String(name + " -- " + message, debugLogFile);
	}

	@Override
	public void error(String message) {
    
		FileUtil.appendUtf8String(name + " -- " + message, errorLogFile);
	}
}

日志的两种实现都有了,分别是控制台输出和写入磁盘文件。
那么,如何生成Logger实例呢?手动new吗?
你怎么知道我需要哪种实现?万一后面又增加了新的需求,需要将日志写入ElasticSearch作离线分析呢?你又要怎么做?

客户端只依赖日志接口,具体Logger实例的创建交给工厂吧。

定义日志工厂接口:

public interface LoggerFactory {
    

	// 获取日志记录器
	Logger getLogger(String name);
}

专门生产控制台日志的工厂:

public class ConsoleLoggerFactory implements LoggerFactory {
    

	@Override
	public Logger getLogger(String name) {
    
		return new ConsoleLogger(name);
	}
}

专门生产文件日志的工厂:

public class FileLoggerFactory implements LoggerFactory{
    

	@Override
	public Logger getLogger(String name) {
    
		return new FileLogger(name);
	}
}

客户端调用:

public class Client {
    
	public static void main(String[] args) {
    
		LoggerFactory factory = new FileLoggerFactory();
		Logger logger = factory.getLogger(Client.class.getName());
		logger.error("哈哈");
	}
}

后面如果加了新的日志实现,只需要再扩展一个工厂类就好了。
这就是工厂方法模式。

2. 工厂方法模式的定义

定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。

在这里插入图片描述

工厂方法模式通用类图
  • Product:产品的抽象类或接口,负责定义产品的特征和共性。
  • ConcreteProduct:具体的产品类。
  • Creator:产品的创建类,工厂接口。
  • ConcreteCreator:具体的创建类,工厂实现。

3. 工厂方法模式的优点

工厂方法模式,封装了对象创建的过程,对外屏蔽了复杂对象的创建细节,而且降低了类间的耦合。客户端只依赖产品的抽象,无需知道产品实现,让产品工厂来完成对象的创建。

  1. 客户端使用简单,无需知道复杂对象的创建过程。
  2. 工厂可以控制对象的创建数量,避免内存溢出,还可以复用对象,避免频繁创建和销毁。
  3. 工厂可以提前创建好一批对象,以便快速响应客户端。
  4. 降低耦合,客户端只依赖产品抽象,符合依赖倒置原则。
  5. 客户端不关心具体实现类,只关心工厂,符合迪米特法则。
  6. 扩展的产品子类可随时替换父类,符合里氏替换原则。

工厂方法模式是new关键字的替代品,它提供了创建对象的一种绝佳的方式,任何需要创建对象的地方都可以考虑使用该模式。缺点是需要增加工厂类,代码复杂度会提高,需要结合实际情况考虑。

4. 工厂方法模式的扩展

4.1 降级为简单工厂

如果一个模块很简单,只需要一个工厂类,就可以降级为简单工厂,使用静态方法来创建对象。

public class SimpleFactory {
    
	/**
	 * 优点:实现简单
	 * 缺点:增加一个产品类型就要修改工厂,不符合开闭原则、static无法定义层级结构
	 * @param type
	 * @return
	 */
	public static Product create(ProductType type) {
    
		switch (type){
    
			case A:
				return new ProductA();
			case B:
				return new ProductB();
			default:
				return null;
		}
	}
}

4.2 替代单例模式

单例模式本身既要负责业务逻辑,又要保证单例,不符合单一职责原则,可以使用工厂模式来替代单例模式,由工厂类来保证单例,而不是单例类本身。

class Singleton{
    
	private Singleton(){
    }
}

class SingletonFactory{
    
	private static Singleton INSTANCE;

	static {
    
		try {
    
			Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor();
			constructor.setAccessible(true);
			INSTANCE = constructor.newInstance();
		} catch (Exception e) {
    
			e.printStackTrace();
		}
	}
	
	public static Singleton getInstance(){
    
		return INSTANCE;
	}
}

4.3 对象复用

对于可以复用的对象,工厂类可以将实例缓存起来,避免频繁的创建和销毁。这在很大程度上可以降低系统的性能开销,减轻GC的压力。

class ProductFactory {
    
	private static final Map<ProductType, Product> CACHE = new ConcurrentHashMap<>();

	public static Product getProduct(ProductType productType) {
    
		if (!CACHE.containsKey(productType)) {
    
			synchronized (ProductFactory.class) {
    
				if (!CACHE.containsKey(productType)) {
     // recheck
					switch (productType) {
    
						case A:
							CACHE.put(productType, new ProductA());
							break;
						case B:
							CACHE.put(productType, new ProductB());
							break;
					}
				}
			}
		}
		return CACHE.get(productType);
	}
}

5. 总结

工厂方法模式在项目中使用的非常频繁,很多开源框架都能看到它的影子,它提供了对象创建的一种绝佳方式,凡是需要创建对象的地方,都可以考虑使用工厂方法模式。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_32099833/article/details/111655795

智能推荐

ewm交货单过账状态,交货单信息(ewm过账信息发给dms)_erp交货单已过账状态同步fsm的tcode-程序员宅基地

3、交货单过账状态,交货单信息(ewm过账信息发给dms) 需求: erp生成交货单,然后交货单同步到ewm ,ewm交货过账以后会返回erp过账的状态 和包装等一些信息,这个时候我们这个点做增强,让ewm过完账同步erp的同时下发信 息给dms系统。 难点: 在找包装信息也就是HU信息的时候,发现他不跟交货单的过账状态一起更新erp,导致..._erp交货单已过账状态同步fsm的tcode

lineEdit->textChanged信号不触发的原因_lineedit textchanged_「已注销」的博客-程序员宅基地

  项目要求是当lineEdit中的内容发生改变时,激活find按纽。但实际运行结果却不行,发现是自己的connect语句写错了:// connect ( lineEdit, SIGNAL ( textChanged ( QString & ) ), this, SLOT ( enableFindButton ( QString & ) ) ); /* 错误写法 */connect ( lineEdit, SIGNAL ( textChanged ( const QString &am_lineedit textchanged

【笔记】控制单元的功能-程序员宅基地

一,微操作命令的分析1.取指周期2.间址周期3.执行周期非访存指令一,微操作命令的分析1.取指周期  取指令的过程可归纳为以下几个操作: PC→MARPC→MARPC \rightarrow MAR 1→R1→R1\rightarrow R 将MAR(通过地址总线)所指的主存单元中的内容(指令)经数据总线读至MDR内,记作 M (M A...

[python异步 第三篇 ] python事件循环库的发展历史_python事件库-程序员宅基地

Python的异步IO异步IO的优势显而易见,各种语言都通过实现这个机制来提高自身的效率,Python也不例外。一、Python 2的异步IO库Python 2 时代官方并没有异步IO的支持,但是有几个第三方库通过事件或事件循环(Event Loop)实现了异步IO,它们是:twisted: 是事件驱动的网络库gevent: greenlet + libevent(后来是libev或libuv)。通过协程(greenlet)和事件循环库(libev,libuv)实现的gevent使用很广泛。to_python事件库

k8s免fq下载镜像-程序员宅基地

简介新手使用kubeadm搭建k8s最困难的一件事情就是下载k8s的docker镜像,那么有没有好的方法呢,之前最常用的就是使用Dockerfile在上面加上FROM+你要下载的镜像字段,然后使用dockerhub把这个镜像编译起来,之后我们本地pull下dockerhub上已经编译好的镜...

安装gcc必需的三个库,解决报错:gcc configure: error: Building GCC requires GMP 4.2+, MPFR 2.3.1+ and MPC 0.8.0+-程序员宅基地

编译安装gcc的时候,configure出了问题Building GCC requires GMP 4.2+, MPFR 2.3.1+ and MPC 0.8.0+http://www.multiprecision.org/mpc 下载mpc-0.9.tar.gzftp://ftp.gnu.org/gnu/gmp/gmp-5.0.1.tar.bz2下载gmp-5.0.1.tar.bz2http://ftp.gnu.org/gnu/mpfr/下载mpfr-3.1.0.tar.xz。先开始安装G_gcc configure

随便推点

【面试题】二叉树_二叉树 面试-程序员宅基地

目录(?)[+]本篇针对面试中常见的二叉树操作作个总结:   (1)前序遍历,中序遍历,后序遍历;   (2)层次遍历;   (3)求树的节点数;   (4)求树的叶子数;   (5)求树的深度;   (6)求二叉树第k层的节点个数;   (7)判断两棵二叉树是否结构相同;   (8)求二叉树的镜像;   (9)求两个节点的最低公共祖先节点;_二叉树 面试

uboot顶层Makefile分析-程序员宅基地

一、uboot源码目录分析很多文件是经过编译生成的,建议编译uboot后,再进行分析。最重要的目录:board/freescale/mx6ullevk,这是一个板级文件,不同的平台各自不同。configs/xxx_defconfig,,xxx 表示开发板名字,这是uboot的默认配置文件。不同的平台根据自己来配置uboot,会生成一个.config,是根据这个文件配置的结果,makefile根据.config来选择将那些模块源码编译径uboot(内核其实也是这么操作的)。.cmd 结尾的文件,_uboot顶层makefile

Linux和Ubuntu的区别与联系-程序员宅基地

Linux和Ubuntu :大家经常会说Linux操作系统,Ubuntu操作系统,这种叫法是不正确的,严格意义上讲,Linux并不是操作系统,而是属于操作系统的一个内核。由于Linux是开源的,免费的,所以程序员可以根据自己的兴趣和灵感对其进行改变,组合成自己想要的操作系统; 以Linux为内核的操作系统很多,我们称这种操作系统..._linux与ubuntu的区别与联系

《惢客创业日记》2019.11.26(周二) 橄榄枝的意义(二)-程序员宅基地

  今天,继续把昨天没有分享完的日记写完,为了能更好的、更真实的记录我的创业日记,经过再三思考后,我还是决定把这封信收藏到日记中,不过,其中有些涉及到隐私的词句我还会做一些删除和修改。下面就是正文的内容:  您好,今天早晨7点28分我收到凉粉儿的消息,说您给予了惢客及我本人这段时间来努力的肯定以及表达支持的意愿,听完后,我非常的激动,对于我来说,是一种莫大的鼓励和鞭策。在表达感谢之余...

Android学习笔记整理(13)--数据存储方式之SharedPreferences的使用-程序员宅基地

1.SharedPreferences的概念SharedPreferences是Android平台上一个轻量级的存储类,主要存储一些应用程序的配置参数,例如用户名、密码、自定义参数的设置等。SharedPreferences中存储的数据是以key/value键值对的形式保存在XML文件中,该文件位于data/data/&lt;packagename&gt;/shared_prefs文件夹中。注意:...

css3立方体3d旋转,css3 3D旋转正方体_Postroggy的博客-程序员宅基地

再来一个3D的案例,本实例自谷歌,技术自然不用说了,那是一级棒。个人能力有限,我就挑简单的分析下呵呵。说下这个正方体的实现吧,先设置父级元素的transform-style:preserve-3d,然后定义各个面的宽高及绝对定位并设置scale3D为1.2倍,最后就是把各个面分别运用rotateX,rotateY,再运用translateZ拉开距离。至于旋转的运动则是设置父元素的animation..._css3 立方体旋转时不是正方体

推荐文章

热门文章

相关标签