前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >设计模式——适配器模式和装饰者模式

设计模式——适配器模式和装饰者模式

作者头像
程序员爱酸奶
发布2020-03-04 18:22:25
9750
发布2020-03-04 18:22:25
举报
文章被收录于专栏:程序员爱酸奶程序员爱酸奶

写在之前

公众号感觉很久没有更新啦,首先谢谢大家的支持哈哈,好友问我最近公众号怎么不更新了,才意识到自己变懒啦,对不起大家的支持和鼓励,所以赶紧补补,以后也希望自己继续努力,坚持下去,也感谢大家一如既往的支持


正文

适配器模式

网上有很多适配器模式的定义和讲解,这里我就记录下自己对适配器模式的理解,更多的大家可以在网上看。

适配器模式到底是什么,也就是所谓的定义:

个人理解,适配适配当然是之前不合适,通过一些手段从而合适了,这些手段就是适配。比方说你买了一个港版的iPhone,但是你现在在内地,怎么对iPhone充电呢。这个时候你需要通过转接口达到充电的目的。而这个转接口就是所谓的适配器。

你想继承或实现类A,但是有一些方法在类B中实现。你想用类B的方法,但是又不能直接实现或者继承,这个时候就需要一个适配器(抽象类)来继承或实现类B中想使用的方法,然后类B继承这个抽象类。

适配器模式的一个简单的例子:

目录

En110v.java

代码语言:javascript
复制
package test;
/**
 * 中国香港充电方式
 */
public interface En110v {
    void enCharging(String address);
}

Cn220v.java

代码语言:javascript
复制
package test;
/**
 * 内地充电方式
 */
public interface Cn220v {
    void cnCharging(String address);
}

IphoneCharging.java

代码语言:javascript
复制
package test;
import org.apache.commons.collections.bag.SynchronizedSortedBag;
/**
 * @ClassName IphoneCharging
 * @Description 港版iPhone充电
 */
public class IphoneCharging implements En110v{
    @Override
    public void enCharging(String address) {
        System.out.println(address+",港版手机在充电中。");
    }
}
/**
 * 那想要在内地充电,怎么办呢.
 * 我们不能直接把插头插在220v的插座上,正如我们不能直接实现Cn220v这个类一样
 * 所以我们需要一个转换头来达到目的
 * 也就是这里需要适配器类Adapter
 */

Adapter.java

代码语言:javascript
复制
package test;
/**
 * @ClassName Adapter
 * @Description 适配器
 */
public class Adapter extends IphoneCharging implements Cn220v{
    @Override
    public void cnCharging(String address) {
        enCharging(address);
        System.out.println("使用适配器");
    }
}
/**
 * 这个适配器就是在进行内地充电的转成港版充电
 * 也就是表面上执行A方法,实际上执行的是B方法
 */

Charging.java

代码语言:javascript
复制
package test;
/**
 * @ClassName Charging
 * @Description 充电
 */
public class Charging {
    private static final String MAINLAND="内地";
    private static final String HONGKONG="中国香港";
    public static void main(String[] args) {
        //中国香港充电,直接创建IponeChraging实例就可以
        IphoneCharging hongkong=new IphoneCharging();
        hongkong.enCharging(HONGKONG);
        //在内地充电,没有可以直接内地充电的实例,所以只能用转接头
        Adapter adapter=new Adapter();
        adapter.cnCharging(MAINLAND);
    }
}
/**
 * 特别说明
 * 在国内充电只能用cnCharging方式,
 * 在中国香港充电只能用enCharging方式,这个是前提。
 * 不然直接 adapter.enCharging(MAINLAND);程序不抱错,但是不符合实情了。
 */

结果:

代码语言:javascript
复制
中国香港,港版手机在充电中。
内地,港版手机在充电中。
使用适配器

可能这个例子不太好,但是也刚好我们理解一下适配器模式吧,将一个类的接口转换成客户希望的另外一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。

应用场景:

主要解决在软件系统中,常常要将一些"现存的对象"放到新的环境中,而新环境要求的接口是现对象不能满足的。核心思想:适配器继承或依赖已有的对象,实现想要的目标接口。

优点:

1、可以让任何两个没有关联的类一起运行。

2、提高了类的复用。

3、增加了类的透明度。

4、灵活性好。

缺点:

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

2.由于 JAVA 至多继承一个类,所以至多只能适配一个适配者类,而且目标类必须是抽象类。


装饰者模式

装饰装饰当然是一个东西还能用,但是呢达不到理想的效果想要更好一点。所以就在原来的基础上进行装饰从而达到目的,这就是装饰者模式啦,和适配器区别在于,适配器模式是原来的东西在现在的环境不能使用,为了达到使用的效果需要使用适配器才能使用。而装饰者模式是原来的东西在现在的环境还能用,但是呢,你想新增一些特性,所以就需要对原来的东西进行装饰,达到想要的效果。

装饰者模式:就是动态地把职责附加到已有对象上去,实现功能扩展。这种特性,使得装饰者模式提供了比继承更具有弹性的解决方案。

下面举一个比较通俗的例子:

送你一个女朋友怎么样!想她是美国金发大妞?浪漫的法国女郎?国产的萌萌哒妹子?OK,没问题!你想她是哪个国家的就是哪个国家的。她们有不同的爱好或者习惯,每一个这样的女孩,都可以看作是一个 Java 类。我知道此刻你一定在想,这一个、那一个...那岂不是有很多类?这种方式没有扩展性,每当有一个新类型的女孩,就得又新建一个类,这简直就是类爆炸啊!

所以这个时候就可以用到装饰者模式,先讲一些共有的特性提取出来形成积累,然后需要一个装饰者的抽象类,来继承基类,其他的不同特性每个特性都自成一个类来继承这个装饰者的类。从而达到通过不同的装饰达到不用的效果,就像房子通过不同的装修达到不同的效果。

下面来看下代码:

整体的目录:

Girl.java

代码语言:javascript
复制
package cn.zlf.code.decoratormode;
/**
 * 抽象类 girl ,作为基类
 */
public abstract class Girl {
    String description;
    public String getDescription() {
        return description;
    }
}

EnGirl.java

代码语言:javascript
复制
package cn.zlf.code.decoratormode;
/**
 * 外国女孩的类
 */
public class EnGirl extends Girl{
    private static final String ENGIRL="我是外国女孩,";
    public  EnGirl(){
        description=ENGIRL;
    }
}

CnGirl.java

代码语言:javascript
复制
package cn.zlf.code.decoratormode;
/**
 *国产妹子
 */
public class CnGirl extends Girl{
    private static final String CNGIRL="我是国产妹子,";
    public  CnGirl(){
        description=CNGIRL;
    }
}

GirlDecorator.java

代码语言:javascript
复制
package cn.zlf.code.decoratormode;
/**
 * 装饰者
 */
public abstract class GirlDecorator extends Girl{
    public abstract String getDescription();
}


/**
 * 好了,接下来我们想要什么样的女朋友呢
 * 比如说:国产黑色长发妹子。
 * 那我们现在该怎样使用这个装饰者呢
 * 需要一个BlackLongHairGirl 来集成这个装饰者
 */

BlackLongHairGirl.java

代码语言:javascript
复制
package cn.zlf.code.decoratormode;
/**
 * 国产黑色长发妹子
 */
public class BlackLongHairGirl extends GirlDecorator{
    private  Girl girl;
    private static final String BLACKLongHair="黑色长发,";
    public BlackLongHairGirl(Girl girl){
        this.girl=girl;
    }
    @Override
    public String getDescription() {
        return girl.getDescription()+BLACKLongHair;
    }
}
/**
 * 上面黑色和长发按正常的实际上是不能放一起的,毕竟黑色的一定是长发。
 * 所以在实际开发时
 * 要注意充分考虑代码的可重用性和拓展性
 * 这里就暂时不处理了,我们再写一个类来表示一个新特征
 * 比如说想要一个黑色长发大长腿的女朋友
 * 现在已经有黑色长发了
 * 所以还需要一个大长腿
 * 所以需要创建一个BigLongLegsGirl
 */

BigLongLegsGirl.java

代码语言:javascript
复制
package cn.zlf.code.decoratormode;
/**
 * 大长腿女孩
 * 继承装饰者,这里为什么不直接继承BlackLongHairGirl呢
 * 就是考虑代码的拓展性,万一下次想要个短发长腿的不就炸了
 * 相当于把不同的属性彻底分开降低耦合
 */
public class BigLongLegsGirl extends GirlDecorator{
    private  Girl girl;
    private static final String BIGLONGLEGS="大长腿,";
    public BigLongLegsGirl(Girl girl){
        this.girl=girl;
    }
    @Override
    public String getDescription() {
        return girl.getDescription()+BIGLONGLEGS;
    }
}
/**
 * 好了,接下来写一个类来创建你想要的女朋友吧
 * CreateGirlfriend
 */

CreateGirlfriend.java

代码语言:javascript
复制
package cn.zlf.code.decoratormode;
/**
 * 创建你想要的女朋友
 */
public class CreateGirlfriend {
    public static void main(String[] args) {
        //想要一个国产妹子
        Girl g1=new CnGirl();
        System.out.println(g1.getDescription());


        //现在想要一个国产黑色长发妹子
        Girl g2=new BlackLongHairGirl(g1);
        System.out.println(g2.getDescription());


        //现在想要一个国产黑色长发大长腿的妹子
        Girl g3=new BigLongLegsGirl(g2);
        System.out.println(g3.getDescription());


        System.out.println("----------------");
        //那如果我想创建一个国外黑色长发大长腿的女朋友呢,一步到位
        Girl g4=new BigLongLegsGirl(new BlackLongHairGirl(new EnGirl()));
        System.out.println(g4.getDescription());
    }
}

结果:

代码语言:javascript
复制
我是国产妹子,
我是国产妹子,黑色长发,
我是国产妹子,黑色长发,大长腿,
----------------
我是外国女孩,黑色长发,大长腿,

上面这个例子就简单的用到了装饰者模式啦,是不是感觉很熟悉其实,在我们平时工作写代码的时候其实这种模式经常用到,只是自己不自知,或者用的不是太规范罢了。所以我们以后工作写代码的时候不妨也按这种思路设计,多想想以后的拓展性和可变性。不然好不容做出一个产品,结果因为每个客户有些特定的需求又得做一个相似的产品就很费时费力了。所以当你需要动态地给一个对象添加功能,实现功能扩展的时候,就可以使用装饰者模式。在Java IO 类中有一个经典的装饰者模式应用, BufferedReader 装饰了 InputStreamReader.

不过装饰者模式缺点也很明显,看上面的例子就可以看出来,才一个这么简单的例子就有好几个类,每一个特性都单独的一个类,这样必然会造成很多相似的代码,且让整个项目显得很臃肿,所以到底使用那种模式还是仁者见仁智者见智了,毕竟没有什么是绝对的,最合适的就是最好的。

最后总结一下适配器模式和装饰者模式的区别:

关于新职责:适配器也可以在转换时增加新的职责,但其主要目的并不在此;而装饰者模式主要目的,就是给被装饰者增加新职责用的。

关于原接口:适配器模式是用新接口来调用原接口,原接口对新系统来说是不可见或者说不可用的;而装饰者模式原封不动的使用原接口,系统对装饰的对象也通过原接口来完成使用。

关于其包裹的对象:适配器是知道被适配者的详细情况的(就是那个类或那个接口);而装饰者只知道其接口是什么,至于其具体类型(是基类还是其他派生类)只有在运行期间才知道。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-05-26,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 程序员爱酸奶 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档