工厂方法模式-日志工厂_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

智能推荐

python中的np.empty_python – np.empty,np.zeros和np.ones的性能-程序员宅基地

文章浏览阅读1.6k次。我很好奇它使用np.empty而不是np.zeros实际上有多大差异,还有关于np.ones的差异.我运行这个小脚本来测试每个创建一个大型数组所需的时间:import numpy as npfrom timeit import timeitN = 10_000_000dtypes = [np.int8, np.int16, np.int32, np.int64,np.uint8, np.uint1..._np.empty((src.shape[0],src.shape[1],3),dtype=np.uint8)

python网络编程基础框架和例子_python网络编程基础案例-程序员宅基地

文章浏览阅读146次。socket网络编程举例目录socket网络编程举例一、TCP通信例子1、服务器端2、客户端二、UDP通信1、服务器端2、客户端三、多线程文件上传1、服务器端2、客户端一、TCP通信例子1、服务器端socket框架:1.建立一个socket(),可以选择socket的标记(INET,INET6,UNIX),类型(TCP,UDP等)2.绑定ip和端口bind(),让客户端可以连接过来3..._python网络编程基础案例

使用ndk-gdb调试android native程序_gdb ndk-程序员宅基地

文章浏览阅读7.7k次。#1.配置目标程序1. C++代码必须使用`ndk-build`编译,传入参数`NDK_DEBUG=1`。编译完成后,会在lib目录下生成gdbserver,供后续调试使用。2. 设置AndroidManifest.xml,在**application**项下面设置`android:debuggable="true"_gdb ndk

状态模式(State Pattern) —— 让你的对象学会72变-程序员宅基地

文章浏览阅读141次。当一个对象内在状态改变时允许其改变行为,这个对象看起来像改变了其类。一、怎么理解一个对象有多个状态?一个对象和多个状态关联,每种状态又对应一种行为,也就是同一个对象会因为状态不同让你觉得这是不是同一个类。比如,手机的HOME键:关机状态: 没有反应。 开机后首次启动: 密码解锁。 非首次启动: 密码解锁或者指纹解锁。 启动后:返回主页面。这里因为手机状态的不同,HOME键就有不同的功能或者行为。同样是HOME键给人们的感觉好像是好多的按键,好像不再是同一个类。也就是说对.

数据挖掘 (一)——ASC文件读写、时间戳转换、可执行文件打包_base hex timestamps absolute-程序员宅基地

文章浏览阅读7.3k次,点赞5次,收藏40次。一、时间戳介绍云平台上的数据通常以timestamp为时间戳,现在有个需求,需要将timestamp时间转换成datetime时间TimesTamp,一个能表示一份数据在某个特定时间之前已经存在的、 完整的、 可验证的数据,通常是一个字符序列,唯一地标识某一刻的时间。时间戳是指格林威治时间1970年01月01日00时00分00秒(北京时间1970年01月01日08时00分00秒)起至现在..._base hex timestamps absolute

vue中设置页面切换时的过渡动画_vue-owesome-swiper最后一页到第一页过渡-程序员宅基地

文章浏览阅读2.4k次。<template> <div id="app"> <!-- 页面切换动画transitionName --> <transition :name="transitionName"> <!-- 缓存数据 <router-view> --> <keep-alive> <router-view v-if="$route.meta.keepAlive">&l._vue-owesome-swiper最后一页到第一页过渡

随便推点

jetpack之databinding_databinding must be created in view's ui thread-程序员宅基地

文章浏览阅读1.2k次。databinding的源码记录_databinding must be created in view's ui thread

数学分析教程史济怀练习15.4_数学分析教程史济怀15.4-程序员宅基地

文章浏览阅读417次。_数学分析教程史济怀15.4

u-boot分析之makefile分析(二)_u-boot makefile libs += fs/cramfs/libcramfs.a fs/f-程序员宅基地

文章浏览阅读291次。目录u-boot(二)makefile引入 目录结构(1.1.6) 配置文件 目标 配置具体的单板 编译阶段 过程 链接入口 配置链接地址 附录 附录A:mkconfig解析 附录B 链接脚本u-boot(二)makefile引入如果要分析uboot结构以及如何链接的话,最好的方法就是去分析它的makefile。我们是怎么编译的?先执行配置make 10..._u-boot makefile libs += fs/cramfs/libcramfs.a fs/fat/libfat.a fs/fdos/libfdo

opencv实现基于边缘的形状匹配算法_opencv cvfastarctan-程序员宅基地

文章浏览阅读2.1w次,点赞24次,收藏206次。1.参考资料https://www.codeproject.com/Articles/99457/Edge-Based-Template-Matching用opencv编写的形状匹配算法,但不具旋转和缩放功能。著名机器视觉软件Halcon 的开发人员出版的一本书2.Machine Vision Algorithms and Applications [Carsten Steger, ..._opencv cvfastarctan

python 视频播放器 ffmpeg_Python3利用ffmpeg针对视频进行一些操作-程序员宅基地

文章浏览阅读1.6k次。FFmpeg是个啥?FFmpeg是一套可以用来记录、转换数字音频、视频,并能将其转化为流的开源计算机程序。采用LGPL或GPL许可证。它提供了录制、转换以及流化音视频的完整解决方案。解压缩后,配置一下环境变量输入命令查看版本ffmpeg -version安装ffmpeg的python扩展,该扩展可以让你直接在python脚本中直接调用,而不需要单独运行命令pip install ffmpeg-py..._ffmpeg-python 显示视频

Android10 FFmpeg开发案例之实现一个简易视频编辑器_android 视频剪辑csdn-程序员宅基地

文章浏览阅读1.6k次。Android里面开发视频播放器的例子很多,但FFmpeg无疑是最为强大而且最多人使用的音视频编解码库,所以,可以这么说,FFmpeg你必须学会使用。下面大部分是收集的,整合一下,感觉很重要,所以拿过来了,至于更加细致的FFmpeg用法,你可以看FFmpeg手册,help命令,或者网上搜一搜,基本上你看到的网上各种音视频流媒体的处理,都离不开FFmpeg:FFmpeg库关键概述:libavutil是一个实用程序库,用于帮助进行多种媒体编程。它包含可移植的字符串函数、任意数生成器、额外的算术能力、数据_android 视频剪辑csdn

推荐文章

热门文章

相关标签