适配器模式是一种结构型模式,它把一个类的接口变换成客户端所期待的另一种接口,从而使原本接口不匹配而无法在一起工作的两个类能够在一起工作。
一、如何定义:
适配器模式是一种结构型模式,它把一个类的接口变换成客户端所期待的另一种接口,从而使原本接口不匹配而无法在一起工作的两个类能够在一起工作。
二、如何理解:
就以本例的故事作为对适配器模式的理解:有一个厨师,他会做许多种菜品,如果你给他说我要麻婆豆腐,Ok,他听得懂你说的,并直接给你炒了一份麻婆豆腐。现在,我们来到皇潮大酒店吃饭,这个酒店的菜谱上提供的不再是具体的菜名,而是按照荤菜,素菜,汤菜等菜品上菜,具体什么菜由他们来给你配,你无权直接点菜名(有点过份哈),但不管是什么菜,都是由那个厨师在做,我们不是直接和厨师打交道,而是和酒店打交道,按照酒店提供的服务接口(即:荤,素、汤)点菜。过了一段时间,我们到另一家名叫兴隆大酒店的馆子吃饭,这家酒店的菜谱也不直接提供菜名,而是按川菜,鲁菜,粤菜等菜系的方式上菜,具体什么菜还是由他们来给你配,但巧的是,上次在皇潮大酒店做工的那个厨师已经跳槽到了这家兴隆大酒店,所以实际的具体菜品还是那些,只不过,这次我们不是按荤、素、汤来点菜,而是按兴隆大酒店给我们提供的服务接口(川、鲁、粤)来点菜。所以,这个故事中,我们遇到了三种点菜的方式,一是直接提供菜名给厨师,则他来直接炒菜给我们。二是按荤,素、汤三类来配套餐。三是按菜系名来配套餐。其中,第一种是我们原系统中已经有的方式了。后两种是我们系统在扩展过程中新出现的需求,如何重复使用同一个厨师(代码复用),而不是分别请不同的厨师来满足后面两种需求呢?
三、适配器模式所涉及的角色包括:
1、目标(Target)角色:这是客户所期待的接口(就是上面故事中提到的按荤素汤来点菜或按川鲁粤来点菜)。因为C#不支持多继承,所以Target必须是接口,不可以是类。
2、源(Adaptee)角色:需要适配的类(就是上面提到的厨师)。
3、适配器(Adapter)角色:把源接口转换成目标接口。这一角色必须是类。(通过此类,把厨师能接受的相关菜品转换成酒店能接受的菜品分类方式)
四、适配器模式的两种形式
适配器模式有类的适配器模式和对象的适配器模式两种。我们将分别实现这两种Adapter模式
五、故事的实现
程序如图:
1、源(Adaptee)角色定义
定义厨师师傅类
Code
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace AdapterPattern
{
//定义一个厨师,他可以做以下的菜式
//这是厨师自己希望的接口:但是由于到了皇潮大酒店,就不能直接这样使用了
class CommonCooker
{
public void YuxiangRouShi()
{
System.Console.WriteLine("炒鱼香肉丝");
System.Console.WriteLine();
}
public void MaPoDouFu()
{
System.Console.WriteLine("麻婆豆腐");
System.Console.WriteLine();
}
public void GongBaoJiDing()
{
System.Console.WriteLine("宫保鸡丁");
System.Console.WriteLine();
}
public void FuQiFeiPian()
{
System.Console.WriteLine("夫妻肺片");
System.Console.WriteLine();
}
public void MaoXieWang()
{
System.Console.WriteLine("毛血旺");
System.Console.WriteLine();
}
public void HuiGuoRou()
{
System.Console.WriteLine("回锅肉");
System.Console.WriteLine();
}
public void TangChuHuangHuaYu()
{
System.Console.WriteLine("糖醋黄花鱼");
System.Console.WriteLine();
}
public void KaiShuiBaiCai()
{
System.Console.WriteLine("开水白菜 ");
System.Console.WriteLine();
}
public void YaCaiXiaoChao()
{
System.Console.WriteLine(" 芽菜小炒 ");
System.Console.WriteLine();
}
public void YuFuChangYangRou()
{
System.Console.WriteLine(" 鱼腹藏羊肉 ");
System.Console.WriteLine();
}
public void DaiZhiShangChao()
{
System.Console.WriteLine(" 带子上朝 ");
System.Console.WriteLine();
}
public void DongJiangYanJuJi()
{
System.Console.WriteLine(" 东江盐焗鸡 ");
System.Console.WriteLine();
}
public void ZhaZiJi()
{
System.Console.WriteLine(" 炸子鸡 ");
System.Console.WriteLine();
}
public void ShiJinDongGuaMao()
{
System.Console.WriteLine(" 什锦冬瓜帽 ");
System.Console.WriteLine();
}
public void GuangShiShaoTianYa()
{
System.Console.WriteLine(" 广式烧填鸭 ");
System.Console.WriteLine();
}
}
}
2、目标(Target)角色定义
皇潮酒店要求的菜谱接口
Code
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace AdapterPattern
{
//这是皇潮大酒店希望的菜式的接口
//菜式按照他们要求的表达来分类
public interface IEmpireRestaurant
{
void HunCai(); //荤菜类
void ShuCai(); //素菜类
void TangCai(); //汤菜类
}
}
兴隆酒店要求的菜谱接口
Code
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace AdapterPattern
{
//这是兴隆大酒店希望的菜式的接口
//菜式按照他们要求的表达来分类
public interface IXingLongRestaurant
{
void ChuanCai(); //川菜
void YueCai(); //粤菜
void LuCai(); //鲁菜
}
}
3、适配器(Adapter)角色定义
对象的Adapter模式示意性实现(用此方式实现皇潮酒店的菜谱要求):
Code
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace AdapterPattern
{
//这是针对皇潮大酒店制定的适配器
//与类适配器相比较,可以看到最大的区别是适配器类的数量减少了,不再需要为每一种具体的Cooker方式来创建一个适配器类。
//同时可以看到,引入对象适配器后,适配器类不再依赖于具体的CommonCooker类,更好的实现了松耦合。
class EmpireAdapter:IEmpireRestaurant
{
CommonCooker ck = new CommonCooker();
//荤菜类
public void HunCai()
{
ck.DaiZhiShangChao();
ck.DongJiangYanJuJi();
ck.FuQiFeiPian();
ck.GuangShiShaoTianYa();
}
//素菜类
public void ShuCai()
{
ck.ShiJinDongGuaMao();
ck.MaPoDouFu();
}
//汤菜类
public void TangCai()
{
ck.KaiShuiBaiCai();
ck.YaCaiXiaoChao();
}
}
}
类的Adapter模式示意性实现(用此方式实现兴隆酒店的菜谱要求):
Code
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace AdapterPattern
{
//XingLongAdapter适配器继承CommonCooker类与IXingLongRestaurant接口
class XingLongAdapter:CommonCooker,IXingLongRestaurant
{
#region 按类方法实现
public void ChuanCai()
{
this.MaoXieWang();
this.MaPoDouFu();
this.FuQiFeiPian();
this.GongBaoJiDing();
}
//粤菜
public void YueCai()
{
this.GuangShiShaoTianYa();
this.DongJiangYanJuJi();
this.ZhaZiJi();
this.ShiJinDongGuaMao();
}
//鲁菜
public void LuCai()
{
this.YuFuChangYangRou();
this.DaiZhiShangChao();
this.TangChuHuangHuaYu();
}
#endregion
}
}
4、客户端调用
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace AdapterPattern
{
class Program
{
static void Main(string[] args)
{
EmpireAdapter emp = new EmpireAdapter();
Console.WriteLine("-----我要荤菜类清单-----");
emp.HunCai();
Console.WriteLine("-----我要素菜类清单-----");
emp.ShuCai();
Console.WriteLine("-----我要汤菜类清单-----");
emp.TangCai();
XingLongAdapter xla = new XingLongAdapter();
Console.WriteLine("-----我要川菜清单-----");
xla.ChuanCai();
Console.WriteLine("-----我要鲁菜清单-----");
xla.LuCai();
Console.WriteLine("-----我要粤菜清单-----");
xla.YueCai();
Console.Read();
}
}
}
六、何时采用
从代码角度来说, 如果需要调用的类所遵循的接口并不符合系统的要求或者说并不是客户所期望的,那么可以考虑使用适配器。
从应用角度来说, 如果因为产品迁移、合作模块的变动,导致双方一致的接口产生了不一致,或者是希望在两个关联不大的类型之间建立一种关系的情况下可以考虑适配器模式。
七、实现要点
适配器模式是否能成功运用的关键在于代码本身是否是基于接口编程的,如果不是的话,那么适配器无能为力。
适配器模式的实现很简单,基本的思想就是适配器一定是遵循目标接口的。
适配器模式的变化比较多,可以通过继承和组合方式进行适配,适配器可以是一组适配器产品,适配器也可以是抽象类型。
适配器模式和Facade的区别是,前者是遵循接口的,后者可以是不遵循接口的,比较灵活。
适配器模式和Proxy的区别是,前者是为对象提供不同的接口,或者为对象提供相同接口,并且前者有一点后补的味道,后者是在设计时就会运用的。
八、注意事项
在对两个无关类进行适配的时候考虑一下适配的代价,一个非常庞大的适配器可能会对系统性能有影响。
运行效果如下图:
前往:设计模式学习笔记清单