设计模式(6)适配器模式
模式介绍
适配器模式用于协调两个不兼容的接口之间的差异。 当用于无法重构的接口时(例如,当接口由Web服务或API控制时),此模式特别有用。
示例
假设我们在维护肉类安全烹饪温度数据库,旧的系统是这样维护的:
public enum TemperatureType
{
Fahrenheit,//华氏摄氏度
Celsius//摄氏度
}
/// <summary>
/// The legacy API which must be converted to the new structure
/// </summary>
class MeatDatabase
{
// Temps from http://www.foodsafety.gov/keep/charts/mintemp.html
public float GetSafeCookTemp(string meat, TemperatureType tempType)
{
if (tempType == TemperatureType.Fahrenheit)
{
switch (meat)
{
case "beef":
case "pork":
return 145f;
case "chicken":
case "turkey":
return 165f;
default:
return 165f;
}
}
else
{
switch (meat)
{
case "beef":
case "veal":
case "pork":
return 63f;
case "chicken":
case "turkey":
return 74f;
default:
return 74f;
}
}
}
//每盎司(28.3495231克)包含的卡路里
public int GetCaloriesPerOunce(string meat)
{
switch (meat.ToLower())
{
case "beef": return 71;
case "pork": return 69;
case "chicken": return 66;
case "turkey": return 38; //Wow, turkey is lean!
default: return 0;
}
}
//每盎司(28.3495231克)包含的蛋白质
public double GetProteinPerOunce(string meat)
{
switch (meat.ToLower())
{
case "beef": return 7.33f;
case "pork": return 7.67f;
case "chicken": return 8.57f;
case "turkey": return 8.5f;
default: return 0d;
}
}
}
可以看到,这个遗留系统没有使用面向对象模式开发,上面的那些数据应该是Meat类的属性。
我们创建一个Meat类:
/// <summary>
/// The new Meat class, which represents details about a particular kind of meat.
/// </summary>
class Meat
{
protected string MeatName;
protected float SafeCookTempFahrenheit;
protected float SafeCookTempCelsius;
protected double CaloriesPerOunce;
protected double ProteinPerOunce;
// Constructor
public Meat(string meat)
{
this.MeatName = meat;
}
public virtual void LoadData()
{
Console.WriteLine("\nMeat: {0} ------ ", MeatName);
}
}
现在的问题是,我们无法修改旧系统的API。我们需要另一个继承自Meat的类但保留对API的引用,以便API的数据可以加载到Meat类的实例中:
/// <summary>
/// The Adapter class, which wraps the Meat class and initializes that class's values.
/// </summary>
class MeatDetails : Meat
{
private MeatDatabase _meatDatabase;
// Constructor
public MeatDetails(string name)
: base(name)
{
}
public override void LoadData()
{
// The Adaptee
_meatDatabase = new MeatDatabase();
SafeCookTempFahrenheit = _meatDatabase.GetSafeCookTemp(MeatName, TemperatureType.Fahrenheit);
SafeCookTempCelsius = _meatDatabase.GetSafeCookTemp(MeatName, TemperatureType.Celsius);
CaloriesPerOunce = _meatDatabase.GetCaloriesPerOunce(MeatName);
ProteinPerOunce = _meatDatabase.GetProteinPerOunce(MeatName);
base.LoadData();
Console.WriteLine(" Safe Cook Temp (F): {0}", SafeCookTempFahrenheit);
Console.WriteLine(" Safe Cook Temp (C): {0}", SafeCookTempCelsius);
Console.WriteLine(" Calories per Ounce: {0}", CaloriesPerOunce);
Console.WriteLine(" Protein per Ounce: {0}", ProteinPerOunce);
}
}
最后,我们在客户端调用:
static void Main(string[] args)
{
//Non-adapted
Meat unknown = new Meat("Beef");
unknown.LoadData();
//Adapted
MeatDetails beef = new MeatDetails("Beef");
beef.LoadData();
MeatDetails turkey = new MeatDetails("Turkey");
turkey.LoadData();
MeatDetails chicken = new MeatDetails("Chicken");
chicken.LoadData();
Console.ReadKey();
}
总结
适配器模式尝试协调两个不兼容的接口,并且在无法重构其中一个或两个接口时特别有用。
该模式和外观模式特别相似,但是用途却不同。简单来说,
外观模式相当于说“这永远不会奏效,我会建立自己的。” 适配器模式相当于说“当然它可以工作,只需要稍微调整一下。”
源代码
https://github.com/exceptionnotfound/DesignPatterns/tree/master/Adapter
原文
https://www.exceptionnotfound.net/the-daily-design-pattern-adapter/