适配器模式
说到适配器,我们现实生活中到处都是适配器,其中最容易被大家提起的就是电源适配器。
比如墙上只有一个三孔的插座,而你的手机充电器是两孔的,这个时候你需要一个,一边是三脚,一边提供两个孔的适配器,来做一次插座接口转换。
实际上,我们常使用的插线板就有这个功能,所以某些场景下它就是适配器。
不兼容的API升级
那么回到面向对象中来,假设现在你手上已有一个软件系统,集成了厂商的类;现在需要升级厂商到新的类,但是厂商新类提供的API新旧不兼容,如果你不想修改已有的代码,来解决掉这个升级问题?
审视下当前系统有些什么
首先有厂商相关的类
/**
* 原始的厂商API
*/
public interface OldVendorApi {
void doSomething();
}
/**
* 旧厂商实现
*/
public class OldVendorApiImplV1 implements OldVendorApi {
@Override
public void doSomething() {
System.out.println("执行旧厂商代码....");
}
}
有使用厂商类的代码
/**
* 使用厂商类的客户端代码
*/
public class YourSystemClient {
private OldVendorApi vendorAPI;
public YourSystemClient(OldVendorApi vendorAPI) {
this.vendorAPI = vendorAPI;
}
public void callVendorApi(){
System.out.println("调用厂商类api....");
vendorAPI.doSomething();
}
}
能够正常跑
/**
* 测试
*/
public class VendorMainV1 {
public static void main(String[] args) {
YourSystemClient systemClient = new YourSystemClient(new OldVendorApiImplV1());
systemClient.callVendorApi();
}
}
好了,如果现在要升级不兼容的新厂商接口(实际情况下,这种厂商还是很少的),新的api和实现如下
/**
* 新的厂商API
*/
public interface NewVendorApiV2 {
void doSomething2();
}
/**
* 新厂商实现
*/
public class NewVendorApiImplV2 implements NewVendorApiV2 {
@Override
public void doSomething2() {
System.out.println("执行新的厂商代码....");
}
}
我们不修改YourSystemClient类的情况下,可以使用适配器来完成这个升级。
新增适配器
/**
* 旧厂商接口适配器,实现旧的接口,实际功能调用到新的API上
*/
public class OldVendorApiAdapter implements OldVendorApi{
private NewVendorApiV2 newVendorApi;
public OldVendorApiAdapter(NewVendorApiV2 newVendorApi) {
this.newVendorApi = newVendorApi;
}
@Override
public void doSomething() {
newVendorApi.doSomething2();
}
}
让我们的客户端依赖新的适配器,代码如下
/**
* 测试
*/
public class VendorMainV2 {
public static void main(String[] args) {
OldVendorApiAdapter oldVendorApiAdapter = new OldVendorApiAdapter(new NewVendorApiImplV2());
YourSystemClient systemClient = new YourSystemClient(oldVendorApiAdapter);
systemClient.callVendorApi();
}
}
这样,通过传入适配器类,就实现了,不修改代码,通过增加代码来实现升级。
按照惯例,看下UML图
火鸡伪装鸭子
再看一个示例,看看如何让火鸡来伪装成一个鸭子,混到鸭子中去的。
首先我们先吧鸭子接口和实现,火鸡接口和实现提供出来。
/**
* 鸭子接口
*/
public interface Duck {
void quack();
void fly();
}
/**
* 绿头鸭
*/
public class MallardDuck implements Duck {
@Override
public void quack() {
System.out.println("呱呱呱的叫");
}
@Override
public void fly() {
System.out.println("飞30米...");
}
}
/**
* 火鸡接口
*/
public interface Turkey {
void gobble();
void fly();
}
/**
* 野生火鸡
*/
public class WildTurkey implements Turkey{
@Override
public void gobble() {
System.out.println("咕咕的叫");
}
@Override
public void fly() {
System.out.println("飞了10米");
}
}
接下来,使用一个适配器,通过它来将火鸡伪装成鸭子
/**
* 火鸡适配器-将火鸡适配成鸭子
*/
public class TurkeyAdapter implements Duck {
Turkey turkey;
public TurkeyAdapter(Turkey turkey) {
this.turkey = turkey;
}
@Override
public void quack() {
turkey.gobble();
}
@Override
public void fly() {
for (int i = 0; i < 3; i++) {
turkey.fly();
}
}
}
测试伪装
/**
* 测试
*/
public class DuckMain {
public static void main(String[] args) {
Duck duck = new MallardDuck();
duck.quack();
duck.fly();
System.out.println();
duck = new TurkeyAdapter(new WildTurkey());
duck.quack();
duck.fly();
}
}
看下UML图
定义
适配者模式将一个类的接口,装换为客户希望的另外一个接口,让原本不兼容的类可以合作无间。
我们用代码来实现一波
目标接口(客户希望的接口)
/**
* 目标接口
*/
public interface Target {
void request();
}
不能够直接兼容的被适配者
/**
* 被适配者
*/
public class Adaptee {
public void specialRequest(){
System.out.println("被适配者具体工作....");
}
}
做接口转换的适配器类
/**
* 适配器,实现目标接口,将请求转发给被适配者
*/
public class Adapter implements Target {
private Adaptee adaptee;
public Adapter(Adaptee adaptee) {
this.adaptee = adaptee;
}
@Override
public void request() {
adaptee.specialRequest();
}
}
客户端类
/**
* 客户
*/
public class Client {
private Target target;
public Client(Target target) {
this.target = target;
}
public void doSomething(){
target.request();
}
}
测试
/**
* 测试
*/
public class AdapterMain {
public static void main(String[] args) {
Adapter adapter = new Adapter(new Adaptee());
Client client = new Client(adapter);
client.doSomething();
}
}
可以看到,通过适配器类的接口转换和转发,让客户类通过目标接口,使用了原本不能兼容的被适配者类的能力。
看看UML图。
我们前面学到了装饰者模式,他们都是通过加一层包装来实现客户也具体工作类之间的解耦。
他们的区别是什么?
适配者模式是通过包装来提供接口转换,而装饰者通过包装来动态的提供新的能力,他们的意图不同。
扩展示例
https://www.c-sharpcorner.com/UploadFile/b7dc95/adapter-design-pattern-demystified/
这是一个只支持USB接口的笔记本电脑,要使用老的PS/2接口鼠标时,使用适配器的例子。
外观模式
你已经知道适配器模式是如何将一个接口转换为成另一个符合客户预期的接口,达到兼容的目的;
另一个装换接口的模式,其目的是为了简化接口,这个模式叫外观模式,它将一个或者数个复杂的接口隐藏在背后,留出干净美好的外观。
装房子
通过前面的项目中,攒了些钱,在加上父母赞助和向朋友借钱,买了一套房子,现在交房了准备装修,通过向朋友了解了下,有两种方式。
一种方式是自己买材料,自己联系工人(泥水工、木工、电工),自己检验,所有的事情亲力亲为,但相对省钱。
另一种方式是,通过装修公司,所有的事情整包给装修公司,你只需要交钱、验收就好了(当然实际情况可没有这么简单,中间还是有很多沟通和交互的)
那么我们来分别用代码实现看看。
首先,将装修中要打交道的角色创建出来。
/**
* 地砖商人
*/
public class 地砖商人 {
public void 购买地砖(){
System.out.println("从地砖商人购买地砖");
}
}
/**
* 水泥商人
*/
public class 水泥商人 {
public void 购买水泥(){
System.out.println("从水泥商人购买水泥");
}
}
/**
* 石材商人
*/
public class 石材商人 {
public void 购买石材(){
System.out.println("从石材商人购买石材");
}
}
/**
* 泥水工
*/
public class 泥水工 {
public void 铺地砖(){
System.out.println("泥水工铺地砖");
}
}
/**
* 木工
*/
public class 木工 {
public void 做柜子(){
System.out.println("木工做柜子");
}
}
/**
* 电工
*/
public class 电工 {
public void 铺电线(){
System.out.println("电工铺电线");
}
}
V1版
V1版使用亲力亲为的这种方式。
/**
* 亲力亲为的新房主人,要和所有人打交道,好累。
*/
public class 新房主人V1 {
public void 装修房子(){
地砖商人 地砖商人 = new 地砖商人();
地砖商人.购买地砖();
水泥商人 水泥商人 = new 水泥商人();
水泥商人.购买水泥();
石材商人 石材商人 = new 石材商人();
石材商人.购买石材();
泥水工 泥水工 = new 泥水工();
泥水工.铺地砖();
木工 木工 = new 木工();
木工.做柜子();
电工 电工 = new 电工();
电工.铺电线();
}
}
测试走起
/**
* 测试
*/
public class HouseDecorateMainV1 {
public static void main(String[] args) {
新房主人V1 新房主人 = new 新房主人V1();
新房主人.装修房子();
}
}
可以看出,主人为了省钱还是很拼的。
V2版
接下来看看V2的做法,通过装修公司来装修新房
/**
* 代替装修业主和各个角色打交道,脏活累活都有我来做吧。
*/
public class 装修公司 {
public void 装修新房(){
地砖商人 地砖商人 = new 地砖商人();
地砖商人.购买地砖();
水泥商人 水泥商人 = new 水泥商人();
水泥商人.购买水泥();
石材商人 石材商人 = new 石材商人();
石材商人.购买石材();
泥水工 泥水工 = new 泥水工();
泥水工.铺地砖();
木工 木工 = new 木工();
木工.做柜子();
电工 电工 = new 电工();
电工.铺电线();
}
}
V2版的新房主人就之和装修公司打交道,世界都变得美好起来了。
/**
* 亲力亲为的新房主人,要和所有人打交道,好累。
*/
public class 新房主人V2 {
public void 装修房子(){
装修公司 兄弟装修公司 = new 装修公司();
兄弟装修公司.装修新房();
}
}
测试
/**
* 测试
*/
public class HouseDecorateMainV2 {
public static void main(String[] args) {
新房主人V2 新房主人 = new 新房主人V2();
新房主人.装修房子();
}
}
可以看出,代码是一样,不过V2中在新房主人和各个商人/工人中间加入一层,这一层聚合了和各个角色交互,对于装房的人来说,简化了装房的过程。
看下V2的UML图
定义
V2版的实现就用到了外观模式,也叫门面模式,外观模式提供了一个统一的接口,用来访问子系统中的一群接口,简化子系统的使用。
当然,你可以绕过这个简单的外观,而直接使用子系统的接口,做精细化控制。
代码来实现一波定义。
/**
* 子系统A
*/
public class SubsystemA {
public void doSpecial(){
System.out.println("子系统A执行特殊的方法....");
}
}
/**
* 子系统B
*/
public class SubsystemB {
public void doSpecial(){
System.out.println("子系统B执行特殊的方法....");
}
}
/**
* 子系统C
*/
public class SubsystemC {
public void doSpecial(){
System.out.println("子系统C执行特殊的方法....");
}
}
接下来,用一个门面来聚合子系统的能力,提供聚合能力
/**
* 门面,提供简单干净的对外接口
*/
public class SystemFacade {
public void doSomeSpecialThing(){
SubsystemA subsystemA = new SubsystemA();
subsystemA.doSpecial();
SubsystemB subsystemB = new SubsystemB();
subsystemB.doSpecial();
SubsystemC subsystemC = new SubsystemC();
subsystemC.doSpecial();
}
}
这样,客户就可以非常容易的使用门面来获取聚合的能力了
/**
* 使用门面的客户
*/
public class Client {
public void doSomeThing(){
SystemFacade systemFacade = new SystemFacade();
systemFacade.doSomeSpecialThing();
}
public static void main(String[] args) {
new Client().doSomeThing();
}
}
观察UML图
扩展示例
为了加深理解,我们来看下一个电商库存的例子。
同样,先把子系统构建起来,就是实际干活的如库存管理、校验管理、费用计算、支付、物流这些子系统。
各个子系统,贴出两个子系统的代码,其他子系统类似。
/**
* 库存接口
*/
public interface IInventory {
void update(int productId);
}
/**
* 库存管理
*/
public class InventoryManager implements IInventory {
@Override
public void update(int productId) {
String msg = "Product# " + productId +
" is subtracted from store's inventory";
System.out.println(msg);
}
}
/**
* 订单校验接口
*/
public interface IOrderVerify {
boolean verifyShippingAddress(int pincode);
}
/**
* 订单检验管理
*/
public class OrderVerificationManager implements IOrderVerify {
@Override
public boolean verifyShippingAddress(int pincode) {
System.out.println(
"The product can be shipped to the pincode "
+ pincode);
return true;
}
}
如果没使用外观时,客户直接使用子系统,代码会长成下面这个样子
/**
* 没有使用外观时的客户长这个样子
*/
public class NoFacadeMain {
public static void main(String[] args) {
// Creating the Order/Product details
OrderDetails orderDetails = new OrderDetails("Java Design Pattern book",
"Simplified book on design patterns in Java",
500, 10, "Street No 1", "Educational Area", 1212,
"8811123456");
// Updating the inventory.
IInventory inventory = new InventoryManager();
inventory.update(orderDetails.getProductNo());
// verifying various details for the order such as the shipping address.
IOrderVerify orderVerify = new OrderVerificationManager();
orderVerify.verifyShippingAddress(orderDetails.getPinCode());
// Calculating the final cost after applying various discounts.
ICosting costManager = new CostManager();
orderDetails.setPrice(
costManager.applyDiscount(
orderDetails.getPrice(),
orderDetails.getDiscountPercent()
)
);
// Going through various steps if payment gateway like card verification,
// charging from the card.
IPaymentGateway paymentGateway = new PaymentGatewayManager();
paymentGateway.verifyCardDetails(orderDetails.getCardNo());
paymentGateway.processPayment(orderDetails.getCardNo(), orderDetails.getPrice());
// Completing the order by providing logistics.
ILogistics logistics = new LogisticsManager();
String shippingAddress = String.format("%s, %s - %d",
orderDetails.getAddressLine1(),
orderDetails.getAddressLine2(),
orderDetails.getPinCode());
logistics.shipProducts(orderDetails.getProductName(), shippingAddress);
}
}
但是如果引入了外观后,客户会长成:
/**
* 使用外观后的客户长这个样子
*/
public class FacadeMain {
public static void main(String[] args) {
// Creating the Order/Product details
OrderDetails orderDetails = new OrderDetails("Java Design Pattern book",
"Simplified book on design patterns in Java",
500, 10, "Street No 1", "Educational Area", 1212,
"8811123456");
// Using Facade
OnlineShoppingFacade facade = new OnlineShoppingFacade();
facade.finalizeOrder(orderDetails);
}
}
其中的外观:
/**
* 购物外观
*/
public class OnlineShoppingFacade {
IInventory inventory = new InventoryManager();
IOrderVerify orderVerify = new OrderVerificationManager();
ICosting costManager = new CostManager();
IPaymentGateway paymentGateway = new PaymentGatewayManager();
ILogistics logistics = new LogisticsManager();
public void finalizeOrder(OrderDetails orderDetails) {
inventory.update(orderDetails.getProductNo());
orderVerify.verifyShippingAddress(orderDetails.getPinCode());
orderDetails.setPrice(
costManager.applyDiscount(
orderDetails.getPrice(),
orderDetails.getDiscountPercent()
)
);
paymentGateway.verifyCardDetails(orderDetails.getCardNo());
paymentGateway.processPayment(orderDetails.getCardNo(), orderDetails.getPrice());
String shippingAddress = String.format("%s, %s - %d",
orderDetails.getAddressLine1(),
orderDetails.getAddressLine2(),
orderDetails.getPinCode());
logistics.shipProducts(orderDetails.getCardNo(), shippingAddress);
}
}
该示例代码参考: https://www.codeproject.com/Articles/767154/Facade-Design-Pattern-Csharp