适配器模式-日志适配器

1. 日志适配器

在写「策略模式」的时候,笔者就用「日志」举了例子,这篇文章还是要以日志为例。
日志的重要性笔者这里不再多言了,大家在系统开发中记录日志的时候,有没有考虑过这样一个问题:万一使用的日志框架要更换呢?
一旦更换你需要怎么做?修改所有的代码,替换Logger类吗?如果这么做的话,不管是对系统,还是对开发者来说,都是个灾难。有没有一种更优雅的解决方式呢?

在系统设计之初,开发者就需要考虑到这一点:封装日志抽象,为第三方日志框架做适配。防止日后更换日志框架时带来的灾难。

我不管第三方日志框架是如何实现的,我只需要定义我们系统日志需要具备哪些功能即可,如果要引入三方库,就把它适配成我们系统需要的。

事实上,SLF4J就是这么做的,它本身并不提供日志功能,它是一个「简单日志门面」,只是将三方库适配成了它定义的日志接口。本篇不会讨论SLF4J的实现。

现在假设系统可能需要三种日志实现:JDK日志、Log4j、Logback,我们试着用代码来描述这个适配的过程,类图设计如下:
在这里插入图片描述
编写Logger接口,定义日志具备的功能:

public interface Logger {

	void trace(String message);

	void debug(String message);

	void info(String message);

	void warn(String message);

	void error(String message, Throwable t);
}

JdkLoggerAdapter适配JDK日志:

public class JdkLoggerAdapter implements Logger{
	private java.util.logging.Logger logger;

	public JdkLoggerAdapter(java.util.logging.Logger logger) {
		this.logger = logger;
	}

	@Override
	public void trace(String message) {
		logger.log(Level.FINEST, message);
	}

	@Override
	public void debug(String message) {
		logger.log(Level.CONFIG, message);
	}

	@Override
	public void info(String message) {
		logger.log(Level.INFO, message);
	}

	@Override
	public void warn(String message) {
		logger.log(Level.WARNING, message);
	}

	@Override
	public void error(String message, Throwable t) {
		logger.log(Level.SEVERE, message, t);
	}
}

Log4jLoggerAdapter适配Log4j日志:

public class Log4jLoggerAdapter implements Logger{
	private org.apache.log4j.Logger logger;

	public Log4jLoggerAdapter(org.apache.log4j.Logger logger) {
		this.logger = logger;
	}

	@Override
	public void trace(String message) {
		logger.trace(message);
	}

	@Override
	public void debug(String message) {
		logger.debug(message);
	}

	@Override
	public void info(String message) {
		logger.info(message);
	}

	@Override
	public void warn(String message) {
		logger.warn(message);
	}

	@Override
	public void error(String message, Throwable t) {
		logger.error(message, t);
	}
}

LogbackLoggerAdapter适配Logback日志:

public class LogbackLoggerAdapter implements Logger{
	private ch.qos.logback.classic.Logger logger;

	public LogbackLoggerAdapter(ch.qos.logback.classic.Logger logger) {
		this.logger = logger;
	}

	@Override
	public void trace(String message) {
		logger.trace(message);
	}

	@Override
	public void debug(String message) {
		logger.debug(message);
	}

	@Override
	public void info(String message) {
		logger.info(message);
	}

	@Override
	public void warn(String message) {
		logger.warn(message);
	}

	@Override
	public void error(String message, Throwable t) {
		logger.error(message, t);
	}
}

定义日志枚举:

public enum LogEnum {
	LOG4J,
	LOGBACK,
	JDK;
}

如何优雅的生成日志对象呢?当然是日志工厂了。

public class LoggerFactory {
	private static LogEnum logEnum;

	public static void setLogEnum(LogEnum logEnum) {
		LoggerFactory.logEnum = logEnum;
	}

	public static Logger getLogger(Class<?> c) {
		switch (logEnum) {
			case JDK:
				return new JdkLoggerAdapter(java.util.logging.Logger.getLogger(c.getName()));
			case LOG4J:
				return new Log4jLoggerAdapter(org.apache.log4j.Logger.getLogger(c));
			case LOGBACK:
				return new LogbackLoggerAdapter(new LoggerContext().getLogger(c));
			default:
				return null;
		}
	}
}

客户端这样调用:

public class Client {
	public static void main(String[] args) throws Exception {
		LoggerFactory.setLogEnum(LogEnum.JDK);
		Logger logger = LoggerFactory.getLogger(Client.class);
		logger.info("JDK 日志...");
	}
}

看到没,客户端完全不关心底层到底用的是什么日志框架,它只依赖日志抽象,只知道日志具备5个记录方法,至于到底是如何记录日志的,管我什么事呢?你只管把三方库适配成我想要的接口,我只负责调用。
这就是适配器模式!

2. 适配器模式的定义

将一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口不匹配而无法在一起工作的两个类能够在一起工作。

在这里插入图片描述

适配器模式通用类图
  • Target:目标接口,也就是适配器需要将其他接口转换成的期望接口。
  • Adapter:适配器角色,适配器模式的核心。
  • Adaptee:源角色,实际干活的人,适配器需要将其转换成目标接口。

适配器在生活中也很常见,例如:电源适配器,3.5mm转Type-C耳机线等。简单地说,适配器模式就是将接口或类转换成另一种接口或类。
如下图,两个不能一起工作的图形,通过一个适配器的加入可以协同工作了。
在这里插入图片描述

3. 适配器模式的优点

  1. 可以在不修改源码的情况下让两个原本不能协同工作的类一起工作,符合「开闭原则」。
  2. 对高层模块屏蔽底层实现,适配器对源对象进行了封装。
  3. 提高了类的复用性。
  4. 非常灵活,适配器对现有代码无侵入性,现有对象无需知道适配器的存在,哪天不想用了直接删掉即可。

只要你想修改一个已经上线运行的接口,你就可以考虑使用「适配器模式」。

4. 总结

「适配器模式」是一种补救方案,针对已经上线的接口,如果修改源码风险很大,则可以考虑使用适配器模式来进行调整,在系统设计之初请忘记它的存在吧!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
1. 单例模式 单例模式是一种创建型设计模式,用于确保类只有一个实例,并提供全局访问点。 ```java public class Singleton { private static Singleton instance; //私有化静态对象 private Singleton(){} //私有化构造方法 public static Singleton getInstance(){ if(instance == null){ instance = new Singleton(); } return instance; } } ``` 解释:Singleton类只有一个私有的静态实例对象instance,构造方法也是私有的,提供了一个getInstance()方法用于获取该实例,若实例不存在则创建一个新的实例,否则直接返回该实例。 2. 工厂模式 工厂模式是一种创建型设计模式,用于将对象的创建和使用分离开来。 ```java public interface Shape { void draw(); } public class Circle implements Shape { @Override public void draw() { System.out.println("Circle.draw()"); } } public class Square implements Shape { @Override public void draw() { System.out.println("Square.draw()"); } } public class ShapeFactory { public Shape getShape(String shapeType){ if(shapeType == null){ return null; } if(shapeType.equalsIgnoreCase("CIRCLE")){ return new Circle(); } else if(shapeType.equalsIgnoreCase("SQUARE")){ return new Square(); } return null; } } ``` 解释:Shape是一个接口,Circle和Square都实现了该接口,并实现了draw()方法。ShapeFactory是一个工厂类,根据传入的参数shapeType来创建对应的对象。 3. 观察者模式 观察者模式是一种行为型设计模式,用于在对象间定义一种一对多的依赖关系,使得一个对象状态的改变能够自动通知并更新其他对象。 ```java public interface Observer { void update(String message); } public interface Subject { void registerObserver(Observer observer); void removeObserver(Observer observer); void notifyObservers(); } public class MessageBoard implements Subject { List<Observer> observers = new ArrayList<>(); @Override public void registerObserver(Observer observer) { observers.add(observer); } @Override public void removeObserver(Observer observer) { observers.remove(observer); } @Override public void notifyObservers() { for (Observer observer : observers) { observer.update("New message on the board!"); } } } public class User implements Observer { String name; public User(String name) { this.name = name; } @Override public void update(String message) { System.out.println(name + " got the message: " + message); } } ``` 解释:Observer是观察者接口,定义了update()方法;Subject是主题接口,定义了注册、删除和通知观察者的方法;MessageBoard是一个具体主题类,维护了一个观察者列表,实现了Subject接口;User是观察者类,实现了Observer接口,具有更新方法。 4. MVC模式 MVC模式是一种架构设计模式,用于将应用程序分为三个主要部分:模型、视图和控制器。 ```java public class Model { private int value; public int getValue() { return value; } public void setValue(int value) { this.value = value; } } public class View { public void display(int value){ System.out.println("Value: " + value); } } public class Controller { private Model model; private View view; public Controller(Model model, View view) { this.model = model; this.view = view; } public void setValue(int value){ model.setValue(value); view.display(model.getValue()); } } ``` 解释:Model是数据模型,定义了数据的行为;View是用户界面,显示数据和接收用户输入;Controller是控制器,根据用户输入来更新模型并更新视图。 5. 装饰器模式 装饰器模式是一种结构型设计模式,用于动态地将责任附加到对象上。 ```java public interface Shape { void draw(); } public class Circle implements Shape { @Override public void draw() { System.out.println("Circle.draw()"); } } public abstract class ShapeDecorator implements Shape { protected Shape decoratedShape; public ShapeDecorator(Shape decoratedShape) { this.decoratedShape = decoratedShape; } public void draw(){ decoratedShape.draw(); } } public class RedShapeDecorator extends ShapeDecorator { public RedShapeDecorator(Shape decoratedShape) { super(decoratedShape); } public void draw() { decoratedShape.draw(); setColor(decoratedShape); } private void setColor(Shape decoratedShape){ System.out.println("Color: Red"); } } ``` 解释:Shape是一个接口,Circle实现了该接口并实现了draw()方法;ShapeDecorator是一个装饰器抽象类,维护了一个Shape对象,实现了Shape接口并定义了draw()方法;RedShapeDecorator是一个具体装饰器类,扩展了ShapeDecorator,添加了setColor()方法来为Shape对象添加颜色。 6. 适配器模式 适配器模式是一种结构型设计模式,用于将一个类的接口转换为另一个接口,以便于在不同类之间进行交互。 ```java public interface MediaPlayer { void play(String audioType, String fileName); } public interface AdvancedMediaPlayer { void playVlc(String fileName); void playMp4(String fileName); } public class VlcPlayer implements AdvancedMediaPlayer { @Override public void playVlc(String fileName) { System.out.println("Playing vlc file. Name: " + fileName); } @Override public void playMp4(String fileName) { // Do nothing } } public class Mp4Player implements AdvancedMediaPlayer{ @Override public void playVlc(String fileName) { // Do nothing } @Override public void playMp4(String fileName) { System.out.println("Playing mp4 file. Name: "+ fileName); } } public class MediaAdapter implements MediaPlayer { AdvancedMediaPlayer advancedMediaPlayer; public MediaAdapter(String audioType){ if(audioType.equalsIgnoreCase("vlc") ){ advancedMediaPlayer = new VlcPlayer(); } else if (audioType.equalsIgnoreCase("mp4")){ advancedMediaPlayer = new Mp4Player(); } } public void play(String audioType, String fileName) { if(audioType.equalsIgnoreCase("vlc")){ advancedMediaPlayer.playVlc(fileName); }else if(audioType.equalsIgnoreCase("mp4")){ advancedMediaPlayer.playMp4(fileName); } } } public class AudioPlayer implements MediaPlayer { MediaAdapter mediaAdapter; public void play(String audioType, String fileName) { if(audioType.equalsIgnoreCase("mp3")){ System.out.println("Playing mp3 file. Name: " + fileName); }else if(audioType.equalsIgnoreCase("vlc") || audioType.equalsIgnoreCase("mp4")){ mediaAdapter = new MediaAdapter(audioType); mediaAdapter.play(audioType, fileName); }else{ System.out.println("Invalid media. " + audioType + " format not supported"); } } } ``` 解释:MediaPlayer是一个接口,定义了play()方法;AdvancedMediaPlayer是另一个接口,定义了播放不同格式音频的方法;VlcPlayer和Mp4Player都实现了AdvancedMediaPlayer接口;MediaAdapter是一个适配器类,实现了MediaPlayer接口并包装了AdvancedMediaPlayer对象;AudioPlayer是另一个类,实现了MediaPlayer接口并根据不同的音频格式选择不同的播放方式。 7. 命令模式 命令模式是一种行为型设计模式,用于将请求封装为对象,使得可以用不同的请求来参数化客户端,将请求排队或记录请求日志,以及支持可撤销的操作。 ```java public interface Command { void execute(); } public class Light { public void on(){ System.out.println("Light is on"); } public void off(){ System.out.println("Light is off"); } } public class LightOnCommand implements Command { Light light; public LightOnCommand(Light light){ this.light = light; } public void execute(){ light.on(); } } public class LightOffCommand implements Command { Light light; public LightOffCommand(Light light){ this.light = light; } public void execute(){ light.off(); } } public class RemoteControl { Command[] onCommands; Command[] offCommands; public RemoteControl(){ onCommands = new Command[7]; offCommands = new Command[7]; for (int i = 0; i < 7; i++){ onCommands[i] = () -> {}; offCommands[i] = () -> {}; } } public void setCommand(int slot, Command onCommand, Command offCommand){ onCommands[slot] = onCommand; offCommands[slot] = offCommand; } public void onButtonPressed(int slot){ onCommands[slot].execute(); } public void offButtonPressed(int slot){ offCommands[slot].execute(); } } ``` 解释:Command是一个接口,定义了execute()方法;Light是一个具体类,定义了开灯和关灯的方法;LightOnCommand和LightOffCommand都实现了Command接口,并将Light对象作为构造函数的参数;RemoteControl是一个遥控器类,维护了一个开灯和关灯的命令列表,并提供了绑定命令和执行命令的方法。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员小潘

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值