02.07 适配器模式

设计模式——适配器模式

需求

你想使用一个已经存在的类,但是它的接口不符合你的要求,怎么办?这样的问题在生活中很普遍:现在大部分笔记本电脑使用USB接口,而现在大部分键盘使用PS2接口,可以使用PS2/USB接口转换器把它们接起来。编程也可以采用这种转换器的思想。

定义

适配器模式(Adapter Pattern):将一个类的接口,转换成客户期望的另一个类的接口。适配器让原本接口不兼容的类可以合作无间。别名包装(Wrapper)模式。

适配器模式的实现方法可以概括为:单独设计一个适配器(Adapter)类,包装(Wrapper)需要适配的源(Adaptee),继承/实现需要适配的目标(Target),重写(override)/实现(implements)目标的方法,从而实现接口的转换。

适配器模式由3部分组成:(1)适配器(Adapter)类,实现接口转换;(2)适配源(Adaptee),通常包括接口(IAdaptee,interface或者abstract class)和相应子类(Adpatee);(3)适配目标(Target),通常包括接口(ITarget,interface或者abstract class)与相应子类(Target)。

适配器模式有对象适配器和类适配器两种形式的实现结构。二者不同之处在于:对象适配器实现需要适配目标(Target)的接口(interface或者abstract class),而类适配器直接继承需要适配目标的类。

对象适配器常常容纳一个它包装(Wrapper)的类的实例,在需要实现的目标接口方法里调用被包装对象的物理实体,来实现适配(接口的转换)。

  

类适配器:这种适配器模式下,适配器包装适配源,直接继承它需要适配的目标类(常常会导致万恶的多重继承),重写目标相应方法实现接口转换。

  

像上面类适配器原理图中,类适配器(Adapter)继承/实现需要适配的源(Adaptee)和目标(Target),内包装(Wapper)了源和目标的对象实例,重写源和目标的方法来实现双方的匹配转换。

类适配器采用“多继承”的实现方式,带来了不良的高耦合,所以一般不推荐使用。对象适配器采用“对象组合”的方式,更符合松耦合精神。

还有种缺省适配器模式:缺省适配器模式是一种特殊的适配器模式,但这个适配器是由一个抽象类实现的,并且在抽象类中要实现目标接口中所规定的所有方法,但很多方法的实现都是“平庸”的实现,也就是说,这些方法都是空方法。而具体的子类都要继承此抽象类。

对象适配器案例

家里有台USB接口的笔记本电脑和一个PS2接口的键盘,加个PS2/USB的转换器后,电脑就可以使用外接键盘了。

    class Program

    {

        static voidMain(string[] args)

        {

            // 客户程序:为了给某USB接口的笔记本电脑配一个PS2接口的键盘,中间加了个接口转换适配器

            Keyboard keyboard = new Keyboard();

            PS2toUSBAdapter adapter = new PS2toUSBAdapter(keyboard);

            adapter.WorkWithUSB();

            NoteBook notebook = new NoteBook();

            notebook.WorkWithUSB();

        }

    }

    // 目标类:USB接口

    public interface IUSBPort { void WorkWithUSB();}

    public class NoteBook : IUSBPort

    {

        public void WorkWithUSB() { Console.WriteLine("这台笔记本电脑使用USB接口..."); }

    }

    // 适配源类:PS2接口

    public interface IPS2Port { void WorkWithPS2();}

    public class Keyboard : IPS2Port

    {

        public virtual void WorkWithPS2() { Console.WriteLine("这个键盘使用PS2接口!"); }

    }

    // 适配器类:Wrapper适配源(Adaptee);继承/实现适配目标(Target)。

    public class PS2toUSBAdapter : IUSBPort

    {

        public PS2toUSBAdapter(IPS2Port ps2Port)

        {

            this._PS2Port = ps2Port;

        }

        private IPS2Port _PS2Port;

        public void WorkWithUSB()   // 调用适配源实例,实现适配目标的方法

        {

            this._PS2Port.WorkWithPS2();

            Console.WriteLine("经过这个适配器的转换,可以连接USB设备了!");

        }

    }

类适配器案例

仍然是上面的故事,但是代码改写了:

    class Program

    {

        static voidMain(string[] args)

        {

            // 客户程序:为了给某USB接口的笔记本电脑配一个PS2接口的键盘,中间加了个接口转换适配器

            Keyboard keyboard = new Keyboard();

            NoteBook notebook = new NoteBook();

            PS2toUSBAdapter adapter = new PS2toUSBAdapter(keyboard);

            adapter.WorkWithUSB();

        }

    }

    // 目标类:USB接口

    public interface IUSBPort { void WorkWithUSB();}

    public class NoteBook : IUSBPort

    {

        public virtual void WorkWithUSB() { Console.WriteLine("这台笔记本电脑使用USB接口..."); }

    }

    // 适配源类:PS2接口

    public interface IPS2Port { void WorkWithPS2();}

    public class Keyboard : IPS2Port

    {

        public void WorkWithPS2() { Console.WriteLine("这个键盘使用PS2接口!"); }

    }

    // 适配器类:继承/实现适配源(Adaptee)和目标(Target)。

    public class PS2toUSBAdapter : NoteBook

    {

        public PS2toUSBAdapter(IPS2Port ps2Port)

        {

            this._PS2Port = ps2Port;

        }

        private IPS2Port _PS2Port;

        public override void WorkWithUSB()  // 重写适配目标的方法

        {

            base.WorkWithUSB();

            Console.WriteLine("实现PS2/USB接口的转换...");

            this._PS2Port.WorkWithPS2();

        }

}

优缺点

优点:使用设配器模式,可以将一个系统的接口和本来不相容的另一个系统联系起来,从而使得这两个类能够在一起工作,强调对接口的转换。

缺点:对于对象适配器来说,重新定义适配的类的行为比较困难。而类适配器则不能适配一个类以及它的子类;

适用场景

(1)适配器模式主要应用于“希望复用一些现存的类,但是接口又与复用环境要求不一致的情况”。

(2)其实适配器模式有点无奈之举,在前期设计的时候,我们就不应该考虑适配器模式,而应该考虑通过重构统一接口。

(3)在遗留代码复用、类库迁移等方面非常有用。

适配器模式与装饰者模式

它们都可以用来包装对象,本质区别在于:(1)适配器模式将一个接口转换成另外一个接口。(2)装饰者模式不改变接口,只加入职责。

补充

如果适配目标不让继承,怎么办?

原文地址:https://www.cnblogs.com/sagahu/p/2711967.html