6

设计模式之(9)——适配器 模式 - 一只烤鸭朝北走

 2 years ago
source link: https://www.cnblogs.com/wha6239/p/16657461.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

设计模式之(9)——适配器 模式

  定义:适配器模式是将一个类的接口转换成客户希望的另一个接口,适配器模式使得原本由于接口不兼容而不能一起工作的类可以一起工作,在软件设计中我们需要将一些“现存的对象”放到新的环境中,而新环境要求的接口是现对象所不能满足的,我们可以使用这种模式进行接口适配转换,使得“老对象”符合新环境的要求。

  使用场景:1、系统需要使用现有的类,而此类的接口不符合系统的需要;2、通过接口转换,将一个类插入另一个类中,用电器来打个比喻:有一个电器的插头是三脚的,而现有的插座是两孔的,要使插头插上插座,我们需要一个插头转换器,这个转换器即是适配器。

  适配器模式涉及三个角色:

  1、源(Adaptee):需要被适配的对象或类型,相当于插头;

  2、适配器(Adapter):连接源和目标对象的中间对象,相当于转换器;

  3、目标角色(Target):定义了客户端期望的接口,相当于插座;

  如何实现呢?

  使用继承(类适配器)或者聚合(对象适配器)已有的对象实现想要的目标接口,优先推荐使用对象适配器(基于组合优先于继承)。

  适配器模式结构图:

  

1139198-20220905100552448-1643622004.png

  以下我们先以对象适配为例来分析,以下是源码:

package cn.com.pep.model.adapter.a1;
/**
*
* @Title: AdvancedMediaPlayer 
* @Description:  源对象的接口
* @author wwh
* @date 2022-9-5 10:10:14
*/
public interface AdvancedMediaPlayer {
/**
* @Title: playVlc
* @Description: 
* @param filename
*/
public void playVlc(String filename);
/**
* @Title: playMp4
* @Description: 
* @param filename
*/
public void playMp4(String filename);
}
package cn.com.pep.model.adapter.a1;
/**
 * 
 * @Title: MeidaPlayer  
 * @Description:  目标对象接口
 * @author wwh 
 * @date 2022-9-5 10:08:30
 */
public interface MeidaPlayer {
    
    /**
     * @Title: play 
     * @Description:  
     * @param audioType
     * @param filename
     */
    public void play(String audioType,String filename);

}
package cn.com.pep.model.adapter.a1;
/**
*
* @Title: Mp4MediaPlayer
* @Description: 源对象
* @author wwh
* @date 2022-9-5 10:13:48
*/
public class Mp4MediaPlayer implements AdvancedMediaPlayer {
@Override
public void playVlc(String filename) {
}
@Override
public void playMp4(String filename) {
System.err.println("Playing mp4 and filename is:" + filename);
}
} 
package cn.com.pep.model.adapter.a1;

/**
 * 
 * @Title: VLCMediaPlayer
 * @Description: 源对象
 * @author wwh
 * @date 2022-9-5 10:12:07
 */
public class VLCMediaPlayer implements AdvancedMediaPlayer {

    @Override
    public void playVlc(String filename) {
        System.err.println("Playing vlc and filename is:" + filename);
    }

    @Override
    public void playMp4(String filename) {
        
    }

}
package cn.com.pep.model.adapter.a1;

/**
 * 
 * @Title: MediaPlayerAdapter
 * @Description: 对象适配器,通过聚合持有一个源对象的引用
 * @author wwh
 * @date 2022-9-5 10:19:10
 */
public class MediaPlayerAdapter implements MeidaPlayer {
    
    /**
     *     通过聚合的方式持有一个源对象的引用
     */
    private AdvancedMediaPlayer player;

    public MediaPlayerAdapter(String filetype) {
        if ("mp4".equalsIgnoreCase(filetype)) {
            player = new Mp4MediaPlayer();
        } else if ("vlc".equalsIgnoreCase(filetype)) {
            player = new VLCMediaPlayer();
        }
    }

    @Override
    public void play(String audioType, String filename) {
        System.err.println("执行了适配器中的play()方法");
        if ("mp4".equalsIgnoreCase(audioType)) {
            player.playMp4(filename);
        } else if ("vlc".equalsIgnoreCase(audioType)) {
            player.playVlc(filename);
        }
    }

}
package cn.com.pep.model.adapter.a1;
/**
 * @Title: AudioMediaPlayer
 * @Description: 目标对象
 * @author wwh
 * @date 2022-9-5 10:49:33
 */
public class AudioMediaPlayer implements MeidaPlayer {

    private MediaPlayerAdapter adapter;

    @Override
    public void play(String audioType, String filename) {
        if ("mp3".equalsIgnoreCase(audioType)) {
            System.out.println("Playing mp3 and filename is: " + filename);
        } else if ("mp4".equalsIgnoreCase(audioType) || "vlc".equalsIgnoreCase(audioType)) {
            adapter = new MediaPlayerAdapter(audioType);
            adapter.play(audioType, filename);
        }
    }
}
package cn.com.pep.model.adapter.a1;
/**
 * 
 * @Title: AdapterPatternDemo  
 * @Description:  测试代码
 * @author wwh 
 * @date 2022-9-5 10:56:33
 */
public class AdapterPatternDemo {
    
    public static void main(String[] args) {
        AudioMediaPlayer player = new AudioMediaPlayer();
        player.play("mp3", "红日.mp3");
        player.play("mp4", "天下无贼.mp4");
        player.play("vlc", "平凡的世界.vlc");
    }
}

  UML类图:

  

1139198-20220905150708782-1169371469.png

  以上就是对象适配模式,目标接口MediaPlayer中有一个play(String,String)方法,而源对象接口AdvancedMediaPlayer中并没有这个方法,我们想通过目标接口的对象AudioMediaPlayer实现播放VCL、Mp4格式的文件,就需要一个MediaPlayerAdapter来对源对象(Mp4MediaPlayer、VLCMedaiPlayer)进行适配,它通过聚合的方式持有一个AdvancedMediaPlayer类型的引用,并且它还实现了与目标对象相同的接口,自然也就包含了和目标对象相同的方法,目标对象AudioMediaPlayer关联了MediaPlayerAdapter对象的引用,当然就可以实现播放VCL、Mp4格式的文件了;

  接下来我们说说类适配器模式

  类适配是通过Adapter类继承Adaptee(被适配类),同时实现Target接口(因为Java不支持多继承,所以只能通过实现接口的方式来实现多继承)来实现的,类适配器的重点在于类,是通过构造一个继承Adaptee类来实现适配器的功能的,而上面提到的对象适配器重点在于对象,是通过直接在Adapter中聚合Adaptee类来实现的,当需要调用特殊功能的时候,直接使用Adapter中聚合的那个Adaptee对象来调用特殊的功能即可;

  以下是类适配器的测试代码:

package cn.com.pep.model.adapter.a2;
/**
 * 
 * @Title: Adaptee  
 * @Description:  需要适配的类
 * @author wwh 
 * @date 2022-9-5 15:29:20
 */
public class Adaptee {
    
    public void specificRequest() {
        System.err.println("执行适配类的方法");
    }
}
package cn.com.pep.model.adapter.a2;
/**
 * 
 * @Title: Target  
 * @Description:  目标对象的接口
 * @author wwh 
 * @date 2022-9-5 15:31:05
 */
public interface Target {
    
    /**
     * @Title: calculate 
     * @Description:
     */
    public void calculate();
}
package cn.com.pep.model.adapter.a2;
/**
 * 
 * @Title: ConcreteTarget  
 * @Description:  目标对象
 * @author wwh 
 * @date 2022-9-5 15:49:50
 */
public class ConcreteTarget implements Target{

    @Override
    public void calculate() {
        System.err.println("执行目标类的方法");
    }
}
package cn.com.pep.model.adapter.a2;
/**
 * 
 * @Title: Adapter  
 * @Description:  类适配器,继承了需要适配的类,并且实现了目标对象的接口
 * @author wwh 
 * @date 2022-9-5 15:59:37
 */
public class Adapter extends Adaptee implements Target{

    @Override
    public void calculate() {
        specificRequest();
    }
}
package cn.com.pep.model.adapter.a2;

/**
 * 
 * @Title: ClassAdapterDemo
 * @Description: 测试类
 * @author wwh
 * @date 2022-9-5 15:57:58
 */
public class ClassAdapterDemo {

    public static void main(String[] args) {
        // 使用普通功能类
        Target concreteTarget = new ConcreteTarget();// 实例化一个普通类
        concreteTarget.calculate();

        // 使用特殊功能类,即适配类
        Target adapter = new Adapter();
        adapter.calculate();
    }
}

  类适配和对象适配的比较:

1、类适配使用继承,是静态定义的;而对象适配采用的是对象聚合的方式,是动态定义的;

  2、对于类适配器,由于适配器Adapter直接继承了Adaptee,使得Adapter不能和Adaptee的子类一起工作,因为继承是静态关系,当适配器继承了Adaptee之后,就不可能再去处理Adaptee的子类了;

  3、对于对象适配器,一个适配器可以把多种不同的源适配到同一个目标,换言之,同一个适配器可以把源类和它的子类都适配到目标接口,因为对象适配器采用的是对象聚合的方式,只要类型正确,是不是子类都无所谓;

  4、对于类适配,适配器可以重新定义Adaptee的部分行为,相当于子类覆盖父类的部分方法实现;

  5、对于对象适配器,想要直接重新定义Adaptee的行为比较困难,我们可以通过一个Adaptee的子类来重新定义Adaptee的行为,然后让适配器聚合这个子类来完成Adaptee类行为的重新定义;

  适配器模式的优缺点:

  1、更好的复用,系统需要使用现有类的时候,而此类的接口不符合系统的要求,我们就可以使用适配器模式让这些功能得到更好的复用;

  2、更好的扩展性,在实现适配器功能的时候,可以调用自己开发的功能,从而自然地扩展系统的功能;

  3、过多的使用适配器,会让系统非常零乱,不易整体进行把握。比如,明明看到调用的是A接口,其实内部被适配成了B接口的实现,一个系统如果太多出现这种情况,无异于一场灾难。因此如果不是很有必要,可以不使用适配器,而是直接对系统进行重构。

  建议尽量使用对象适配器的实现方式,多用合聚合/组合、少用继承。当然,具体问题具体分析,根据需要来选用实现方式,最适合的才是最好的。适配器不是在详细设计时添加的,而是解决正在服役的项目的问题。


Recommend

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK