安卓开发用到的设计模式(2)结构型模式

本文首发地址 https://h89.cn/archives/398.html

1. 适配器模式(Adapter Pattern)

适配器模式将一个类的接口转换成客户端所期望的另一个接口,使得原本由于接口不兼容而不能一起工作的类可以一起工作。

在Android中的主要应用场景:

  1. RecyclerView和ListView的Adapter

    • 详细描述: 在Android开发中,Adapter 是连接数据源(如 List<String>Cursor)与 UI 组件(如 RecyclerViewListView)的关键桥梁。它负责将数据项转换为可以在屏幕上显示的 View 对象。通过实现 Adapter 接口,开发者可以自定义数据如何绑定到列表项的布局上,从而实现数据和 UI 的解耦。
    • 具体示例: 创建一个自定义的 RecyclerView.Adapter,在 onCreateViewHolder 中加载列表项布局,在 onBindViewHolder 中将数据绑定到布局中的各个 View 上。
  2. 网络请求适配

    • 详细描述: 在处理来自不同来源(如 RESTful API、GraphQL 或其他服务)的网络响应时,数据格式可能不一致。适配器模式可以用来将这些不同格式的响应数据转换为应用程序内部统一的数据模型,方便后续处理和使用。这有助于隔离外部数据格式的变化对核心业务逻辑的影响。
    • 具体示例: 使用 Retrofit 这样的网络库时,可以通过定义不同的 ConverterFactory 来适配 JSON、XML 或其他格式的响应数据,将其自动转换为 Java/Kotlin 对象。或者手动编写适配器类,将特定格式的响应数据解析并映射到应用的数据模型中。

2. 装饰器模式(Decorator Pattern)

装饰器模式允许向一个现有的对象添加新的功能,同时又不改变其结构。这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装。

在Android中的主要应用场景:

  1. Context的扩展

    • 详细描述: Android中的 ContextWrapper 类是装饰器模式的一个典型应用。它包装了一个 Context 对象,并提供了相同的方法接口,但允许在其基础上添加或修改行为。ActivityServiceApplication 类都继承自 ContextWrapper,通过这种方式,它们在提供基本的 Context 功能的同时,也扩展了各自特有的功能(如管理生命周期、启动组件等)。
    • 具体示例: 创建一个自定义的 ContextWrapper 子类,可以在调用原始 Context 方法之前或之后执行额外的逻辑,例如统一处理权限检查或资源加载。
  2. View的自定义装饰

    • 详细描述: 装饰器模式可以用于动态地为 Android 的 View 组件添加额外的视觉效果或行为,而无需修改 View 的原有类结构。这可以通过创建包装 View 的自定义 DrawableViewGroup 来实现,这些包装器在绘制或处理事件时,在调用原始 View 的方法前后添加自己的逻辑。
    • 具体示例: 创建一个自定义的 Drawable,它可以包装一个现有的 View 的背景 Drawable,并在其外部绘制边框或阴影。或者创建一个自定义的 ViewGroup,它包含一个子 View,并在子 View 的基础上添加点击波纹效果或拖拽功能。

3. 代理模式(Proxy Pattern)

代理模式为其他对象提供一种代理以控制对这个对象的访问。

在Android中的主要应用场景:

  1. 图片加载代理

    • 详细描述: 在 Android 应用中加载图片,尤其是来自网络或本地大图时,通常需要处理缓存、异步加载、缩放和内存管理等复杂问题。图片加载库(如 Glide、Picasso、Coil)内部就广泛使用了代理模式。它们提供一个简单的接口供开发者调用(代理),而实际的加载、缓存、解码等复杂操作则由背后的真实对象(被代理对象)完成。代理层可以控制对真实加载逻辑的访问,例如在加载前检查缓存,加载失败时显示占位图等。
    • 具体示例: 使用 Glide 加载图片时,我们通常写 Glide.with(context).load(imageUrl).into(imageView);。这里的 Glide 对象或其内部组件就充当了代理,它隐藏了图片加载的复杂性,并在内部管理线程池、内存缓存、磁盘缓存等。
  2. 权限检查代理

    • 详细描述: 在 Android 中,访问某些敏感操作(如调用系统服务、访问联系人、使用相机等)需要特定的权限。代理模式可以用于在执行这些操作之前插入权限检查的逻辑。这可以通过静态代理(手动创建代理类)或动态代理(在运行时生成代理类)来实现。动态代理常用于实现面向切面编程 (AOP),将权限检查、日志记录、性能监控等“横切关注点”从核心业务逻辑中分离出来。
    • 具体示例: 可以创建一个代理类,包装一个需要权限才能调用的方法。在调用代理方法时,先检查是否有所需权限,如果权限不足则提示用户或请求权限,只有权限满足时才调用被代理对象的实际方法。使用 AspectJ 或 Kotlin 的 AOP 框架,可以通过注解等方式,在方法执行前自动插入权限检查的逻辑,底层实现就依赖于动态代理。

4. 桥接模式(Bridge Pattern)

桥接模式将抽象部分与实现部分分离,使它们都可以独立地变化。

在Android中的主要应用场景:

  1. 主题切换

    • 详细描述: 桥接模式在 Android 主题切换中非常有用。它可以将界面的抽象表示(例如,一个按钮应该是什么颜色、字体大小等)与具体的实现(例如,日间主题的颜色值、夜间主题的颜色值)分离开来。这样,你可以独立地改变主题的实现(添加新的主题或修改现有主题的颜色、样式等),而无需修改使用这些主题的界面组件的代码。
    • 具体示例: 定义一个抽象的 Theme 接口,包含获取颜色、字体等方法。然后创建 DayThemeNightTheme 等实现类。界面组件(如 Button)持有 Theme 接口的引用,通过调用接口方法获取样式信息,而不是直接依赖具体的 DayThemeNightTheme 类。切换主题时,只需改变 Button 持有的 Theme 实例即可。
  2. 数据存储

    • 详细描述: 在 Android 应用中,数据可以存储在多种地方,如 SQLite 数据库、SharedPreferences、文件或网络存储。桥接模式可以将数据访问的抽象接口(如 UserDataRepository 接口,包含 getUser()saveUser() 等方法)与具体的存储实现(如 SQLiteUserRepositorySharedPreferencesUserRepository)分离开。这样,你可以根据需求轻松切换底层存储方式,而上层业务逻辑代码只需依赖抽象接口,无需关心具体的存储细节。
    • 具体示例: 定义一个 UserRepository 接口。实现 SQLiteUserRepositorySharedPreferencesUserRepository 类。在应用的数据层或业务逻辑层,通过依赖注入等方式获取 UserRepository 的实例。根据配置或用户选择,可以注入不同的实现类,从而在运行时切换数据存储方式。

5. 组合模式(Composite Pattern)

组合模式将对象组合成树形结构以表示"部分-整体"的层次结构。

在Android中的主要应用场景:

  1. View树结构

    • 详细描述: Android 的 UI 界面是典型的树形结构,其中 ViewGroup 作为复合对象(Composite),可以包含其他 ViewViewGroup(叶子或复合对象)。View 作为叶子对象,不能包含其他组件。组合模式使得开发者可以统一处理单个 View 对象和 ViewGroup 组合对象,例如对整个 View 树进行遍历、测量、布局和绘制操作,而无需区分是单个 View 还是 View 组。
    • 具体示例: 在布局文件中,我们可以嵌套使用 LinearLayoutRelativeLayoutViewGroup 来组织 TextViewButtonView。例如,一个 LinearLayout 可以包含多个 TextView 和一个嵌套的 AnotherViewGroup,这种层级关系就是组合模式的应用。对根 ViewGroup 调用 measure()layout() 方法,会递归地调用其所有子 View 的相应方法。
  2. 菜单结构

    • 详细描述: Android 的菜单系统(如选项菜单 Options Menu、上下文菜单 Context Menu)也常使用组合模式来构建层级结构。Menu 对象可以包含 MenuItem(叶子)和 SubMenu(复合对象)。SubMenu 自身也是一个 Menu,可以包含更多的 MenuItemSubMenu,从而形成多级菜单。这使得开发者可以一致地处理单个菜单项和包含子菜单的菜单项。
    • 具体示例: 在定义菜单资源文件时,可以使用 <menu> 标签作为根,包含 <item> 标签表示单个菜单项,以及嵌套的 <menu> 标签表示子菜单。例如:
      <menu>
          <item android:id="@+id/action_settings" android:title="Settings"/>
          <item android:id="@+id/action_share" android:title="Share">
              <menu>
                  <item android:id="@+id/action_share_email" android:title="Email"/>
                  <item android:id="@+id/action_share_social" android:title="Social Media"/>
              </menu>
          </item>
      </menu>

      这里的 <menu><item> 结构就体现了组合模式。

6. 享元模式(Flyweight Pattern)

享元模式主要用于减少创建对象的数量,以减少内存占用和提高性能。

在Android中的主要应用场景:

  1. Message对象池

    • 详细描述: 在 Android 的 Handler 消息机制中,为了避免在高频率发送消息时频繁创建和销毁 Message 对象导致的性能开销,系统内部使用了享元模式。Message 对象被放入一个对象池中进行复用。当需要发送消息时,优先从池中获取可用的 Message 对象,使用完毕后,通过 Message.recycle() 方法将其回收到池中,而不是直接销毁。这样大大减少了对象的创建数量,降低了内存压力。
    • 具体示例: 在使用 Handler 发送消息时,通常会通过 Message.obtain() 方法从消息池中获取一个 Message 对象,而不是直接 new Message()。例如:Message msg = Message.obtain(handler, what, obj); handler.sendMessage(msg);
  2. 线程池

    • 详细描述: 线程的创建和销毁是比较耗费资源的。享元模式的思想也体现在线程池的应用中。线程池维护一组可重用的线程,当有任务到来时,优先使用池中空闲的线程来执行任务,而不是为每个任务都创建新线程。任务执行完毕后,线程不会被销毁,而是返回到池中等待下一个任务。这减少了线程创建和销毁的开销,提高了系统的响应速度和吞吐量。
    • 具体示例: Android 中的 AsyncTask 内部就使用了线程池来管理后台任务的执行。虽然 AsyncTask 在 Android 11 中已被废弃,但其底层实现展示了线程池的应用。开发者也可以使用 java.util.concurrent 包中的 ThreadPoolExecutor 等类来创建自定义线程池,实现线程的复用。

7. 外观模式(Facade Pattern)

外观模式为子系统中的一组接口提供一个一致的界面,这个界面使得子系统更加容易使用。

在Android中的主要应用场景:

  1. 网络请求封装

    • 详细描述: 在 Android 应用中进行网络请求通常涉及构建请求、处理连接、解析响应、错误处理等一系列复杂步骤。外观模式可以创建一个简单的接口(外观),隐藏底层网络库(如 OkHttp, Retrofit)的复杂性。开发者只需调用外观提供的少量方法,即可完成网络请求,而无需直接与复杂的底层 API 交互。这简化了网络请求的使用,并使得替换底层网络库变得更容易。
    • 具体示例: 创建一个 ApiManager 类,其中包含 getUser(userId, callback)postData(data, callback) 等方法。在这些方法内部,使用 Retrofit 或 OkHttp 执行实际的网络请求,处理响应和错误,然后通过回调接口将结果返回给调用者。调用者只需与 ApiManager 交互,无需了解 Retrofit 或 OkHttp 的具体用法。
  2. 数据库操作

    • 详细描述: Android 的 SQLite 数据库操作(如打开数据库、创建表、执行 SQL 语句、管理 Cursor)相对底层和繁琐。外观模式可以提供一个更高级、更简单的接口来执行常见的数据库操作(CRUD:Create, Read, Update, Delete)。例如,可以创建一个 DatabaseHelperRepository 类作为外观,封装底层的 SQLiteOpenHelper 或 Room 库的细节,提供如 getAllUsers()addUser(user) 等方法。
    • 具体示例: 使用 Android Architecture Components 中的 Room 持久性库时,@Dao 接口就充当了外观的角色。开发者只需定义接口方法并使用注解,Room 会自动生成底层实现代码,封装了 SQLite 的复杂性。调用者只需调用 DAO 接口的方法即可进行数据库操作。
  3. 媒体播放

    • 详细描述: 使用 Android 原生的 MediaPlayerExoPlayer 进行媒体播放涉及状态管理(准备、播放、暂停、停止)、错误处理、音频焦点管理等多个方面。外观模式可以创建一个简单的播放器控制接口,隐藏这些复杂的状态转换和细节。这个外观对象负责协调底层播放器的各种操作,提供如 play(url)pause()stop() 等简单方法。
    • 具体示例: 创建一个 MusicPlayerFacade 类,内部持有 MediaPlayerExoPlayer 实例。MusicPlayerFacade 提供 play(String audioUrl)pause()resume()stop() 等方法。在 play() 方法中,处理 MediaPlayersetDataSourceprepareAsyncstart 等调用和状态监听。调用者只需调用 MusicPlayerFacade 的方法,无需直接操作 MediaPlayer 的复杂状态机。

这些结构型设计模式在Android开发中的应用不仅能够提高代码的复用性和可维护性,还能使代码结构更加清晰。通过合理使用这些模式,我们可以更好地组织代码,提高开发效率,并且使应用程序更容易维护和扩展。在实际开发中,我们应该根据具体场景选择合适的设计模式,避免过度设计,确保代码的简洁性和可读性。


相关系列文章:


本文链接:安卓开发用到的设计模式(2)结构型模式 - https://h89.cn/archives/398.html

版权声明:原创文章 遵循 CC 4.0 BY-SA 版权协议,转载请附上原文链接和本声明。
微信公众号-清霜安卓

标签: 设计模式, 结构型模式, 适配器模式, 装饰器模式, 代理模式, 桥接模式, 组合模式, 享元模式, 外观模式

添加新评论