设计模式之适配器模式

转载请注明出处: https://blog.csdn.net/Seasons_in_your_sun/article/details/83415275

适配器模式的定义

        将一个类的接口转换成客户希望的另外一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。(Convert the interface of a class into another interface clients expect. Adapter lets classes work together that couldn’t ohterwise because of incompatible interfaces.)


适配器模式的通用类图

在这里插入图片描述
从UML图可以看出,适配器模式涉及到3个角色:
Target目标角色
该角色定义把其他类转换为哪种接口,也就是我们希望的接口。
Adaptee源角色
你想把谁转换成目标角色,这个“谁”就是源角色,在我们项目中,它是已经存在的、运行良好的类或对象,经过适配器角色的包装,它会成为一个崭新、靓丽的角色。
Adapter适配器角色
适配器模式的核心角色,其他两个角色通常都是已经存在的,而适配器角色是需要新建立的,它的职责非常简单:把源角色转换为目标角色,怎么转换?通过继承或类关联的方式。


各个角色的职责已经清楚了,接下来通过几个例子来加深我们的理解。

例子

1. 简单例子

       大多数理论都是来自生活的,适配器也不例外,当今生活中,手机已经成为了我们必不可少的工具,与我们生活息息相关,不知大家有没有注意到,我们在给手机充电时,充电头上就会有熟悉的五个字:电源适配器。是的,我们国家的电源标准电压是220v,而手机充电时只需要5v电压即可,过大会损坏手机,电源适配器就是把220v电压转换为5v电压。

       分析这个例子,不难得出,其中目标角色是5v电压,源角色是220v电压,适配器角色是电源适配器。接下来请看源码。

目标角色:target(手机充电需要5v电压):

package target;
public interface Voltage5 {
	int output5v();
}

源角色:adaptee(我们目前拥有220v电压):

package adaptee;
public class Voltage220 {
	private int src = 220;
	public int output220v() {
		System.out.println("我是" + src + "V");
		return src;
	}
}

适配器角色:adapter(将源220v电压转换成目标5v):

package adapter;
import adaptee.Voltage220;
import target.Voltage5;
// 继承源类,并实现目标接口
public class VoltageAdapter extends Voltage220 implements Voltage5{
	public int output5v() {
		// VoltageAdapter类继承自Voltage220,可以直接调用父类的方法output220v
		int src = super.output220v();
		System.out.println("适配器工作开始适配电压");
		// 开始适配
		int dst = src / 44;
		System.out.println("适配完成后输出电压:" + dst + "V");
		return dst;
	}
}

接下来,我们还需要一部手机,也就是我们的客户端:

package client;
import target.Voltage5;
public class Mobile {
	public void charging(Voltage5 vol) {
		if (5 == vol.output5v()) {
			System.out.println("充电电压为5V,开始充电");
		} else {
			System.out.println("充电电压不满足,无法充电");
		}
	}
}

现在,我们就可以测试是否能够给手机正常充电了,测试代码:

import client.Mobile;
import adapter.VoltageAdapter;
public class main {
	public static void main(String[] args) {
		VoltageAdapter voltageAdapter = new VoltageAdapter();
		Mobile mobile = new Mobile();
		mobile.charging(voltageAdapter);
	}
}

输出:

我是220V
适配器工作开始适配电压
适配完成后输出电压:5V
充电电压为5V,开始充电

是的,我们的手机在电源适配器的转换下,已经能够正常充电了,再也不用担心手机电用完了。

类图如下:
在这里插入图片描述

       不知道大家发现没有,上一个例子中,适配器VoltageAdapter是通过继承源角色Voltage220来实现适配的,这样通过继承来实现适配的方式,我们称之为类适配器,由于java单继承机制,在使用类适配器时,就必须要求目标角色Voltage5必须是接口,有一定局限性。

一句话描述类适配器:
       Adapter类,通过继承 Adaptee类,实现 Target 类接口,完成Adaptee->Target的适配。


实际上,适配器有两种模式,类适配器对象适配器,而对象适配器可以通过组合的方式来解决类适配器需要继承的问题,接下来,根据上面的例子做一些改动,来看看对象适配器的工作方式。


2. 将简单例子进行修改(类适配器修改为对象适配器)

目标角色:target(手机充电需要5v电压,与类适配器代码一致):

package target;
public interface Voltage5 {
	int output5v();
}

源角色:adaptee(我们目前拥有220v电压,与类适配器代码一致):

package adaptee;
public class Voltage220 {
	private int src = 220;
	public int output220v() {
		System.out.println("我是" + src + "V");
		return src;
	}
}

适配器角色:adapter(将源220v电压转换成目标5v):

package adapter;
import target.Voltage5;
import adaptee.Voltage220;
public class VoltageAdapter2 implements Voltage5 { // 与类适配器不同之处,不继承Voltage220类
	private Voltage220 mVoltage220;// 持有Voltage220类的引用
	public VoltageAdapter2(Voltage220 voltage220) {
		this.mVoltage220 = voltage220;// 在构造函数中赋值,拥有Voltage220类的对象
	}
	public int output5v() {
		int dst = 0;
		if (null != mVoltage220) {
			int src = mVoltage220.output220v(); // 调用对象的方法
			System.out.println("对象适配器工作,开始适配电压");
			dst = src / 44; // 开始适配
			System.out.println("适配完成,输出电压:" + dst + "V");
		}
		return dst;
	}
}

同样的,我们还需要一部手机,客户端(需要5v电压充电,与类适配器代码一致):

package client;
import target.Voltage5;
public class Mobile {
	public void charging(Voltage5 vol) {
		if (5 == vol.output5v()) {
			System.out.println("充电电压为5V,开始充电");
		} else {
			System.out.println("充电电压不满足,无法充电");
		}
	}
}

现在,测试一下代码改动过后,是否还能正常充电,测试代码:

import client.Mobile;
import adapter.VoltageAdapter2;
import adaptee.Voltage220;
public class main {
	public static void main(String[] args) {
		VoltageAdapter2 voltageAdapter2 = new VoltageAdapter2(new Voltage220());
		Mobile mobile = new Mobile();
		mobile.charging(voltageAdapter2);
	}
}

输出:

我是220V
适配器工作开始适配电压
适配完成后输出电压:5V
充电电压为5V,开始充电

改用对象适配器模式过后,手机照常能充电,说明适配正常。

同样的,我们可以画出这个例子的类图:
在这里插入图片描述

一句话描述对象适配器:
       Adapter类,通过持有 Adaptee类实例,实现 Target 类接口,完成Adaptee->Target的适配。


结合这两个例子可以看出,类适配器和对象适配器唯一的差别就在于类适配器继承自Adaptee,而对象适配器利用组合的方式来实现适配的效果。


接下来,是一个更接近实际的例子。
3. 人力资源管理的例子

       现在这个社会,单打独斗已经不现实了,任何伟大的公司都是由无数伟大的团队组成的,那么公司员工的也是需要管理起来的,假设一个简单的人员管理项目,分为三大模块:人员信息管理、薪酬管理、职位管理。
       基于以上需求,我们可以设计一个UserInfo类来存储用户的所有信息(实际系统上还有很多子类,这里为了简单,不多展开说明),类图可以如下设计:
在这里插入图片描述
UserInfo这个类可能会在系统中很多地方使用,当然这个类是有setter方法的,我们这里用不到就隐藏了,先来看看代码实现

员工信息接口:

package user;
public interface IUserInfo {
	public String getUserName();// 获取用户姓名
	public String getHomeAddress(); // 获取家庭地址
	public String getMobileNumber(); // 获取手机号
	public String getOfficeTelNumber(); // 获取办公电话
	public String getHomeTelNumber(); // 获取家庭电话
}

实现类:

package user;
public class UserInfo implements IUserInfo{
	public String getUserName() {
		System.out.println("员工姓名叫做...");
		return null;
	}
	public String getHomeAddress() {
		System.out.println("员工的家庭地址是...");
		return null;
	}
	public String getMobileNumber() {
		System.out.println("员工的手机号码是...");
		return null;
	}
	public String getOfficeTelNumber() {
		System.out.println("办公室电话是...");
		return null;
	}
	public String getHomeTelNumber() {
		System.out.println("家庭电话是...");
		return null;
	}
}

使用场景类:

import user.IUserInfo;
import user.UserInfo;
public class main {
	public static void main(String[] args) {
		IUserInfo userInfo = new UserInfo();
		userInfo.getUserName();
	}
}

现在这个项目就能正常稳定的运行了,可是忽然有一天,你的公司开始使用借聘人员的方式引进员工,也就是从劳动资源公司借用了一大批的低技术、低工资的人员,这时候,就需要增加一个功能:借聘人员的管理。但是,借聘人员的人员信息、工资情况、福利情况等都是由劳动服务公司管理的,并且有一套自己的人员管理系统,我们就需要同步劳动服务公司的信息同步到我们的系统里,但劳动服务公司人员对象和我们系统的对象不相同,他们的对象类图如下:
在这里插入图片描述
从类图中可以看出,劳动服务公司把人员信息分成了三部分:基本信息、办公信息和个人家庭信息,并且都放到了HashMap中,我们来看看他们的代码。

劳动服务公司人员信息接口:

public interface IOuterUser {
	public Map getUserBaseInfo();// 基本信息,比如名称、性别、手机号码等
	public Map getUserOfficeInfo();// 工作区域信息
	public Map getUserHomeInfo();// 用户的家庭信息
}

劳动服务公司人员实现类:

package outeruser;
import java.util.HashMap;
import java.util.Map;
public class OuterUser {
	public Map getUserBaseInfo() {
		// <String, String>是Java中的泛型,表示key,value都以String类型存取
		HashMap<String, String> baseInfoMap = new HashMap<String, String>();
		baseInfoMap.put("userName", "员工姓名是...");
		baseInfoMap.put("mobileNumber", "电话是...");
		return baseInfoMap;
	}
	public Map getUserHomeInfo() {
		HashMap<String, String> homeInfo = new HashMap<String, String>();
		homeInfo.put("homeTelNumber", "家庭电话是...");
		homeInfo.put("homeAddress", "家庭地址是...");
		return homeInfo;
	}
	public Map getUserOfficeInfo() {
		HashMap<String, String> officeInfo = new HashMap<String, String>();
		officeInfo.put("officeTelNumber", "办公电话是...");
		return officeInfo;
	}
}

看到这里,咱不好说他们的系统设计得不好,问题是咱的系统要和他们的系统进行交互,怎么办呢?我们不可能为了这一小小的功能而对我们已经运行良好的系统进行大手术,那怎么办?我们可以转化,先拿到对方的数据对象,然后转化为我们自己的数据对象,中间加一层转换处理,按照这个思路,我们可以设计如下类图:
在这里插入图片描述
在这里,OuterUserInfo可以看作是“两面派”,实现了IUserInfo接口,还继承了OuterUser,通过这样的设计,把OuterUser伪装成我们系统中的一个IUserInfo对象,这样,我们的系统基本不用修改,所有人员的查询、调用跟本地一样。
中转角色OuterUserInfo:

package adapter;
import outeruser.OuterUser;
import user.IUserInfo;
import java.util.Map;
public class OuterUserInfo extends OuterUser implements IUserInfo {
	private Map<String, String> baseInfo = super.getUserBaseInfo();// 调用父类方法得到用户基本信息的HashMap
	private Map<String, String> homeInfo = super.getUserHomeInfo();
	private Map<String, String> officeInfo = super.getUserOfficeInfo();
	@Override
	public String getUserName() {
		String userName = this.baseInfo.get("userName");// 通过HashMap的get方法获取到key对应value值
		System.out.println(userName);
		return userName;
	}
	@Override public String getHomeAddress() {...}
	@Override public String getMobileNumber() {...}
	@Override public String getOfficeTelNumber() {...}
	@Override public String getHomeTelNumber() {...}
}

这时,如果我们需要查看借聘人员信息,只需要修改场景类部分代码。
修改后的场景类:

import user.IUserInfo;
import adapter.OuterUserInfo ;

public class main {
	public static void main(String[] args) {
		// 我们只修改了这行代码
		IUserInfo userInfo = new OuterUserInfo ();
		userInfo.getUserName();
	}
}

大家看,使用了适配器模式只修改了一句代码,其他的业务逻辑都不用修改就解决了系统对接的问题,而且在我们实际系统中只是增加了一个业务类的继承,就实现了可以查本公司的员工信息,也可以查询劳动服务公司的员工信息,尽量少的修改,通过扩展的方式解决了该问题,这就是适配器模式。

总结及应用

1. 适配器模式的优点
  • 适配器模式可以让两个没有关系的类在一起运行,只要适配器这个角色能够搞定他们就成
  • 增加了类的透明性
    想想看,我们访问的Target目标角色,但是具体的实现都委托给了源角色,而这些对高层次模块是透明的,也是它不需要关心的。
  • 提高了类的复用度
    当然了,源角色在原有系统还是可以正常使用,而在目标角色中也可以充当新的演员。
  • 灵活性非常好
    某一天,突然不想要适配器,没问题,删除掉这个适配器就可以了,其他的代码都不用修改,基本上就类似一个灵活的构件,想用就用,不想就卸载。
2. 适配器模式的使用场景

       适配器应用的场景只要记住一点就够了:你有动机修改一个已经投产中的接口时,适配器模式可能是最适合你的模式,比如系统扩展了,需要使用一个已有或新建立的类,但这个类又不符合系统的接口,怎么办?使用适配器模式。

3. 适配器模式的注意事项

       适配模式是一种补偿模式,或者说是一个“补救”模式,最好在详细设计阶段不要考虑它,它不是为了解决还处在开发阶段的问题,而是解决正在服役的项目问题,这个模式使用的主要场景是扩展应用中,系统扩展了,不符合原有设计的时候才考虑通过适配器模式减少代码修改带来的风险。同时,项目一定要遵守依赖倒置原则和里氏替换原则,否则即使在适合使用适配器的场合下,也会带来非常大的改造。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

AlwaysWM丶

你的鼓励是我最大的动力

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

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

打赏作者

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

抵扣说明:

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

余额充值