【设计模式】适配器模式 Adapter Pattern

适配器模式在软件开发界使用及其普遍,在工业界,现实中也是家常便饭。好比手机充电器,笔记本充电器,广播接收器,电视接收器等等。都是适配器。html

image 

适配器主要做用是让原本不兼容的两个事物兼容和谐的一块儿工做。好比, 一般咱们使用的交流电都是220v,可是手机电池可以承载的5v电压,所以直接将咱们使用的220v交流电直接接到手机上,手机确定就坏,第二个做用是匹配交流电插座和手机充电接口不兼容的问题,所以,一个充电器解决了电和手机存在的俩个问题(电压和接口),并使其正常工做。git

那么在软件开发过程当中也会常常碰到这样的问题,那就是系统都开发好了,忽然有一天客户说要接入其它系统的数据,可是当你看到接口接入文档时发现两边的接口都对不上,数据结构定义的也不同,好比说,咱们系统中有个定义的方法叫 GetUserByUserId(int userId) 返回的数据结构是这样定义的:数据库

image

public class User
{
    public int UserId { get; set; }
    public string UserName { get; set; }
    public int Age { get; set; }
    public string Address { get; set; }
    public string TelNumber{get;set;}
    public string MobileNumber { get; set; }
}

而对方系统接口也定义了一个方法叫 GetUserInfoById(int id)  可是返回的数据结构长这样子:数据结构

image

public class UserInfo
{
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public int Age { get; set; }
    public Address Address { get; set; }
    public string TelphoneNumber { get; set; }
    public string CellphoneNumber { get; set; }
}
public class Address
{
    public Country Country { get; set; }
    public string City{get;set;}
    public  string Street{get;set;}
    public string Number { get; set; }
    public Location Location { get; set; }
    public string PostCode { get; set; }
       
}

public class Country
{
    public string Name { get; set; }
    public string Number { get; set; }
    public string Abbreviation { get; set; }
}
public class Location
{
    public long Longitude { get; set; }
    public long Latitude { get; set; }
}

 

 

那咱们该怎么对接这个外部系统的用户到咱们的系统中来呢? 这就是了咱们要讨论的适配器(Adapter) 模式了。app

1、适配器模式的定义

适配器模式(Adapter Pattern):将一个接口转换成客户但愿的另外一个接口,使接口不兼容的那些类能够一块儿工做,其别名为包装器(Wrapper)。适配器模式既能够做为类结构型模式,也能够做为对象结构型模式。ide

2、适配器模式的结构图

image 一、Target(目标抽象类):

目标抽象类定义客户所需接口,能够是一个抽象类或接口,也能够是具体类。this

二、Adapter(适配器类):

适配器能够调用另外一个接口,做为一个转换器,对Adaptee和Target进行适配,适配器类是适配器模式的核心,在对象适配器中,它经过继承Target并关联一个Adaptee对象使两者产生联系。在类机构中他直接继承target接口和一个Adaptee类来实现。     spa

三、Adaptee(适配者类):

适配者即被适配的角色,它定义了一个已经存在的接口,这个接口须要适配,适配者类通常是一个具体类,包含了客户但愿使用的业务方法,在某些状况下可能没有适配者类的源代码。code

3、适配器模式的经典实现

public abstract class Target
{
    public abstract void Request();
}
public class Adaptee
{
    public void specificRequest()
    {
        Console.WriteLine("I'm Adaptee method");
    }
}
public class Adapter : Target
{
    private Adaptee _adaptee;
    public Adapter(Adaptee adaptee)
    {
        _adaptee = adaptee;
    }
    public override void Request()
    {
        _adaptee.specificRequest();
    }
}

客户端调用代码:orm

static void Main(string[] args)
{
    Target target = new Adapter(new Adaptee());
    target.Request();

    Console.ReadKey();
}

结果输出:

image

4、适配器模式实例

讨论完适配器模式的概念后咱们来使用适配器模式解决文中开头提出来的问题, 怎么将UserProvider 接口适配到IUserService接口(注意:这里所说的接口是广义的接口,而不是C#中用I开头定义的接口),有了适配器模式如今就变得简单了,IUserService 接口就是适配器模式的目标抽象类(Target), UserProvider 就是适配器模式的适配者类(Adaptee),咱们新建一个适配器类UserAdapter (Adapter) 就可让它们工做了。结构图以下:

image

对象结构型实现:

在UserPorvider类中实例化两个UserInfo对象(模拟数据存储在数据库中),假设它就是要接入的数据。那么代码就是这样子:

public class User
{
    public int UserId { get; set; }
    public string UserName { get; set; }
    public int Age { get; set; }
    public string Address { get; set; }
    public string TelNumber { get; set; }
    public string MobileNumber { get; set; }
}

public class UserInfo
{
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public int Age { get; set; }
    public Address Address { get; set; }
    public string TelphoneNumber { get; set; }
    public string CellphoneNumber { get; set; }
}
public class Address
{
    public Country Country { get; set; }
    public string City { get; set; }
    public string Street { get; set; }
    public string Number { get; set; }
    public Location Location { get; set; }
    public string PostCode { get; set; }

}

public class Country
{
    public string Name { get; set; }
    public string Number { get; set; }
    public string Abbreviation { get; set; }
}
public class Location
{
    public double Longitude { get; set; }
    public double Latitude { get; set; }
}

public class UserProvider
{
    private static IDictionary<int, UserInfo> innerDictionary = new Dictionary<int, UserInfo>();
    static UserProvider()
    {
        innerDictionary.Add(1, new UserInfo
        {
            FirstName = "Kevin",
            LastName = "Durnt",
            Age = 30,
            CellphoneNumber = "136xxxx1234",
            TelphoneNumber = "010-34567890",
            Id = 1,
            Address = new Address
            {
                City = "Xi'an",
                Number = "24",
                PostCode = "710000",
                Street = "Gao xin",
                Country = new Country
                {
                    Abbreviation = "zh-CN",
                    Name = "China",
                    Number = "018",
                },
                Location = new Location
                {
                    Latitude = 31.123456,
                    Longitude = 35.23456,
                }
            }
        });
        innerDictionary.Add(2, new UserInfo
        {
            FirstName = "Kobe",
            LastName = "Durnt",
            Age = 39,
            CellphoneNumber = "139xxxx1234",
            TelphoneNumber = "010-24567890",
            Id = 2,
            Address = new Address
            {
                City = "Xi'an",
                Number = "24",
                PostCode = "710000",
                Street = "Gao xin",
                Country = new Country
                {
                    Abbreviation = "zh-CN",
                    Name = "China",
                    Number = "018",
                },
                Location = new Location
                {
                    Latitude = 31.123456,
                    Longitude = 35.23456
                }
            }
        });
    }
    public UserInfo GetUserById(int id)
    {
        return innerDictionary[id];
    }
}

public interface IUserService
{
    User GetUserByUserId(int userId);
}
public class UserAdapter : IUserService
{
    private UserProvider _userProvider;
    public UserAdapter(UserProvider userProvider)
    {
        _userProvider = userProvider;
    }
    public User GetUserByUserId(int userId)
    {
        UserInfo userInfo = _userProvider.GetUserById(userId);

        User user = new User();
        user.UserId = userInfo.Id;
        user.UserName = string.Format("{0} {1}", userInfo.FirstName, userInfo.LastName);
        user.TelNumber = userInfo.TelphoneNumber;
        user.MobileNumber = userInfo.CellphoneNumber;
        user.Age = userInfo.Age;
        user.Address = string.Format("{0} {1}, {2},{3}, Location:{4}, {5}",
            userInfo.Address.Street,
            userInfo.Address.Number,
            userInfo.Address.Country.Name,
            userInfo.Address.PostCode,
            userInfo.Address.Location.Latitude,
            userInfo.Address.Location.Longitude);

        return user;
    }
}

客户端调用:

static void Main(string[] args)
{
    IUserService target = new UserAdapter(new UserProvider());
    User user=target.GetUserByUserId(1);

    Console.WriteLine("UserId: " + user.UserId);
    Console.WriteLine("UserName: " + user.UserName);
    Console.WriteLine("Age: " + user.Age);
    Console.WriteLine("TelNumber: " + user.TelNumber);
    Console.WriteLine("MobileNumber: " + user.MobileNumber);
    Console.Write("Address: " + user.Address);

    Console.ReadKey();
}

输出结果:

image 

反射+配置实现热替换

为了达到灵活配置的目的,其实在不少时候,客户端不须要知道第三方接口长什么样,所以,在适配器类里面能够隐藏掉调用第三方代码的细节,那么对Adaptee的实例化直接放到Adapter里,所以,客户端直接依赖高层抽象Target就能够了,这样就能够随时将Adaptee 替换掉, 而且咱们可使用配置+反射来达到这种动态替换的效果。下面咱们稍加修改UserAdapter类,并加一个配置来完成这个设想:

A、在UserAdapter构造里去掉类型为UserProvider 的参数,UserAdapter变成这样了:

public class UserAdapter : IUserService
{
    private UserProvider _userProvider;
    public UserAdapter()
    {
        _userProvider = new UserProvider();
    }
    public User GetUserByUserId(int userId)
    {
        UserInfo userInfo = _userProvider.GetUserById(userId);

        User user = new User();
        user.UserId = userInfo.Id;
        user.UserName = string.Format("{0} {1}", userInfo.FirstName, userInfo.LastName);
        user.TelNumber = userInfo.TelphoneNumber;
        user.MobileNumber = userInfo.CellphoneNumber;
        user.Age = userInfo.Age;
        user.Address = string.Format("{0} {1}, {2},{3}, Location:{4}, {5}",
            userInfo.Address.Street,
            userInfo.Address.Number,
            userInfo.Address.Country.Name,
            userInfo.Address.PostCode,
            userInfo.Address.Location.Latitude,
            userInfo.Address.Location.Longitude);

        return user;
    }
}

B. 在App.config中加入以下配置:

<appSettings>
  <add key="Adapter" value="DesignPattern.Adapter.UserAdapter"/>
</appSettings>

C.在代码中使用反射获得具体的Adapter 类,而后调用相应方法:

static void Main(string[] args)
{    
    var setting = ConfigurationSettings.AppSettings["Adapter"];
    Assembly assembly=Assembly.GetExecutingAssembly();
    IUserService target = assembly.CreateInstance(setting) as IUserService;
    
    User user=target.GetUserByUserId(1);

    Console.WriteLine("UserId: " + user.UserId);
    Console.WriteLine("UserName: " + user.UserName);
    Console.WriteLine("Age: " + user.Age);
    Console.WriteLine("TelNumber: " + user.TelNumber);
    Console.WriteLine("MobileNumber: " + user.MobileNumber);
    Console.Write("Address: " + user.Address);

    Console.ReadKey();
}

结果:

image

类结构实现

上面的adapter是对象结构型的实现。adapter 还能够是类结构型模式, 类适配器和对象适配器的不一样之处就是适配器与适配者的关系不一样。对象适配器,适配器与适配者之间是关联关系,而类适配器,适配器与适配者之间是继承关系。

下来咱们使用类结构来实现上面的需求:

public class UserClassAdapter : UserProvider, IUserService
{      
    public User GetUserByUserId(int userId)
    {
        UserInfo userInfo =this.GetUserById(userId);

        User user = new User();
        user.UserId = userInfo.Id;
        user.UserName = string.Format("{0} {1}", userInfo.FirstName, userInfo.LastName);
        user.TelNumber = userInfo.TelphoneNumber;
        user.MobileNumber = userInfo.CellphoneNumber;
        user.Age = userInfo.Age;
        user.Address = string.Format("{0} {1}, {2},{3}, Location:{4}, {5}",
            userInfo.Address.Street,
            userInfo.Address.Number,
            userInfo.Address.Country.Name,
            userInfo.Address.PostCode,
            userInfo.Address.Location.Latitude,
            userInfo.Address.Location.Longitude);

        return user;
    }
}

仅仅只须要须要将UserAdapter和UserProvider的关系改为集成就能够了。 输出结果和以前是同样的。

在C#中因为类只能是单继承关系, 一个类只能继承自一个类,但能够继承多个接口,若是Target角色是类,Adaptee也是类的话就不能使用类结构模式。

5、适配器模式的缺点

A. 类结构适配器和对象结构适配器共有的优势:

  1. 将目标类和适配者类解耦,经过引入一个适配器类来重用现有的适配者类,无须修改原有结构。

  2.  增长了类的透明性和复用性将具体的业务实现过程封装在适配者类中,对于客户端类而言是透明的,并且提升了适配者的复用性,同一个适配者类能够在多个不一样的系统中复用。
  3. 灵活性和扩展性都很是好,经过使用配置文件,能够很方便地更换适配器,也能够在不修改原有代码的基础上增长新的适配器类,彻底符合“开闭原则OCP”。

B.除了共有的优势外,类适配器还有以下优势:

  1. 因为适配器类是适配者类的子类,所以能够在适配器类中置换一些适配者的方法,使得适配器的灵活性更强。

C.除了共有的优势外,对象适配器还有以下优势:

  1. 一个对象适配器能够把多个不一样的适配者适配到同一个目标
  2. 能够适配一个适配者的父类,因为适配器和适配者之间是关联关系,根据“里氏代换原则LSP”,适配者的子类也可经过该适配器进行适配。

6、适配器模式的缺点

A.类适配器的缺点

  1. 因为C#不支持类的多继承,一次最多只能适配一个适配者类,不能同时适配多个适配者。
  2. 适配者类不能为最终类,C#中不能为sealed类,这样没法继承了。
  3. 在C#语言中,类适配器模式中的目标抽象类只能为接口,不能为类,其使用有必定的局限性。其实这些都是单类继承的语言特性形成的。

B.对象适配器的缺点

  1. 与类适配器模式相比,要在适配器中置换适配者类的某些方法比较麻烦。若是必定要置换掉适配者类的一个或多个方法,能够先作一个适配者类的子类,将适配者类的方法置换掉,而后再把适配者类的子类当作真正的适配者进行适配,实现过程较为复杂, 另外一种方法是直接在适配器类中将相应的方法从新实现掉。

7、适配器模式的使用场景

  1. 系统须要使用一些现有的类,而这些类的接口(如方法名)不符合系统的须要,甚至没有这些类的源代码。
  2. 想建立一个能够重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在未来引进的类一块儿工做。
  3. 在调用第三方接口是,和现有的系统模型不配是可使用Adapter模式将模型转化一直。

8、扩展-Default Adapter Parttern

在使用适配器模式的时候常常会碰到一类场景,就是已有的类的全部方法都都正常工做,可是只有那么几个方法须要调用第三方的几个系统提供的API,这时咱们使用继承在适配器类里从新实现一遍工做量太大。这就要使用适配器模式的一个变体。这就是默认适配器,默认适配器上Target类是一个具体的类,实现大多数方法,甚至全部方法,但都是成虚方法,这样在适配器中有选择的重写Target中的方法就能够了。这种变体在实践中继承使用。也是颇有用的一种模式。

 

会不会存在一个多功能的双向适配器呢(好比A系统对接B系统,同时B系统也要对接A系统)? 若是用C#该如何实现呢?

相关文章
相关标签/搜索