第 1 章 设计模式

第 1 章 设计模式

长期以来,设计模式一直被视作解决常见软件设计问题最可靠、最有效的途径之一。各类设计模式提供了可复用的通用解决方案,用于解决常见的开发问题,例如如何在不改变对象结构的前提下添加功能,以及如何更好地构造复杂的对象。

应用模式有许多优势,尤其是开发者可以遵循这些最佳实践,简化大型项目的管理,因为使用可复用的整体软件结构(模式)可以解决相似的问题。这并不是说代码可以简单地从一个项目中复制和粘贴到另一个项目中,而是说概念本身可以在不同场景下反复使用。

应用编程模式还有许多其他的好处,本书都将涵盖,以下是一些值得提及的要点。

  • 模式为团队中的开发者提供了一种高效的通用语言。当一位开发者描述适配器外观等结构时,其他开发者可以马上理解其含义,并识别出代码的结构和目的。
  • 用模式添加抽象层,可以使修改和变更正在开发中的项目代码更加容易。甚至有些模式就是专为此种情况而设计的。
  • 模式的应用范围很广,从项目的整体架构到构造项目中最基本的对象都可以应用模式。
  • 使用模式可以大幅减少代码内部注释和通用文档,因为模式也是一种描述。类或接口的名称可以直观地说明它们的目的以及它们在模式中的地位。

Android开发平台非常适合使用模式,不仅大量的应用程序是用Java编写的,而且SDK中的许多API也应用了模式,比如使用工厂接口创建对象,以及使用建造者来构造对象。像单例这种简单的模式甚至可以作为一种模板类。通过本书,我们将学会组合构造出自己的大型模式,还将学会如何利用这些内置的结构进行最佳实践以及简化编码。

本章首先简单地介绍本书的整体情况:将要用到的模式、学习模式的顺序、在现实环境中应用模式的示例应用程序。然后,我们将快速查阅SDK中哪些组件最适合参考,尤其是一些支持库中提供的组件,这些组件可以同时支持多个平台版本。最好的学习方式就是实践,所以本章剩余部分将开发一个简单的示例应用程序,并使用第一个模式——工厂模式,以及与工厂模式相关的抽象工厂模式

在本章,你将学到以下内容:

  • 模式是如何分类的,以及本章将要介绍的模式;
  • 书中示例应用程序的目的;
  • 应该面向哪些平台版本;
  • 支持库的用途;
  • 工厂模式是什么,以及如何构造一个工厂模式;
  • 如何使用UML类图;
  • 如何在真机和模拟器上测试应用程序;
  • 如何监控正在运行的应用程序;
  • 如何用简单的调试工具测试代码;
  • 抽象工厂模式是什么,以及如何使用抽象工厂模式。

1.1 如何使用本书

本书旨在展示设计模式的应用对Android应用程序开发的直接帮助。我们将专注于开发一个完整的客户端移动应用程序,重点关注何时、如何以及为什么应该在Android开发中使用模式。

从历史上看,模式的构成存在一定程度的争议。1994年,Erich Gamma、Richard Helm、Ralph Johnson和John Vlissides“四人组”合著了《设计模式:可复用面向对象软件的基础》一书。书中阐述了23种设计模式,公认这些模式能帮助解决软件工程中可能遇到的几乎所有问题。正是由于这个原因,这些模式将成为本书的主干。这些模式可以分成3类:

  • 创建型——用于创建对象
  • 结构型——用于组织对象
  • 行为型——用于对象之间的通信

考虑到实用性,本书不会按上述顺序逐类讲解模式。相反,我们将在开发应用程序的过程中自然而然地逐个探索出现的模式,而这通常意味着从创建一个结构开始。

在一个独立的应用程序中囊括所有设计模式是困难、臃肿和不切实际的,尝试使用尽可能多的设计模式看起来更现实。在本书中,这些设计模式不会被直接使用,我们至少会探索一下为什么要这么做,并且针对每种情形至少会用一个实例来展示如何使用设计模式。

模式不是一成不变的,它们也不能解决所有可能出现的问题。我们将在书末看到,一旦掌握了这个主题,在没有既定模式适合的少见的情况下,我们依然可以创建自己的模式或是基于已有模式进行改造适配。

简而言之,模式不是一套一成不变的规则,而是一系列经受过已知问题检验的约定俗成的方法。如果你在探索的过程中发现了窍门,请务必多多实践。经过长久的坚持和积累,就会创造出属于你自己的模式,它和本书所涵盖的传统模式一样有用。

本书的前几章重点关注UI设计,并介绍一些基本的设计模式及其工作原理。大概从第6章开始,我们将把它们和其他模式一起应用到真实的示例中,特别是应用到同一个应用程序中。后面几章的内容集中在开发的后期阶段,例如使应用程序适配不同的设备(这是专为讲解设计模式而构建的任务),从而获取更广阔的市场并获利。

 如果你不熟悉Android开发,请参阅前三章中的详细介绍。如果你已经十分熟悉Android开发,那么可以跳过这几章的内容,而专注于模式本身。

在开始学习第一个模式之前,先来看一下本书将要构建的应用程序以及它所带来的挑战和机遇是很有意义的。

1.2 我们将构建什么

正如前面所提到的,在本书中,我们将构建一个虽小但完整的Android应用程序。不妨现在就来看一下我们将构建什么以及为什么要构建它。

我们将把自己定位成一个接洽了潜在客户的独立Android开发者。我们的潜在客户经营着一家小企业,制作新鲜的三明治并将其运送到当地的几栋办公楼。客户面临着几个问题,他们相信可以通过移动应用程序来解决这些问题。为了理解应用程序所能提供的解决方案,我们将把情境分为三个部分:场景、问题和解决方案。

1.2.1 场景

客户经营着一个小而成功的业务:制作新鲜的三明治,然后把它们送到附近的办公楼。工作人员可以在办公桌前使用手机买三明治外卖吃。三明治很好吃,借助口口相传的广告效应,越来越受欢迎。现在有了一个业务扩张的好机会,但商业模式中存在一些明显低效的问题,客户相信可以使用移动应用程序来解决这些问题。

1.2.2 问题

对客户来说,需求几乎无法预测。在很多情况下,某种三明治制作得太多,导致了浪费,而三明治生产线有时准备不足,又会导致销售损失。此外,仅依靠口口相传的营销方式,业务扩张局限在一小片地理区域。客户没有可靠的途径知晓,是否值得投入更多的员工和摩托车去更远的地方配送,以及是否值得在城镇的其他区域开设新店。

1.2.3 解决方案

一个供所有顾客免费使用的移动应用程序,不仅能解决上述问题,还能带来一系列全新的商机。应用程序不仅能解决需求不可预测的问题,还使我们有机会挖掘出未曾预料的新需求。当我们可以让顾客通过原料表定制个性化的三明治时,为什么仅给顾客一套菜单呢?也许顾客喜欢已有的奶酪和黄瓜三明治,但希望能在其中加入一两片苹果,或者更喜欢用杧果酸辣酱腌制;也许顾客是素食主义者,希望从他们的选择中去掉肉类产品;也许顾客对某些食材过敏。这些需求都可以通过一个精心设计的移动应用程序来满足。

此外,口口相传的广告以及在当地报纸或广告牌上的宣传,这些推广方式都存在地理局限性,很难让企业在更广阔的舞台上取得成功。而使用社交媒体不仅可以让客户清楚地了解当前的趋势,还可以将信息传播给更多的受众。

现在我们的客户不仅能准确地判断业务范围,而且可以增加一些适合现代数字化生活的新特性,例如使用游戏化的应用程序。竞争、难题和挑战带来了全新的视角以吸引顾客,并提供了能增加收入和市场份额的强大技术。

任务现在清晰些了,可以开始写代码了。我们将从一个非常简单的工厂模式示例开始编写,开发过程中还将仔细了解一下SDK中一些有用的特性。

1.3 目标平台版本

为了跟上新技术的步伐,Android平台经常发布新的版本,这意味着开发者可以在应用程序中使用最新的特性。但是,这也存在一个显而易见的缺点:只有最新的设备才能在这个平台上运行,而这些设备只占市场份额的一小部分。图1-1展示了从Android官方网站上获取的图表。

{%}

图 1-1

如你所见,绝大多数的Android设备仍在旧平台上运行。幸运的是,在Android平台上,通过使用支持库和设置最低SDK版本,我们可以同时适配旧设备和使用最新的平台版本特性。

首先要确定的就是目标平台。虽然这个决定在以后可以更改,但是尽早确定要包含的特性以及如何在旧设备上适配它们,可以大大简化整体任务。

要了解如何完成上述操作,请先新建一个Android Studio项目,命名随意。选择Phone and Tablet(手机和平板计算机)作为形状因子,选择API 16作为最低SDK

从模板列表中,选择Empty Activity(空活动)并保留其他内容,如图1-2所示。

图 1-2

Android Studio将自动选择可用的最高SDK版本作为目标版本。从项目面板中打开build.gradle(Module: app)文件,在该文件中的defaultConfig部分可以看到应用的版本。defaultConfig部分的代码如下所示。

defaultConfig {
    applicationId "com.example.kyle.factoryexample"
    minSdkVersion 16
    targetSdkVersion 25
    versionCode 1
    versionName "1.0"
}

以上代码可以确保项目在此API版本区间正确地编译。但是如果要构建准备发行的应用程序,则需要告知Google Play商店,该应用程序可以在哪些设备上使用。这可以通过配置build.gradle文件中的模块实现,如下所示。

minSdkVersion 21
targetSdkVersion 24

AndroidManifest.xml文件同样需要编辑。如下所示,需要在manifest节点下添加uses-sdk元素。

<uses-sdk
    android:minSdkVersion="16"
    android:targetSdkVersion="25" />

确定了目标平台版本的范围后,便可以继续学习如何使用支持库。支持库让我们能够在旧设备上集成许多新特性。

1.4 支持库

在构建向后兼容的应用程序时,支持库毫无疑问是最强大的工具。它实际上是一系列单独的代码库,通过替代标准API中的类和接口来提供支持。

Android有12个独立的支持库1,这些支持库不仅能提供兼容性,还包含一些常见的UI组件,如滑动式抽屉(SlidingDrawer)和悬浮按钮(FloatingActionButton)。如果不使用支持库,就只能自己从零开始编写这些UI组件。支持库还可以简化适配不同屏幕形状和尺寸以及添加一两个其他功能的过程。

1翻译本书时已有20多个支持库。——译者注

 在使用Android Studio进行开发时,应该下载support repository,它是专为Android Studio设计的。虽然support repository和support library提供的功能相同,但前者更高效。2

2Android Support Library下载的是对应的源码或jar包,而Android Support Repository下载的则是这个support库所对应的本地Maven库。目前,Google官方已经不再提供Android Support Library了。——译者注

本章的示例不会用到任何支持库,项目中唯一包含的v7 appcompat库是在项目创建时自动添加的。后文会经常提及支持库,现在我们要专注于应用第一个设计模式。

1.5 工厂模式

工厂模式是使用最广的创建型模式之一。顾名思义,它会创建一些东西。确切地说,它会创建对象。工厂模式的用途是借助通用接口将逻辑与使用分开。要了解工厂模式的工作原理,最好的方式就是实践。打开我们之前创建的项目,或者新建一个项目。最低SDK版本和目标SDK版本对本练习来说并不重要。

 当选择API 21及以上版本时,Android Studio可以采用热部署技术。热部署避免了每次运行项目时都要重新构建项目,这大大加快了应用程序的测试速度。因为热部署能节约时间,所以若计划将目标平台设置成较低的版本,可以在开发完成之后再降低版本。

我们将构建一个非常简单的示例应用程序,用于生成“三明治制作应用程序”所需的对象,这些对象代表不同种类的面包。为了强调模式,我们将保持示例的简单,对象的返回值不会比字符串复杂。

(1) 在项目视图中找到MainActivity.java文件。

(2) 右键单击,新建一个Java类,种类选择接口(Interface),类名叫Bread,如图1-3所示。

图 1-3

(3) 完成接口:

public interface Bread {

    String name();
    String calories();
}

(4) 创建Bread的具体类:

public class Baguette implements Bread {

    @Override
    public String name() {
        return "Baguette";
    }

    @Override
    public String calories() {
        return " : 65 kcal";
    }
  }

  public class Roll implements Bread {

    @Override
    public String name() {
        return "Roll";
    }
    @Override
    public String calories() {
        return " : 75 kcal";
    }
  }

  public class Brioche implements Bread {

    @Override
    public String name() {
        return "Brioche";
    }

    @Override
    public String calories() {
        return " : 85 kcal";
    }
}

(5) 创建一个新类,取名BreadFactory

public class BreadFactory {

    public Bread getBread(String breadType) {

        if (breadType == "BRI") {
            return new Brioche();

        } else if (breadType == "BAG") {
            return new Baguette();

        } else if (breadType == "ROL") {
            return new Roll();
        }

        return null;
    }
}

UML图

理解设计模式的关键在于理解它们的结构以及组件之间的关联。图形化是查看设计模式的最佳方式之一,统一建模语言(unified modeling language,UML)类图则是实现图形化的好方法。

思考一下,我们刚刚创建的设计模式该如何用图形化的方式表达。图1-4给出了示例。

图 1-4

设计模式已经准备就绪,接下来要做的就是查看它的实际效果。对于这个示例,我们将在布局中使用模板生成的TextView。每次主活动(MainActivity)启动时,onCreate()方法都会被调用。

(1) 在文本模式下打开activity_main.xml文件。

(2) 为文本视图添加id

<TextView
    android:id="@+id/text_view"
    android:layout_width="match_parent"
    android:layout_height="wrap_content" />

(3) 打开MainActivity.java文件,依照以下代码编辑onCreate()方法。

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    TextView textView = (TextView) findViewById(R.id.text_view);

    BreadFactory breadFactory = new BreadFactory();
    Bread bread = breadFactory.getBread("BAG");

    textView.setText(new StringBuilder()
            .append(bread.name())
            .toString());
}

 根据Android Studio的设置,可能需要导入TextView小部件:import android. widget.TextView;。通常,编辑器会提示你,按下Alt+Enter快捷键即可导入。

现在,可以在模拟器或真机上测试设计模式了,如图1-5所示。

图 1-5

乍看之下,这可真是小题大做,但也是设计模式的魅力所在。新增的抽象层让我们无须编辑活动(activity)就能修改类,反之亦然。当需要开发更复杂的对象以及使用多个工厂时,该特性的实用性将变得更加明显。

本节的示例太简单了,以至于不需要做任何测试。不过,我们不妨来探索如何在真机和模拟器上测试Android应用程序,并学习如何监控性能,以及如何通过调试工具,实现不添加额外的屏幕组件即可测试输出。

1.6 运行和测试应用程序

如今,大量的设备使用Android系统,它们的形状和尺寸各异。作为开发者,我们希望能够用最少的代码,让应用程序适配尽可能多的设备和形状因子。幸运的是,Android平台非常适合这一挑战。我们能够轻松地调整布局,并且可以构建模拟器,适配一切我们能想到的形状因子。

 Google提供了一个非常方便的基于云的应用程序测试工具——Firebase测试实验室。

显然,在任何测试环境中,模拟器都是重要的组成部分。但这并不意味着连接测试机测试应用程序不方便。使用自己的测试机,不仅比任何模拟器都快,而且正如我们接下来将要看到的,真机连接的配置非常简单。

1.6.1 连接到真机

真机除了比模拟器快,还能让我们在现实环境中测试应用程序。

将真机连接到我们的开发环境需要两个步骤。

(1) 在手机上启用开发者选项。在某些型号的手机上,需要进入Settings | About phone(设置 | 关于手机),点击7次Build number(版本号),之后Developer options(开发者选项)会出现在设置中。使用此选项,可以启用USB debugging(USB调试)以及Allow mock locations(允许模拟位置)。

(2) 现在,你应该可以通过USB或者Wi-Fi插件线将设备连接到工作站了。此时打开Android Studio,已经可以显示设备。如果没有显示设备,可能需要打开SDK Manager,通过Tools(工具)选项卡安装Google USB driver(Google USB驱动程序)。极少数情况下,需要从设备制造商网站下载USB驱动程序。

真机对于快速测试应用程序的功能变化是非常有用的,但是为了在开发过程中可以直观看到应用程序在不同形状、尺寸的屏幕上的显示效果,需要创建一些模拟器。

1.6.2 连接到模拟器

利用Android虚拟设备(AVD),开发者可以随意试验各种模拟的硬件设置,但众所周知,它们速度很慢,会耗尽许多计算机系统资源,而且缺乏真机中的许多特性。虽然模拟器有这些缺点,但它仍是Android开发者工具箱中的重要部分。通过考虑以下内容,可以减少许多阻碍。

  • 拆解模拟器,使其只包含应用程序设计所需的特性。例如,如果应用程序中没有拍照功能,可以从模拟器中删除相机功能,以后随时可以添加回来。
  • 尽量降低AVD的内存和存储需求。当应用程序有需要时,创建另一个设备非常容易。
  • 只有需要测试特定的新特性时,才使用最新的API级别创建AVD。
  • 先在具有低分辨率和低密度屏幕的模拟器上进行测试,这将使运行速度更快,并且仍能测试不同的屏幕尺寸和长宽比。
  • 尝试拆分耗费资源的功能,并单独测试它们。例如,如果应用程序中使用了大量的高清图像集合,可以通过单独测试此功能来节省时间。

构建适合特定用途的模拟器通常比构建用于测试所有功能的通用模拟器要快,而且现在有越来越多的第三方Android模拟器可用,例如Android-x86和Genymotion,它们通常更快,并且具有更多的开发特性。

值得注意的是,当只测试布局时,Android Studio提供了一些强大的预览选项,可以让我们预览许多形状因子、SDK级别以及主题上的潜在UI效果,如图1-6所示。

{%}

图 1-6

现在,创建一个基本的AVD来运行和测试当前项目。目前没有什么真正需要测试的东西,但是通过此操作,我们将看到如何在运行时监控应用程序的行为,以及如何在不使用设备屏幕的情况下使用调试监控器服务来测试输出(对于调试项目来说,这种方式不太吸引人)。

1.6.3 监控设备

下面的演示对模拟器和真机都有效,因此可以任选最适合你的设备。如果要创建AVD,无须大尺寸、高密度的屏幕以及大内存。

(1) 打开我们刚做的项目。

(2) 从Tools | Android(工具 | Android)菜单中,选择Enable ADB Integration(启用ADB集成),如图1-7所示。

图 1-7

(3) 在同一个菜单中,选择Android Device Monitor3,尽管它可能已经在运行了。

3Android Device Monitor已在Android Studio 3.1中弃用,并已从Android Studio 3.2中移除。——译者注

(4) 现在,使用Android Device Monitor在连接的设备上运行应用程序。

Android Device Monitor在以下几个方面很有用。

  • 可以在运行时使用Monitors(监视器)选项卡查看实时系统信息,例如应用程序占用的内存或CPU时间。当我们想要查看应用程序未在前台运行时所使用的资源,监视器尤其有用。
  • 可以设置监视器来收集各种数据,例如方法跟踪和资源使用,这些数据会被存储为文件,可以在Captures(捕获)面板(通常可以在左侧边栏打开)中查看。
  • 在Captures面板中,对应用程序进行截屏和录屏非常简单。
  • LogCat是一个特别有用的工具,因为它不仅可以实时报告应用程序的行为,而且正如我们接下来将要看到的,它还可以生成用户定义的输出。

目前,使用文本视图(TextView)来测试工厂模式是一种便捷但笨拙的代码测试方法。一旦我们开始开发复杂的布局,这种方式就会变得非常不便。一个更优雅的解决方案是使用调试工具,这些工具可以在不影响UI的情况下进行查看。本练习剩余的部分将演示该如何做。

(1) 打开MainActivity.java文件。

(2) 声明如下常量。

private static final String DEBUG_TAG = "tag";

(3) 同样,需要确认android.util.Log;的导入。

(4) 用如下所示代码,替换onCreate()方法中用来设置文本视图文本的代码。

Log.d(DEBUG_TAG, bread);

(5) 再次打开设备监视器,这可以用快捷键Alt+6操作。

(6) 从监视器右上角的下拉列表中,选择Edit Filter Configuration(编辑过滤器配置)。

(7) 填写触发的对话框,如图1-8所示。

图 1-8

运行应用程序,测试工厂示例。在logcat监视器中应该会产生如下输出。

05-24 13:25:52.484 17896-17896/? D/tag: Brioche
05-24 13:36:31.214 17896-17896/? D/tag: Baguette
05-24 13:42:45.180 17896-17896/? D/tag: Roll

 当然,如果你愿意,依然可以使用System.out.println()方法将信息在ADB监视器中打印出来,但需要在所有输出中搜索它。

我们已经了解了如何在真机和模拟器上测试应用程序,以及如何在运行时使用调试和监视工具查询应用程序。下面可以切换到更贴近现实的情况——涉及多个工厂以及比双字字符串更复杂的输出。

1.7 抽象工厂模式

在制作三明治时,面包只是首个最基本的原料,显然我们还需要一些馅料。用编程语言来讲,这意味着只需简单地构建另一个像Bread的接口,可以将其称为Filling,并为其提供关联的工厂。同样,我们也可以创建一个名为Ingredient的全局接口,并将BreadFilling都作为其样本。无论使用哪种方式,我们都必须重写许多代码。

设计模式范例提供了抽象工厂模式,它可能是最适合解决这一难题的方案。简单来说,抽象工厂就是创建其他工厂的工厂。额外添加的抽象层可以减少对主活动中上层控制代码的更改。能够修改底层结构而不影响前面的结构,这是应用设计模式的主要原因之一。当应用于复杂的体系结构时,这种灵活性可以节省多个星期的开发时间,并且相比其他方法有更多的实验空间。

使用多个工厂

下一个项目和前一个项目惊人地相似,也理应如此。使用模式最大的优点之一就是可以复用结构。你可以继续编辑上一个示例,也可以重新创建一个。这里,我们将重新开始一个新项目,希望这样可以让模式的讲解更清晰。

抽象工厂的工作方式与前面的示例稍有不同。这里,活动使用工厂生成器,而工厂生成器又使用抽象工厂类来决定实际调用的工厂任务和创建的具体类。

和之前一样,我们不关注输入和输出的实际机制,而是专注于模式的结构。在继续之前,启动一个新的Android Studio项目。无论你如何命名,请将最低API级别设置为你想要的最低版本,并选择使用Blank Activity(空白活动)模板。

(1) 和之前一样,开始创建接口,只是这次我们需要两个接口:一个用于面包,另一个用于馅料。两个接口的代码应该如下所示:

public interface Bread {

    String name();
    String calories();
}

public interface Filling {

    String name();
    String calories();
}

(2) 和之前一样,创建这些接口的实体类。为了节省空间,每种接口将只创建两个实体类。因为它们的代码几乎完全相同,所以这里只给出一个示例:

public class Baguette implements Bread {

    @Override
    public String name() {
        return "Baguette";
    }

    @Override
    public String calories() {
        return " : 65 kcal";
    }
}

(3) 创建另一个叫BriocheBread,以及叫CheeseTomato的两种馅料。

(4) 接下来,创建一个类,它可以调用所有类型的工厂类:

public abstract class AbstractFactory {

    abstract Bread getBread(String bread);
    abstract Filling getFilling(String filling);
}

(5) 下面创建工厂。首先是BreadFactory

public class BreadFactory extends AbstractFactory {

    @Override
    Bread getBread(String bread) {

        if (bread == null) {
            return null;
        }

        if (bread == "BAG") {
            return new Baguette();
        } else if (bread == "BRI") {
            return new Brioche();
        }

        return null;
    }

    @Override
    Filling getFilling(String filling) {
        return null;
    }
}

(6) 然后是FillingFactory

public class FillingFactory extends AbstractFactory {

    @Override
    Filling getFilling(String filling) {

        if (filling == null) {
            return null;
        }

        if (filling == "CHE") {
            return new Cheese();
        } else if (filling == "TOM") {
            return new Tomato();
        }

        return null;
    }

    @Override
    Bread getBread(String bread) {
        return null;
    }
}

(7) 最后,添加工厂生成器类:

public class FactoryGenerator {

    public static AbstractFactory getFactory(String factory) {

        if (factory == null) {
            return null;
        }

        if (factory == "BRE") {
            return new BreadFactory();
        } else if (factory == "FIL") {
            return new FillingFactory();
        }

        return null;
    }
}

(8) 我们可以像之前一样,使用调试标记测试代码:

AbstractFactory fillingFactory = FactoryGenerator.getFactory("FIL");
Filling filling = fillingFactory.getFilling("CHE");
Log.d(DEBUG_TAG, filling.name()+" : "+filling.calories());

AbstractFactory breadFactory = FactoryGenerator.getFactory("BRE");
Bread bread = breadFactory.getBread("BRI");
Log.d(DEBUG_TAG, bread.name()+" : "+bread.calories());

测试的时候,Android监视器中将产生如下输出:

com.example.kyle.abstractfactory D/tag: Cheese : : 155 kcal
com.example.kyle.abstractfactory D/tag: Brioche : : 85 kcal

至本书末尾时,每种原料都将是一个复杂的对象,具有相关的图像、描述文字、价格、热值,等等。这时,我们就会看到坚持使用模式的回报。此处这样一个简单的示例,只是用来演示创建型模式(例如抽象工厂)如何在不影响客户端代码或部署的情况下对产品进行更改。

和前面一样,可以通过视觉表示来增强我们对模式的理解,见图1-9。

{%}

图 1-9

想象一下,我们想在菜单中加入软饮料。它们既不是面包也不是馅料,因此我们需要引入一种全新的对象。增加软饮料所需的模式已经讲过了。我们需要一个与其他接口相同的新接口,称为Drink,它将使用相同的name()calories()方法。具体类(例如IcedTea)可以按照与上面完全相同的方式实现,例如:

public class IcedTeaimplements Drink {

    @Override
    public String name() {
        return "Iced tea";
    }

    @Override
    public String calories() {
        return " : 110 kcal";
    }
}

我们需要如下代码来扩展抽象工厂。

abstract Drink getDrink(String drinkType);

当然,我们还需要实现一个DrinkFactory类,它和其他工厂有相同的结构。

换言之,我们可以添加、删除、更改以及徘徊在项目的细节之中,而无须担心软件上层逻辑如何感知这些变化。

工厂模式是最常使用的模式之一,它可以且应该在很多情景下使用。但是和所有模式一样,如果不仔细思考,它可能被过度使用或未充分使用。当考虑项目的总体架构时,如我们所见,还有许多其他模式可供使用。

1.8 小结

考虑到这是一个介绍性的章节,我们已经涵盖了很多内容。我们构建了两个最知名、最有用的设计模式的示例,并且了解了它们有用的原因。

首先,通过查看所使用的开发工具,了解如何以及为何针对特定平台版本以及形状因子进行开发,我们学习了什么是模式,以及为什么可以在Android环境中使用它们。

然后,我们应用这些知识创建了两个非常简单的应用程序,它们采用基本的工厂模式。随后,我们了解了如何对真机和模拟器上运行的应用程序进行测试和获取数据。

这使得我们在构建一个完整的应用程序时,能够认真思考该使用哪些模式。下一章将更详细地介绍建造者模式以及如何生成Android布局。

目录

  • 版权声明
  • 译者序
  • 前言
  • 第 1 章 设计模式
  • 第 2 章 创建型模式
  • 第 3 章 Material模式
  • 第 4 章 布局模式
  • 第 5 章 结构型模式
  • 第 6 章 活动模式
  • 第 7 章 混合模式
  • 第 8 章 组合模式
  • 第 9 章 观察者模式
  • 第 10 章 行为型模式
  • 第 11 章 可穿戴模式
  • 第 12 章 社交模式
  • 第 13 章 分发模式
  • 作者简介