技术标签: 设计模式
一个软件,不管是本地开发调试,还是线上生产运行,日志的收集都是非常重要的。如何设计一个灵活的日志系统呢?
将日志的功能抽象出来,封装成接口:
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("哈哈");
}
}
后面如果加了新的日志实现,只需要再扩展一个工厂类就好了。
这就是工厂方法模式。
定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。
工厂方法模式,封装了对象创建的过程,对外屏蔽了复杂对象的创建细节,而且降低了类间的耦合。客户端只依赖产品的抽象,无需知道产品实现,让产品工厂来完成对象的创建。
工厂方法模式是new
关键字的替代品,它提供了创建对象的一种绝佳的方式,任何需要创建对象的地方都可以考虑使用该模式。缺点是需要增加工厂类,代码复杂度会提高,需要结合实际情况考虑。
如果一个模块很简单,只需要一个工厂类,就可以降级为简单工厂,使用静态方法来创建对象。
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;
}
}
}
单例模式本身既要负责业务逻辑,又要保证单例,不符合单一职责原则,可以使用工厂模式来替代单例模式,由工厂类来保证单例,而不是单例类本身。
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;
}
}
对于可以复用的对象,工厂类可以将实例缓存起来,避免频繁的创建和销毁。这在很大程度上可以降低系统的性能开销,减轻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);
}
}
工厂方法模式在项目中使用的非常频繁,很多开源框架都能看到它的影子,它提供了对象创建的一种绝佳方式,凡是需要创建对象的地方,都可以考虑使用工厂方法模式。
文章浏览阅读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)
文章浏览阅读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网络编程基础案例
文章浏览阅读7.7k次。#1.配置目标程序1. C++代码必须使用`ndk-build`编译,传入参数`NDK_DEBUG=1`。编译完成后,会在lib目录下生成gdbserver,供后续调试使用。2. 设置AndroidManifest.xml,在**application**项下面设置`android:debuggable="true"_gdb ndk
文章浏览阅读141次。当一个对象内在状态改变时允许其改变行为,这个对象看起来像改变了其类。一、怎么理解一个对象有多个状态?一个对象和多个状态关联,每种状态又对应一种行为,也就是同一个对象会因为状态不同让你觉得这是不是同一个类。比如,手机的HOME键:关机状态: 没有反应。 开机后首次启动: 密码解锁。 非首次启动: 密码解锁或者指纹解锁。 启动后:返回主页面。这里因为手机状态的不同,HOME键就有不同的功能或者行为。同样是HOME键给人们的感觉好像是好多的按键,好像不再是同一个类。也就是说对.
文章浏览阅读7.3k次,点赞5次,收藏40次。一、时间戳介绍云平台上的数据通常以timestamp为时间戳,现在有个需求,需要将timestamp时间转换成datetime时间TimesTamp,一个能表示一份数据在某个特定时间之前已经存在的、 完整的、 可验证的数据,通常是一个字符序列,唯一地标识某一刻的时间。时间戳是指格林威治时间1970年01月01日00时00分00秒(北京时间1970年01月01日08时00分00秒)起至现在..._base hex timestamps absolute
文章浏览阅读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最后一页到第一页过渡
文章浏览阅读1.2k次。databinding的源码记录_databinding must be created in view's ui thread
文章浏览阅读417次。_数学分析教程史济怀15.4
文章浏览阅读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
文章浏览阅读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
文章浏览阅读1.6k次。FFmpeg是个啥?FFmpeg是一套可以用来记录、转换数字音频、视频,并能将其转化为流的开源计算机程序。采用LGPL或GPL许可证。它提供了录制、转换以及流化音视频的完整解决方案。解压缩后,配置一下环境变量输入命令查看版本ffmpeg -version安装ffmpeg的python扩展,该扩展可以让你直接在python脚本中直接调用,而不需要单独运行命令pip install ffmpeg-py..._ffmpeg-python 显示视频
文章浏览阅读1.6k次。Android里面开发视频播放器的例子很多,但FFmpeg无疑是最为强大而且最多人使用的音视频编解码库,所以,可以这么说,FFmpeg你必须学会使用。下面大部分是收集的,整合一下,感觉很重要,所以拿过来了,至于更加细致的FFmpeg用法,你可以看FFmpeg手册,help命令,或者网上搜一搜,基本上你看到的网上各种音视频流媒体的处理,都离不开FFmpeg:FFmpeg库关键概述:libavutil是一个实用程序库,用于帮助进行多种媒体编程。它包含可移植的字符串函数、任意数生成器、额外的算术能力、数据_android 视频剪辑csdn