Android开发学习笔记之设计模式——适配器模式&代理模式

Android开发学习笔记之设计模式——适配器模式&代理模式

结构型模式即通过将类或对象按某种布局组成更大、更复杂的结构的设计模式。通常可以分为类结构型模式和对象结构型模式,前者采用继承机制来组织接口和类,后者釆用组合或聚合来组合对象。

适配器模式

概述

适配器模式即将一个接口转换成客户希望的另一个接口,适配器模式使接口不兼容的那些类可以一起工作,又可称为包装器(Wrapper)。适配器模式既可以作为类结构型模式,也可以作为对象结构型模式。

通常情况下,客户端可以通过目标类的接口访问它所提供的服务。有时,现有的类可以满足客户类的功能需要,但是它所提供的接口不一定是客户类所期望的,这可能是因为现有类中方法名与目标类中定义的方法名不一致等原因所导致的。

在这种情况下,现有的接口需要转化为客户类期望的接口,这样保证了对现有类的重用。通过适配器模式,我们可以创建一个适配器类提供客户类需要的接口,把客户类的请求转化为对适配者的相应接口的调用。也就是说:当客户类调用适配器的方法时,在适配器类的内部将调用适配者类的方法,而这个过程对客户类是透明的,客户类并不直接访问适配者类。因此,适配器可以使由于接口不兼容而不能交互的类可以一起工作。

比如,Java中的JDBC实际上就是使用了适配器模式,因为数据库的查询需要使用SQL语句,而Java通过提供了JDBC使得开发者能够通过java程序来连接数据库并操作数据。

在Android开发过程中,如RecyclerView、ListView等相关类使用时的各种Adapter实际上也是使用了适配器模式,将两个不相关的类进行适配,从而实现了相关的功能。

应用场景

  • 客户端所需要使用的类与当前代码接口不兼容时,可以使用适配器模式,创建一个适配器类,作为中间层来进行适配调用相关接口。
  • 想要建立一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作。

优缺点

优点:

  • 将目标类和适配者类解耦,通过引入一个适配器类来重用现有的适配者类,而无须修改原有代码。符合开闭原则
  • 增加了类的透明性和复用性,将具体的实现封装在适配者类中,对于客户端类来说是透明的,而且提高了适配者的复用性。
  • 灵活性和扩展性都非常好,通过使用配置文件,可以很方便地更换适配器,也可以在不修改原有代码的基础上增加新的适配器类。
  • 对于类适配器而言,由于继承了适配者类,可以重写适配者类中的方法,更加灵活;而对于对象适配器而言,以把多个不同的适配者适配到同一个目标。

缺点:

  • 代码整体复杂度增加, 因为你需要新增一系列接口和类。 有时直接更改服务类使其与其他代码兼容会更简单。
  • 对于类适配器而言,如果不支持多继承,一个适配器只能适配一个类;对于对象适配器,无法重写适配器方法。

实现

适配器模式可以分为类适配器和对象适配器两种,前者可以通过多继承的方式来实现,后者一般通过持有适配者类的对象实现。两者都可以分为三个部分,如下:

  • 适配器:适配器模式的核心类,它是一个转换器,通过继承或引用适配者的对象,把适配者接口转换成目标接口,让客户按目标接口的格式访问适配者。
  • 适配者:它是被访问和适配的现存组件库中的组件接口。
  • 目标接口:当前系统业务所期待的接口,它可以是抽象类或接口。

使用类适配器的结构图如下:

在这里插入图片描述

使用对象适配器的结构图如下:

在这里插入图片描述

由于类适配器需要通过继承的方式实现,且需要了解适配者类的内部实现,耦合性更高,根据合成复用原则我们一般很少使用,而是使用对象适配器。

我们考虑一个“方钉圆孔”的实例,假如存在方孔、圆孔、方钉和圆钉四个对象,方孔和圆孔都存在对应的matching方法用于对应孔径,只有对应的孔径才能使用,一般情况下,我们可能在方孔中使用方钉、圆孔中使用圆钉,所有方孔匹配的是边长而圆孔匹配的是半径,此时如果我们需要方孔匹配圆钉这就无法直接使用matching方法来实现了。

首先,我们创建方钉、圆孔的类,如下:

/**
 * 圆孔
 */
class CircleHole(val radius: Float) {

    /**
     * 匹配孔径方法,匹配半径
     */
    fun matching(radius: Float) : Boolean{
        return this.radius == radius
    }

}

/**
 * 方钉
 */
class SquarePeg(private val width: Float){

    /**
     * 获取钉子的边长
     */
    fun getPegWidth(): Float = width

}

我们可以看到,圆孔只能通过匹配半径的方式来判断是否匹配,而方钉只能获取到其边长,此时,我们就可以创建一个方钉的适配器用于获取其半径,此时方钉就是适配者,如下:

/**
 * 方钉适配器
 */
class SquarePegAdapter(private val squarePeg: SquarePeg) {
	
    /**
 	* 适配圆孔,获取半径
 	*/
    fun getPegRadius() : Float = squarePeg.getPegWidth()* (sqrt(2f)/2)

}

然后,在进行匹配时,我们只需要构建一个适配器的对象,通过适配器即可知道方钉是否和圆孔匹配了。

代理模式

概述

代理模式即给某一个对象提供一个代 理,并由代理对象控制对原对象的引用。

在某些情况下,我们可能不希望直接访问某个对象,此时我们就可以通过创建一个称之为“代理”的第三者来实现 间接引用。代理对象可以在客户端和目标对象之间起到中介的作用,并且可以通过代理对象去掉客户不能看到 的内容和服务或者添加客户需要的额外服务。

应用场景

  • 虚拟代理:当我们需要创建一个资源消耗较大的对象,我们可以延迟创建,先创建一个消耗相对较小的对象来表示,真实对象只在需要时才会被真正创建。
  • 远程代理:为了隐藏目标对象存在于不同地址空间的事实,方便客户端访问,我们可以为一个位于不同的地址空间的对象提供一个本地的代理对象,将所有与远程服务对象通信等复杂细节都放于本地代理中。
  • 安全代理:当我们只希望特定的客户端对象才能访问真正的服务对象时,我们可以使用代理模式来控制访问权限,仅仅在客户端满足某些条件时才能将请求传给对象。
  • 日志记录代理:当我们希望记录对真正主题对象的操作时,我们可以使用代理模式,记录相关日志。
  • 缓存代理:当我们需要访问的对象较大时,或者需要耗费较多系统资源时,我们可以使用代理模式来保存相关缓存,减少重复创建对象的资源消耗。
  • 智能引用代理:当我们持有一些重量级对象时,我们可以使用代理模式将所有获取了指向服务对象或其结果的客户端记录在案。 代理会时不时地遍历各个客户端, 检查它们是否仍在运行。 如果相应的客户端列表为空, 代理就会销毁该服务对象, 释放底层系统资源。

优缺点

优点:

  • 代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用;
  • 代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度,增加了程序的可扩展性。
  • 你可以在客户端毫无察觉的情况下控制服务对象。对客户端添加相关操作。
  • 通过代理模式,可以减少资源消耗,提高性能,控制使用权限等。

缺点:

  • 由于在客户端和真实主题之间增加了代理对象,因此 有些类型的代理模式可能会造成请求的处理速度变慢。
  • 代理模式会造成系统设计中类的数量增加,增加系统复杂度。

实现

代理模式的具体实现和应用场景多种多样,但是其基本结构和实现是相同的,通常我们会创建一个代理类,实现实际服务对象中的所有对外开发的接口,然后在代理对象中持有一个服务对象的引用,客户端使用时只需要调用代理类的接口即可,而代理类会根据情况来调用服务对象的对应接口,并添加相关操作等。具体结构如下:

  • 抽象主题类:通过接口或抽象类声明真实主题和代理对象实现的业务方法。
  • 具体主题类:实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象。
  • 代理类:提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能。

如下图所示:
在这里插入图片描述

假如,我们存在一个图片下载工具类ConmmonImageLoad,提供了downloadImage方法下载图片,但是,项目中我们可能会下载同一张图片,此时如果我们一直重复下载,那么效率就会变得很低了,此时,我们可以使用代理模式,增加一个缓存,如果存在缓存就直接使用缓存即可。这样就能够减少网络请求的次数,提高性能。

首先,创建抽象主题类和具体主题类,如下:

/**
 * 抽象主题类
 */
interface ImageLoad{
    fun download(url:String) : File
}

/**
 * 具体主题类
 */
class CommonImageLoad : ImageLoad{
    
    override fun download(url: String): File {
        //TODO 下载图片相关逻辑
        return File("")
    }

}

然后,创建代理类,如下:

/**
 * 代理类,单例
 */
object ImageLoadProxy : ImageLoad {

    /**
     * 图片缓存,url->path
     */
    private val cache = mutableMapOf<String,String>()
    
    private val imageLoad by lazy { CommonImageLoad() }

    override fun download(url: String): File {
        //使用缓存
        if (cache.contains(url)) return File(cache[url])
        //无缓存,下载图片,然后进行缓存
        val file = imageLoad.download(url)
        cache[url] = file.path
        return file
    }
}

如上述代码,通过代理模式,我们同样使用了CommonImageLoad下载图片,但是在下载前会先判断是否存在缓存,如果存在缓存则使用缓存,这样就提供了app性能。同时,我们还可以在下载图片前,记录相关日志。

我们可以看到,代理模式的结构实际上非常简单,最重要的是我们如何灵活运用代理模式来为我们的开发提供性能上的优化,这就需要我们具体问题具体分析了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值