07-结构型模式(中)

结构型模式(中)

  • 结构型模式共7种,如下所示,本文介绍适配器模式和装饰器模式。
  • 代理模式
  • 适配器模式
  • 装饰器模式
  • 外观模式
  • 桥接模式
  • 组合模式
  • 享元模式

一、适配器模式

1.1 定义

  • 适配器模式用于2个不兼容之间的接口的桥梁,尤其是涉及到不同的对接的时候,将一个接口转换为客户端锁期望的接口。比如我们期望的接口是T,那么
    我们目前有的接口可能是X,可能是Y,未来还可能是Z等等,为了保持良好的扩展性,我们提供一个适配器xt来将X转换为目标T接口,使用适配器yt将Y接口转换
    为目标T接口,比较典型的是支付接口,项目内的支付接口定义为MyPay,但是需要调用不同的第三方支付接口,支付宝,微信,以后可能还有银联,甚至其他接口。
    使用适配器模式,未来扩展的时候,只需要添加新的适配器就行了。

1.2 优点和使用场景

  • 优点:便于扩展
  • 缺点:增加了一定的复杂性
  • 场景:双方接口不便修改时,通过适配器适配接口,比如接入第三方组件

1.3 实现

  • 如果目标接口是T(Target),已有的适配者角色接口是X(Adaptee),适配者是XT(Adapter),那么XT需要实现T接口,因为它对外要提供给客户端使用,客户端期望的是T接口,
    另一方面XT内部要持有适配者(Adaptee)实例,因为它真正的还是要去调用Adaptee的接口

  • 类关系图:

image

1.3.1 目标接口(Target)
  • 目标接口是我的一个支付接口;
/**
 * 适配器中的目标接口,客户端期望得到的接口
 */
public interface MyPay {
    void pay(int money);
}
1.3.2 适配者角色(Adaptee)
  • 适配者角色,被适配的接口,比如第三方接口;这里代表2个第三方支付接口,为了简便没有使用接口,直接使用类,如果是接口的话,思想是一样的,但是稍微复杂点
public class AliPay {

    public void aliPay(int money) {
        System.out.println("aliPay pay...  " + money);
    }
}

public class WeChatPay {

    public void weChatPay(int money) {
        System.out.println("weChatPay pay...  "+ money);
    }

}
1.3.3 适配器角色(Adapter)
  • Adapter实现目标接口,因为它对外要提供给客户端使用
  • Adapter内部持有Adaptee实例,因为它真正的还是要去调用Adaptee的接口
public class AliPayAdapter extends AliPay implements MyPay {

    private AliPay aliPay;

    public AliPayAdapter() {
        this.aliPay = new AliPay();
    }

    @Override
    public void pay(int money) {
        System.out.println("调用第三方支付接口...");
        aliPay.aliPay(money);
    }
}

public class WeChatPayAdapter extends WeChatPay implements MyPay {

    private WeChatPay weChatPay;

    public WeChatPayAdapter() {
        this.weChatPay = new WeChatPay();
    }

    @Override
    public void pay(int money) {
        System.out.println("调用第三方支付接口...");
        weChatPay.weChatPay(money);
    }
}
1.3.4 测试
public class AdapterTest {
    public static void main(String[] args) {
        MyPay myPay = new AliPayAdapter();
        myPay.pay(100);
        MyPay myPay1 = new WeChatPayAdapter();
        myPay1.pay(100);
    }
}
  • 输出:
调用第三方支付接口...
aliPay pay...  100
调用第三方支付接口...
weChatPay pay...  100
  • 示例中我们通过调用自己的目标接口,成功得调用到了不同的第三方接口,而且如果有第三种支付方式,可以再扩展一个适配器角色即可,扩展会很方便。

二、装饰器模式

2.1 定义

  • 装饰器模式用于给类添加特定的功能。给类添加特定的功能实现方式包括继承和装饰,继承是静态的,不便于用户自己控制方式和时机,而且如果新功
    能存在多种组合,继承将使得类变得冗余臃肿,无法维护,但是装饰器模式会很灵活,可以按照自己的意愿添加新功能。

2.2 优点和使用场景

  • 优点:灵活,且添加的功能可以任意组合。
  • 场景:用于给核心类添加附加功能的场景。比如MyBatis中的缓存模块,使用装饰器模式给缓存添加了很多附加功能,比如添加FIFO功能,阻塞功
    能,日志功能,LRU功能等,或者JDK中的IO流。

2.3 实现

  • 下面场景是:有一个游戏,普通玩家只能有 登陆->玩游戏->退出这3个核心功能。在此基础上,增加了Vip玩家,Vip玩家登陆后会增加积分,在线时长达标后也会再次追加积分。
    另外有黑钻玩家,黑钻玩家玩DNF的时候经验翻倍,还有绿钻玩家,绿钻玩家玩QQ音乐的时候可以免费听歌,后面三种玩家都是在核心功能上添加了一定的附加功能,而且用户可
    能有组合,比如黑钻Vip,那么登陆要加积分,玩DNF还要经验加倍,我们来看如何实现。
2.3.1 接口
  • 接口,定义的是核心功能接口
public interface Player {

    void login();

    void play(String gameName);


    void loginOut();
}
2.3.2 实现类
  • 实现类实现了基本的主体功能
public class NormalPlayer implements Player {
    @Override
    public void login() {
        System.out.println("登陆...");
    }

    @Override
    public void play(String gameName) {
        System.out.println("玩: " + gameName);
    }


    @Override
    public void loginOut() {
        System.out.println("退出...");
    }
}
2.3.3 装饰器类
  • 根据需要添加的功能,给基本实现类添对应的功能
  • Vip用户,登陆送积分,在线时长达标后追加送积分
public class VipPlayer implements Player {

    private final Player delegate;

    public VipPlayer(Player delegate) {
        this.delegate = delegate;
    }

    private long loginTime;

    @Override
    public void login() {
        delegate.login();
        loginTime = System.currentTimeMillis();
        System.out.println("VipPlayer 登陆后增加5个积分...");
    }

    @Override
    public void play(String gameName) {
        delegate.play(gameName);
    }

    @Override
    public void loginOut() {
        if (System.currentTimeMillis() - loginTime > 200) {
            System.out.println("VipPlayer 在线时长达标,赠送5个积分...");
        }
        delegate.loginOut();
    }
}
  • 黑钻用户,玩DNF经验翻倍
public class BlackDiamondPlayer implements Player {

    private final Player delegate;

    public BlackDiamondPlayer(Player delegate) {
        this.delegate = delegate;
    }

    private long loginTime;

    @Override
    public void login() {
        delegate.login();
    }

    @Override
    public void play(String gameName) {
        delegate.play(gameName);
        if ("DNF".equals(gameName)) {
            System.out.println("玩游戏经验值翻倍....");
        }
    }

    @Override
    public void loginOut() {
        delegate.loginOut();
    }
}
  • 蓝钻用户,玩QQ音乐免费听歌
public class BlueDiamondPlayer implements Player {

    private final Player delegate;

    public BlueDiamondPlayer(Player delegate) {
        this.delegate = delegate;
    }

    private long loginTime;

    @Override
    public void login() {
        delegate.login();
    }

    @Override
    public void play(String gameName) {
        delegate.play(gameName);
        if ("QQMusic".equals(gameName)) {
            System.out.println("玩QQ音乐免费听歌....");
        }
    }

    @Override
    public void loginOut() {
        delegate.loginOut();
    }
}
2.3.4 测试
  • 测试不同类型的用户
public class DecratorTest {

    public static void main(String[] args) throws InterruptedException {

        System.out.println("普通玩家:");
        Player player = new NormalPlayer();
        player.login();
        player.play("英雄联盟");
        player.loginOut();

        System.out.println("\n" + "Vip玩家:");
        Player vipPlayer = new VipPlayer(player);
        vipPlayer.login();
        vipPlayer.play("DNF");
        Thread.sleep(300);
        vipPlayer.loginOut();

        System.out.println("\n" + "蓝钻玩家:");
        Player blueDiamondPlayer = new BlueDiamondPlayer(player);
        blueDiamondPlayer.login();
        blueDiamondPlayer.play("QQMusic");
        blueDiamondPlayer.loginOut();

        System.out.println("\n" + "黑钻玩家:");
        Player blackDiamondPlayer = new BlackDiamondPlayer(player);
        blackDiamondPlayer.login();
        blackDiamondPlayer.play("DNF");
        blackDiamondPlayer.loginOut();

        System.out.println("\n" + "黑钻Vip玩家:");
        Player blackVip = new BlackDiamondPlayer(new VipPlayer(new NormalPlayer()));
        blackVip.login();
        blackVip.play("DNF");
        Thread.sleep(300);
        blackVip.loginOut();


        System.out.println("\n" + "超级玩家:");
        Player superPlayer = new BlueDiamondPlayer(new BlackDiamondPlayer(new VipPlayer(new NormalPlayer())));
        superPlayer.login();
        superPlayer.play("DNF");
        superPlayer.play("QQMusic");
        Thread.sleep(300);
        superPlayer.loginOut();
    }
}
  • 输出:
普通玩家:
登陆...: 英雄联盟
退出...

Vip玩家:
登陆...
VipPlayer 登陆后增加5个积分...: DNF
VipPlayer 在线时长达标,赠送5个积分...
退出...

蓝钻玩家:
登陆...: QQMusic
玩QQ音乐免费听歌....
退出...

黑钻玩家:
登陆...: DNF
玩游戏经验值翻倍....
退出...

黑钻Vip玩家:
登陆...
VipPlayer 登陆后增加5个积分...: DNF
玩游戏经验值翻倍....
VipPlayer 在线时长达标,赠送5个积分...
退出...

超级玩家:
登陆...
VipPlayer 登陆后增加5个积分...: DNF
玩游戏经验值翻倍....: QQMusic
玩QQ音乐免费听歌....
VipPlayer 在线时长达标,赠送5个积分...
退出... 
  • 我们看到,各种不同的用户类型的输出都符合我们的预期,由其是组合的类型,后面两种组合类型,我们看最后一种用户的构造方式,有么有觉得很熟悉,JDK中的IO流就是典型的装饰器模式的应用。由此
    我们看到,通过装饰器模式,我给主要业务添加附加业务的时候很方便,而且可以任意组合搭配。
new BlueDiamondPlayer(new BlackDiamondPlayer(new VipPlayer(new NormalPlayer())));
new BufferedReader(new FileReader(new File("test.txt")));

三、小结

3.1 装饰器和代理

  • 从上面的例子我们可以看到,装饰器模式的扩展性很强,比如你有ABCD四个附加功能,使用装饰器可以很轻松的添加到主要功能上去,还可以组合搭配,使用代理就不方便,代理模式主要是对某个类增强
    一些功能,一般是前后拦截,具体阅读[13-Mybatis源码和设计模式-4(缓存模块和装饰器模式)] 到时候会有深刻体验。

3.2 适配器和代理

  • 前者主要是用于接口对接,第三方接口的对接,比如双方的接口都不好改变,但是对于的功能调用需要对接,就引入一个适配器,而且扩展也很方便。后者核心还在在代理增强。

  • 关于适配器模式,这里只是简单的给出了适配器模式的使用示例,在MyBatis源码的日志模块中有适配器的经典使用,可以阅读 [11-Mybatis源码和设计模式-2(日志模块和适配器模式)]
  • 关于装饰器模式,这里只是简单的给出了装饰器模式的使用示例,在MyBatis源码的缓存模块中有装饰器的经典使用,可以阅读 [13-Mybatis源码和设计模式-4(缓存模块和装饰器模式)]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值