在Java Web开发框架中创建VoiceXML页面-扩展 Java 驱动的 VoiceXML 应用程序

在Java Web开发框架中创建VoiceXML页面-扩展 java 驱动的 VoiceXML 应用程序

编辑:未知 文章来源:互联网

在关于 VocieXML 的 上一期 文章中,您看到了 java™ servlet 是如何轻松赋予 VoiceXML 应用程序强大功能的。在这篇续文中,您将学习如何使用 servlet 扩展单页面的应用程序,以及如何为您的 VoiceXML 应用程序添加导航功能。在整个学习过程中,您应注意确保 VoiceXML 内容受无线设备(例如电话和手持设备)的支持,并针对它进行了优化。
在本系列的第一篇文章中,您大致了解了 编写基于 servlet 的 VoiceXML 应用程序 的相关内容。现在您应已熟悉使用 servlet 来输出 VXML,因而也就作好了准备,可以在 VXML 和基于 servlet 的 VoiceXML 应用程序的基础上略加扩展了。您将详细了解具有呼叫导航 的应用程序,它允许用户利用提示转移到应用程序的不同页面和节。

VoiceXML 和 VXML

切记,VXML 和 VoiceXML 这两个术语不可交替使用。VoiceXML 是一种技术,VXML 是 VoiceXML 使用的实际文件格式。因此,您可以说 VoiceXML 应用程序 或基于 VXML 的应用程序,但不能说 VXML 应用程序。

即便是在一个中等规模的 VoiceXML 应用程序中,往往也需要开发五个、十个乃至二十个不同的 VXML 页面。当然,您还要维护这些文件,确保其权限正确,可在 VoiceXML 应用程序中访问,同时又能不受恶意用户威胁;您还需要确保文件名此后不会发生变化(如果发生变化,务必更新其他文件中的所有引用链接)。最终结果几近于一场维护梦魇。

转而采用基于 servlet 的应用程序,您就可以解决大部分此类麻烦。单独的一个 servlet 即可管理多个 VXML 节,甚至还可以在一个文件系统上处理来自多个不同 VXML 文件的输出。权限问题几乎不复存在,因为只有 servlet 需要访问文件。此外,您的 servlet 可以通过简单的常量来处理文件命名,这使您可在一个位置更改一个文件名称,并使之影响相应的 servlet 输出的所有文件。最好的事情就是,您可以使用 servlet 添加动态内容,而在使用静态 VXML 文件时,这几乎是不可能的。

多页面 VoiceXML 的简要回顾
深入研究编写 servlet 驱动的多页面应用程序之前,您需要清楚地了解 VoiceXML(特别是您的 VXML 文件)中是如何处理链接和节的。记住,servlet 只是用以输出 VXML 的一种手段,如果您没有很好地掌握 VXML,无论 java 编码有多么强大的能力,都无法帮助您的应用程序在移动设备或电话上很好地执行或运作。

向您的 VXML 添加链接

在 VXML 文件中添加链接非常容易,只需使用 goto 元素,它与 HTML 中的 a 元素相似。清单 1 显示一个 VXML 页面,它惟一的功能就是提供到另外一个 VXML 页面的链接。

清单 1. 从一个 VXML 文件到另一个 VXML 文件的链接

<?xml version="1.0" encoding="UTF-8"?>


<vxml version="2.1">
<form>
<block>
<goto next="anotherPage.xml" />
</block>
</form>
</vxml>

随后您可能希望以一个简单的文件作为链接目标,如 清单 2 所示。只要将文件命名为 anotherPage.xml 即可,目标文件中包含哪些内容无关紧要。

清单 2. 一个非常简单的 VXML 文件

<?xml version="1.0" encoding="UTF-8"?>


<vxml version="2.1">
<form>
<block>
Welcome to another page.
</block>
</form>
</vxml>

务必确保您的 VXML 页面仅链接到其他 VXML 页面(或链接到输出 VXML 的资源,例如生成 VXML 的 servlet)。这一点看上去似乎显而易见,但由于 VXML 与 HTML 和 XHTML 极为相似,所以经常看到有 VXML 错误地指向了 HTML 页面,而不是 VXML 文档。

将您的 VXML 拆分成节

您可以将用户定位到其他的 VXML 页面,与此相似,也可以将用户定位到当前 VXML 页面中的另外一节。清单 3 显示了一个菜单,允许用户选择不同类型的音乐,然后将用户定位到 VXML 文件的各节。

清单 3. 有多个节的 VXML 文件

<?xml version="1.0" encoding="UTF-8"?>


<vxml version="2.1">
<link next="#Menu">
<grammar type="text/gsl">[menu begin start (start over)]</grammar>
</link>

<form id="Menu">
<block>
<prompt>Welcome to the Menu.</prompt>
</block>

<field name="TypeOfMusic">
<prompt>What is your favorite type of music?</prompt>
<grammar type="text/gsl">
<![CDATA[[
[blues] {<TypeOfMusic "blues">}
[jazz swing] {<TypeOfMusic "jazz">}
[rap urban (hip hop)] {<TypeOfMusic "hip">}
[rock (rock and roll)] {<TypeOfMusic "rock">}
]]]>
</grammar>

<noinput>
<prompt>I didn't get that. Can you try again?</prompt>
<reprompt />
</noinput>
<nomatch>
<prompt>Try another kind of music.</prompt>
<reprompt />
</nomatch>
</field>

<filled>
<if cond="TypeOfMusic == 'blues'">
<goto next="#Blues" />
<elseif cond="TypeOfMusic == 'jazz'" />
<goto next="#Jazz" />
<elseif cond="TypeOfMusic == 'hip'" />
<goto next="#Hip" />
<elseif cond="TypeOfMusic == 'rock'" />
<goto next="#Rock" />
</if>
</filled>
</form>

<form id="Blues">
<field name="ReturnToMain">
<prompt>
Check out Robert Johnson. It doesn't get any better. For
other ideas, say "Menu".
</prompt>
</field>
</form>

<form id="Jazz">
<field name="ReturnToMain">
<prompt>
Check out Django Reinhardt. It doesn't get any better. For
other ideas, say "Menu".
</prompt>
</field>
</form>

<form id="Hip">
<field name="ReturnToMain">
<prompt>
Sorry, I don't have a clue on this one. For
other ideas, say "Menu".
</prompt>
</field>
</form>

<form id="Rock">
<field name="ReturnToMain">
<prompt>
Check out Jimi Hendrix or Cream. It doesn't get any better. For
other ideas, say "Menu".
</prompt>
</field>
</form>
</vxml>

尽管这是一段相当长的 VXML,但其中没有什么特别复杂的东西。如果您还不确定其中某些元素的功能,请查看 参考资料 部分中列出的 VXML 入门文章。

通过这种方式创建了一个主菜单(form,其 ID 是 TypeOfMusic),还提供了一条提示。用户可在多个选项中任选一个,然后使用标准 VoiceXML 语法匹配那些选项。根据应答,用户将被定位到某一节处。每一节都在 form 元素中定义了自己的 ID,并且各有各的提示。

在这个简单的示例中,各节都有一个简短的提示,还提供了返回主菜单的方法。在更为复杂的应用程序中,您实际上要添加提示和下级选项,其中每一节(如示例中的 “Blues” 或 “Rock”)都能将用户定位到其他 节。理论上,您要重复这一过程无数次,在一个 VXML 文件中得到数百节。

当然,这只是理论而已。在实践中,只要节的层次超过两或三层,维护就会成为一场梦魇。但将一些相关的节放在一起确实是一个好主意。这个简单的示例可扩展为至少两个 VXML 文件,一个用于主菜单,各种类型的音乐的所有节。在更糟糕的情况下,它被拆分为五个文件:一个用于主菜单,每种类型的音乐各使用一个文件。毫无疑问,这种方法也会带来大量的维护工作。最好的方法 —— 也是本文推荐的方法,就是寻找一种恰当的媒体,为相关的分组使用一个文件。

拆分应用程序

让 VoiceXML 安静下来

XML 最大的优点之一就是它可以通过多种不同的方法呈现。许多较为先进的移动电话实际上都是在屏幕上以可视化方式呈现 VXML 菜单。用户可以利用电话的控制键来浏览这些菜单,而不必对着电话说话 —— 当您在公共场所使用电话时,这显然非常方便。

进一步介绍使用 servlet 驱动包含多个 VXML 文件或节的应用程序之前,首先应该了解为什么要拆分应用程序。前面已经提到,完全有可能开发出一个极大(也很长)的 VXML 文件,在一组提示中或在一个屏幕上提供应用程序的大部分功能。如此之长的文件与热线电话服务非常相似,这种电话服务中,按 “112” 表示在一个有着实体墙壁的家庭中使用的立体声音响;按 “243” 表示冬季在西南部地区各州使用的微波炉;如果您想联系汽车销售部、在敞蓬货车和有蓬货车中更喜欢敞蓬货车,并且三个以上的孩子,请按 “9828”。这听上去有些滑稽(也确实有开玩笑的意思),但人们总是很容易失去自制力,给一名用户提供大批大批的选项。切勿使用这种方法,考虑将应用程序拆分成多个较小的节或文件,每一段提供更少的选项。

对于电话客户,这会得到更便于使用的应用程序。不必再费力浏览 8 个、10 个 或者 15 个选项,只需从 3 或 4 个主要类目中进行选择即可。随后他们通常再次看到提示,得到更为具体的子类目。虽然他们必须要在电话上输入更多一些的数字,但会有一种向着目标前进的感觉。

选项与目标的平衡

在任何 VoiceXML 应用程序中,总是存在着这样的压力:在各菜单中使用较少的选项,使用户达成目标(获得特定功能或一段信息,或者是接触人工操作员)的时间最小化。尽管您不希望一次为用户提供数量过多的选项,但必须在这个数量与用户达成目标所花费的时间之间找到一种平衡。使用较少的提示、非常具体的选项,这非常好,但必须在它和达成用户目标的合理路径间获得平衡。根据经验,使用两个菜单不会造成任何问题,三个菜单有时也可以。使用四个或更多的菜单时,用户就开始厌烦了(可能会直接挂断电话)。

为满足无线客户的需求,将应用程序拆分成多个较小的节至关重要。通常,屏幕选项是以自顶向下的方式显示的。这也就意味着,查看第四个或第五个选项时,许多无线设备的屏幕都需要滚动。用户往往不会查看未处于初始屏幕上的选项,您的应用程序可能会得到不公平的指责,而原因仅仅在于用户不希望或者不知道要向下滚动屏幕。通过使用较小的菜单,每一节中提供较少的选项,最终转用更多的节(或文件)。您将得到更多移动和无线用户的欣赏,同时又在开发人员一方保证了较低的额外的维护成本。

servlet 生成的 VXML 文件

至此,您应该认识到,您需要找到一种非常均衡的方法,从而跨一个文件的多个节、跨所有 VXML 页面处理 VXML 内容。前面已经介绍过,一个 VXML 文件越长(它往往会变得越来越长,因为您要不断为它添加新节),维护和管理起来就越困难。在很多情况下,servlet 都能提供一种方法,使您获得多节的效果,同时又没有维护成本。通过使用 servlet 来提供 VXML,您依然可以为客户机提供多节式 VXML,但可将内容隔离成更多的可管理的节。这就是本节介绍的重点。

将多个 VXML 大文件组织到一个 servlet 中

使用 servlet 输出 VXML 的最显著的优势就在于组织。请查看前面的 清单 3,您会注意到两节:主菜单,然后是负责路由用户的所有代码。使用静态 VXML 时,您在一个很大的 XML 文件中管理所有这一切。但在 servlet 中,您可将 VXML 拆分成多个方法。看 清单 4,它展示了一个输出 清单 3 所示 VXML 的 servlet 的开始部分。

清单 4. 具有多个 VXML 节的 servlet

package com.ibm.vxml;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Calendar;
import javax.servlet.*;
import javax.servlet.http.*;

public class SectionServlet extends HttpServlet {

public void doGet(HttpServletRequest req, HttpServletResponse res)
throws ServletException, IOException {

BufferedInputStream bis = null;
PrintWriter out = null;

try {
// Let the browser know that XML is coming
out = res.getWriter();
res.setContentType("text/xml");

// Output VXML
printHeader(out);
printMainMenu(out);
printOptions(out);
printFooter(out);

} finally {
if (out != null) out.close();
if (bis != null) bis.close();
}
}

public void doPost(HttpServletRequest req, HttpServletResponse res)
throws ServletException, IOException {

doGet(req, res);
}

private void printHeader(PrintWriter out) throws IOException {
out.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
out.println("<vxml version=\"2.1\">");
}

private void printMainMenu(PrintWriter out) throws IOException {
out.println(
" <link next=\"#Menu\">\n" +
" <grammar type=\"text/gsl\">[menu begin start (start over)]</grammar>\n" +
" </link>\n" +
"\n" +
" <form id=\"Menu\">\n" +
" <block>\n" +
" <prompt>Welcome to the Menu.</prompt>\n" +
" </block>\n" +
"\n" +
" <field name=\"TypeOfMusic\">\n" +
" <prompt>What is your favorite type of music?</prompt>\n" +
" <grammar type=\"text/gsl\">\n" +
" <![CDATA[[\n" +
" [blues] {<TypeOfMusic \"blues\">}\n" +
" [jazz swing] {<TypeOfMusic \"jazz\">}\n" +
" [rap urban (hip hop)] {<TypeOfMusic \"hip\">}\n" +
" [rock (rock and roll)] {<TypeOfMusic \"rock\">}\n" +
" ]]]>\n" +
" </grammar>\n" +
"\n" +
" <noinput>\n" +
" <prompt>I didn't get that. Can you try again?</prompt>\n" +
" <reprompt />\n" +
" </noinput>\n" +
" <nomatch>\n" +
" <prompt>Try another kind of music.</prompt>\n" +
" <reprompt />\n" +
" </nomatch>\n" +
" </field>\n" +
"\n" +
" <filled>\n" +
" <if cond=\"TypeOfMusic == 'blues'\">\n" +
" <goto next=\"#Blues\" />\n" +
" <elseif cond=\"TypeOfMusic == 'jazz'\" />\n" +
" <goto next=\"#Jazz\" />\n" +
" <elseif cond=\"TypeOfMusic == 'hip'\" />\n" +
" <goto next=\"#Hip\" />\n" +
" <elseif cond=\"TypeOfMusic == 'rock'\" />\n" +
" <goto next=\"#Rock\" />\n" +
" </if>\n" +
" </filled>\n" +
" </form>");
}

private void printOptions(PrintWriter out) throws IOException {
out.println(
" <form id=\"Blues\">\n" +
" <field name=\"ReturnToMain\">\n" +
" <prompt>\n" +
" Check out Robert Johnson. It doesn't get any better. For\n" +
" other ideas, say \"Menu\".\n" +
" </prompt>\n" +
" </field>\n" +
" </form>\n" +
"\n" +
" <form id=\"Jazz\">\n" +
" <field name=\"ReturnToMain\">\n" +
" <prompt>\n" +
" Check out Django Reinhardt. It doesn't get any better. For\n" +
" other ideas, say \"Menu\".\n" +
" </prompt>\n" +
" </field>\n" +
" </form>\n" +
"\n" +
" <form id=\"Hip\">\n" +
" <field name=\"ReturnToMain\">\n" +
" <prompt>\n" +
" Sorry, I don't have a clue on this one. For\n" +
" other ideas, say \"Menu\".\n" +
" </prompt>\n" +
" </field>\n" +
" </form>\n" +
"\n" +
" <form id=\"Rock\">\n" +
" <field name=\"ReturnToMain\">\n" +
" <prompt>\n" +
" Check out Eric Clapton. It doesn't get any better. For\n" +
" other ideas, say \"Menu\".\n" +
" </prompt>\n" +
" </field>\n" +
" </form>");
}

private void printFooter(PrintWriter out) throws IOException {
out.println("</vxml>");
}
}

动态 VXML 101

本文未涉及简单的通过 servlet 输出 VXML 的具体细节,相关内容已在本系列的第一篇文章中介绍过(参见 参考资料 部分)。如果您还不清楚使用 servlet 将 VXML 发送给客户机有多么好,那么应该暂时停一下,回过头去阅读那篇文章,然后再继续学习本文。那样您就能够将更多的注意力集中在使用多个 VXML 节和页面的应用程序的特定细节上。

这是一段相当长的代码,但不要因为长度而忽略它的优势。完全有可能将菜单代码与选项代码彻底分开,那样维护 VXML 时会更轻松。使用上面的代码,您不必处理静态文件,而是可以在 IDE 或可视化环境中非常轻松地管理代码。您也可以将 printOptions() 方法拆分成多个方法,例如 printBluesOption() 或 printJazzOptions()。您可以更别出心裁一些,编写一个方法,让它接受一个参数,然后输出恰当的 VXML 音乐消息,这个方法的形式类似于 printMusicAdvice(String musicType, PrintWriter out)。这部分内容留给您作为练习,但您应看到,您可以快速获得自己的 VXML,并将其拆分成高度模块化的节。

使您的 VXML 和 servlet 模块化

更好的方法是,您可以将 清单 4 中的设置轻松更改为更加模块化的方式。例如,您不必将所有 VXML 都以文本的形式保存在 servlet 中,而是可以将其拆分到各自的类中。看一下 清单 5,其中正应用了这种理念。


清单 5. 使提供 VXML 的 servlet 模块化

package com.ibm.vxml;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Calendar;
import javax.servlet.*;
import javax.servlet.http.*;

public class ModularSectionServlet extends HttpServlet {

public void doGet(HttpServletRequest req, HttpServletResponse res)
throws ServletException, IOException {

BufferedInputStream bis = null;
PrintWriter out = null;

try {
// Let the browser know that XML is coming
out = res.getWriter();
res.setContentType("text/xml");

// Output VXML
VXMLUtils.printHeader(out);
MusicVXML.printMainMenu(out);
MusicVXML.printOptions(out);
VXMLUtils.printFooter(out);

} finally {
if (out != null) out.close();
if (bis != null) bis.close();
}
}

public void doPost(HttpServletRequest req, HttpServletResponse res)
throws ServletException, IOException {

doGet(req, res);
}
}


在这个示例中,所有实际的 VXML 都归入两个类中。第一个是 VXMLUtils,这个类处理重复性的 VXML 任务。清单 6 展示了这个类,它完成的功能与 清单 4 完全相同,但这次 VXML 的 header 和 footer 从主 servlet 代码中分离出来,放到了这个实用工具类中。


清单 6. 为一般 VXML 任务使用一个实用工具类

package com.ibm.vxml;

import java.io.IOException;
import java.io.PrintWriter;

public class VXMLUtils {

public static void printHeader(PrintWriter out) throws IOException {
out.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
out.println("<vxml version=\"2.1\">");
}

public static void printFooter(PrintWriter out) throws IOException {
out.println("</vxml>");
}
}


这种方法的优点应该非常明显。首先,您现在可以将任何在整个 VXML 中使用的功能移动到新的 VXMLUtils 类中。这使得更改轻而易举,比如说更改 header 中使用的 VXML 版本。其次,您可以在所有 VoiceXML servlet 中使用这个实用工具类。所以就避免了重复信息,并将信息集中在一起。因此,只要在一处更改 VXML 版本即可影响到所有的 VXML 输出,再不必去更改 servlet 的源文件。

除了使用不同类表示不同 VXML 文件的能力之外,您现在可以真正地开始构建一种灵活的框架。例如,注意 清单 6 中另外一个新类 —— MusicVXML 的使用。这个类处理与音乐文件相关的 VXML(除去现在由 VXMLUtils 处理的通用 VXML)。清单 7 展示了这个类,它非常简单。


清单 7. 处理音乐 VXML 文件输出的 MusicVXML

package com.ibm.vxml;

import java.io.IOException;
import java.io.PrintWriter;

public class MusicVXML {

public static void printMainMenu(PrintWriter out) throws IOException {
out.println(
" <link next=\"#Menu\">\n" +
" <grammar type=\"text/gsl\">[menu begin start (start over)]</grammar>\n" +
" </link>\n" +
"\n" +
" <form id=\"Menu\">\n" +
" <block>\n" +
" <prompt>Welcome to the Menu.</prompt>\n" +
" </block>\n" +
"\n" +
" <field name=\"TypeOfMusic\">\n" +
" <prompt>What is your favorite type of music?</prompt>\n" +
" <grammar type=\"text/gsl\">\n" +
" <![CDATA[[\n" +
" [blues] {<TypeOfMusic \"blues\">}\n" +
" [jazz swing] {<TypeOfMusic \"jazz\">}\n" +
" [rap urban (hip hop)] {<TypeOfMusic \"hip\">}\n" +
" [rock (rock and roll)] {<TypeOfMusic \"rock\">}\n" +
" ]]]>\n" +
" </grammar>\n" +
"\n" +
" <noinput>\n" +
" <prompt>I didn't get that. Can you try again?</prompt>\n" +
" <reprompt />\n" +
" </noinput>\n" +
" <nomatch>\n" +
" <prompt>Try another kind of music.</prompt>\n" +
" <reprompt />\n" +
" </nomatch>\n" +
" </field>\n" +
"\n" +
" <filled>\n" +
" <if cond=\"TypeOfMusic == 'blues'\">\n" +
" <goto next=\"#Blues\" />\n" +
" <elseif cond=\"TypeOfMusic == 'jazz'\" />\n" +
" <goto next=\"#Jazz\" />\n" +
" <elseif cond=\"TypeOfMusic == 'hip'\" />\n" +
" <goto next=\"#Hip\" />\n" +
" <elseif cond=\"TypeOfMusic == 'rock'\" />\n" +
" <goto next=\"#Rock\" />\n" +
" </if>\n" +
" </filled>\n" +
" </form>");
}

public static void printOptions(PrintWriter out) throws IOException {
out.println(
" <form id=\"Blues\">\n" +
" <field name=\"ReturnToMain\">\n" +
" <prompt>\n" +
" Check out Robert Johnson. It doesn't get any better. For\n" +
" other ideas, say \"Menu\".\n" +
" </prompt>\n" +
" </field>\n" +
" </form>\n" +
"\n" +
" <form id=\"Jazz\">\n" +
" <field name=\"ReturnToMain\">\n" +
" <prompt>\n" +
" Check out Django Reinhardt. It doesn't get any better. For\n" +
" other ideas, say \"Menu\".\n" +
" </prompt>\n" +
" </field>\n" +
" </form>\n" +
"\n" +
" <form id=\"Hip\">\n" +
" <field name=\"ReturnToMain\">\n" +
" <prompt>\n" +
" Sorry, I don't have a clue on this one. For\n" +
" other ideas, say \"Menu\".\n" +
" </prompt>\n" +
" </field>\n" +
" </form>\n" +
"\n" +
" <form id=\"Rock\">\n" +
" <field name=\"ReturnToMain\">\n" +
" <prompt>\n" +
" Check out Eric Clapton. It doesn't get any better. For\n" +
" other ideas, say \"Menu\".\n" +
" </prompt>\n" +
" </field>\n" +
" </form>");
}
}


现在您只要编译这三个文件(servlet 和两个 VXML 输出文件),就能动态生成 VXML。这种方式的美妙之处在于,一个开发人员(或团队)即可处理 servlet 编程,另外一个开发人员(或团队)可以去处理通用 VXML 方法和实用工具,第三个开发人员(或团队)则可以去处理音乐 VXML 文件。这些小组之间只需通过可用方法签名即可相互通信(就像您发布 java 接口一样),除此之外,开发流程完全是彼此独立的。

混合文件

这种方法还有另外一个略微次之的优势。回过头去观察 清单 5,您会注意到 VXML 输出是由 VXMLUtils 类和 MusicVXML 类产生的输出构成的。这看上去或许不是那么惊天动地,但在您开始真正地考虑它的内涵时就不是这样了。

首先,您不再受文件边界的约束。例如,servlet 可以输出一个 1,000 行的 VXML 文件,也可以输出五个 200 行的 VXML 文件,两者最终都会为客户机显示一个 1000 行的 VXML 输出。换句话说,极大、极复杂的 VXML 输出可由多个较小的文件或组件构成。因此,维护这些较小、彼此分离的代码基更加轻松,而较小的节可按需轻松合并成较大的输出。

另外一个优势就是您可以混合搭配输出节。例如,如果您有一个来自 MusicVXML 的节,比较适合用在关于 iTunes 音乐商店的 VXML 文件中,那么可以直接在 MusicVXML 上调用一个方法,将 VXML 的这一部分包含进来。使用 servlet 时的输出与从单独一个文件中读取全部 VXML 的输出是完全相同的,但维护成本要低得多,原因就在于重用了代码和 VXML。更好的是,如果您更改音乐文件中的 VXML,所有使用该类的方法的 servlet 和应用程序都和会立即更新。最终得到的不仅仅是可维护性,还有灵活性。

服务器对客户机没有重大影响

在这里,务必注意服务器上运行的代码和电话或无线客户机实际接收到的内容之间的差异。从服务器的角度来看,您是将 VXML 放在可由客户机直接访问的静态文件中、由 servlet 载入和输出的静态文件中、从零开始生成 VXML 的 servlet 中、PHP 脚本中、CGI 程序中或者其他您能想到的东西中,有着很大的差别。每中不同类型的 VXML 提供者都有着对资源、内存等等的不同需求,有些甚至需要其他软件才能运行(例如 servlet 引擎)。因此在您确定 VoiceXML 交付框架时,必须考虑到所有这一切。

然而,所有这些都不会 真正地影响到客户机。servlet 一输出 VXML —— 无论这个 VXML 是从文件载入的还是从零开始在内存中创建的,servlet(或其他任何东西)将完成这个过程。电话客户机或无线设备实际上完全不了解它究竟是怎样 获得特定 VXML 段的,它关心的只是确实得到了 VXML。因此,从用户的角度来看,您可以随意使用任何方法。

仔细考虑就会发现,这一点将使您受益无穷。如果使用 servlet 更方便 —— 且服务器上的资源成本不太高,那么就可以使用 servlet。您不必担心某个特定的客户机或某台特定的设备是否支持 java 平台,因为客户端不需要任何 java 代码。servlet 输出 VXML,这就解决了问题。如果您更愿意延用 “旧式普通静态文件”,那么也不会有任何无线用户嘲笑您处理他们的应用程序的编程能力不足。因而您可以使用任何最灵活的方法。对于简单的应用程序,静态文件可能比较合适。在您希望实现更动态的交互时,servlet 是最好的选择 —— 而且不需要任何客户机软件就能使一切运转起来。

多页面应用程序还是多节应用程序?
本文已经介绍了多页面应用程序和多节应用程序。在功能方面,您应能够编写各种类型的应用程序来执行相同的步骤,用户实际上也无法区分出多页面应用程序和多节应用程序。这似乎暗示着,您可以任意选择多页面或多节,在给定的任何情况下,这两种应用程序都可以很好地工作。

但事实并非如此,应用程序的架构应该与应用程序的功能得到同样的重视。选择正确的架构(多页面或多节)会对您的用户造成重大影响,即便是功能完全相同。

术语复习

明确地说,多页面(multipage) 应用程序是包含两个或多个 VXML 文件的应用程序。应用程序使用 goto 元素在文件间导航,该元素的目标是另外一个 VXML 文件。

多节(multisection) 应用程序也使用 goto 元素,但链接的是同一文档中的其他部分。因此,尽管节是不同的功能 “块”,但都出现在同一个 VXML 文件中。这有时也称为单页面(single-page) 应用程序。


多节(单页面)应用程序的优点

首先应该说明,选择单页面还是多页面几乎不会对电话用户产生任何影响。电话用户基本不会看到多节和多页面应用程序之间的差异,因为网络带宽和文件大小根本不会体现在电话连接中。

但是,VoiceXML 应用程序被无线设备访问的几率与电话一样高 —— 在某些情况下,无线设备访问的更加频繁。此时两种平台就会给用户带来不同的效果。通常,无线设备可用于下载文件、应用程序或资源的带宽极为有限。下载一个冗长的 VXML 文件(构成多节应用程序的 VXML 文件往往都很长)不是个好主意。但在初次访问一个新应用程序时出现较长时间的停顿并不希奇,用户也习惯了这样的延迟。所以多页面应用程序的初始下载不会造成太大的损失,至少就用户的感觉来说是这样。

然而,初次访问过应用程序之后,用户的耐性就会大打折扣。当用户点击一个按钮或访问一项功能时,他们不希望等待。出于这方面的原因,多节应用程序在移动或无线设备上的表现更加出色。因为多节应用程序中的链接所指的是同一文件中的其他部分,而这部分已经下载过了,结果就是应用程序响应迅速。您可将类似的功能放在一起,在请求一项新功能时只引用一个新的 VXML 文件。用户尝试访问一个应用程序所有部分时,他们的忍耐程度会更高一些。与初次访问应用程序时差不多。

因此,如果您希望真正地为无线设备优化内容,就应该考虑多节应用程序。用户往往会报告,此类应用程序速度更快,需要他们付出的额外工夫也非常少。

多页面应用程序的优点

与之相对应的就是多页面应用程序,这种程序为每个选项或下级选项是用一个单独的 VXML 文件。上述许多考虑事项在这里同样适用:电话客户基本不会受到应用程序类型的影响,但无线客户会受到影响。

列出一长串多节应用程序非常适合移动设备的理由之后,使用多页面应用程序看起来似乎不那么明智。但情况并非如此。移动设备下载文件的带宽确实有限,但类似地,下载也会给它们带来一定的成本,因而下载较小的文件要比下载较大的文件节省成本。尽管存在无限带宽计划,但许多非商业用户依然按时间或字节数付费。在这种情况下,具有许多较小文件的多页面应用程序使用户能够下载自己真正需要的内容,只为这些内容付费。

此外,将大量信息合并到一个 VXML 文件中只有在用户访问 其中的绝大部分信息时才是有效的。如果一个 VXML 文件包含 20 节,但典型使用情况表明用户只会访问其中的 3 节,那么对于典型的用户来说,其余 17 节完全是浪费时间和带宽。通过使用多页面应用程序,这种问题即可消除,用户只下载自己需要的内容,而且每项功能都处于单独的一个 VXML 文件中。

理解您的选择

“正确” 的解决方案是根据具体情况结合使用多页面和多节应用程序,这或许并不出人意料。在这样做之前,您需要迈出关键性的第一步,真正地去分析和理解目标用户群。您的用户通常会使用应用程序提供的一切功能吗?或者大多数用户直接去使用某项特定功能?在决策之前,您需要获得这些信息。

了解典型的使用模式后,您需要分组一般功能,然后根据用户将这些组划分到节中。换句话说,如果 10 个构成某站点新闻部分的页面几乎总是做为一组被访问 —— 也就是,如果用户通常不仅阅读一篇文章,而是阅读所有这 10 个页面,那么可以考虑将所有这 10 个页面放到一个多页面 VXML 文件中。用户需要一次性地下载更多信息,但在多数情况下,他们要访问所有 这些信息。因此,您充分利用了他们的时间和带宽,而且文章与文章之间的切换将迅速、无缝。

另一方面,如果您发现用户很少会点击在线商店中的 “帮助” 链接,那么应将所有帮助文件放在一个单独的应用程序中,不要将其作为商店页面的一节构建。下载帮助 VXML 文件时,点击 “帮助” 的用户会感觉到略有停顿,但更多的用户不会单击 “帮助” 链接,也不必为下载这部分几乎用不到的信息付出时间和成本。

如您所见,所有这一切都建立在真正理解您的用户群的基础之上。了解用户将如何使用您的应用程序,这样就能在考虑应用程序结构时作出明智的决策。务必牢记,这里讨论的多页面/多节的内容指的都是 VoiceXML 应用程序的无线方面,电话方面基本上不受这些内容的影响。

结束语

如果您按本文中的示例进行操作,运行了一些多页面和多节应用程序(均以 servlet 为动力),那么应该已经有了长足的进步。要知道,在学习本系列的前一篇文章时,您只不过才使第一个基于 servlet 的应用程序运行起来,而现在已经有了构建在一个 VXML 文件的节之间、在多个文件之间跳转的复杂应用程序的工具。

更重要的是,您应该理解,有时输出一个较大的 VXML 文件是最好的选择,而有时输出多个较小的文件更好。在拆分应用程序方面作出正确的选择,就可以确保无线设备能够像电话客户一样流畅地访问您的应用程序功能。您无法花费过多的时间确保应用程序可以供尽可能多的人、尽可能多类型的设备访问。无论您关心的是销售收入、广告收入、在人们心目中的地位、公众认知还是单纯的声望,能访问您的应用程序的人和设备总是越多越好。

在本系列的下一篇文章中,您将在自己的窍门与技术百宝箱中再增加一种工具:JavaServer Pages(JSP)技术。用 JSP 页面输出 XML 非常容易,而 VXML 只是 XML 的一种改良。多数情况下,您不需要处理 servlet 的功能,JSP 能够为您提供动态编程能力,为您节省编写 servlet 代码的开销。请继续关注本系列下月推出的文章,了解 servlet、JSP 和动态 VoiceXML 应用程序的更多内容。

本文示例代码下载

参考资料

学习

您可以参阅本文在 developerWorks 全球站点上的 英文原文 。

“在 java Web 开发框架中创建 VoiceXML 页面”(developerWorks,2006 年 1 月):阅读本系列的第 1 篇文章。

“深入研究 VoiceXML”(developerWorks,2002 年 10 月):如果您对 VoiceXML 或 VXML 感到生疏,或者是这方面的新手,这份 developerWorks 教程能够帮助您提高 VoiceXML 技能。

Voxeo:如果您对 CallXML、CCXML 和 VoiceXML 之间的差别不够清楚,这是一个不错的资源。

Voice Browser activity 页面:关于规范的权威性信息来源就是 W3C 的 voice browser activity 页面,这里有规范、FAQ、工具和有用文章的链接。

VoiceXML 2.1 candidate recommendation status:VoiceXML 2.1 随时有可能得到 W3C 的认可。

java and XML (O'Reilly Media, Inc.):我撰写的一本图书,其中提供了大量关于 XHTML、在 Web 上提供 XML 以及向多种类型的设备提供内容的资料。

XML in a Nutshell (O'Reilly Media, Inc.):一本非常好的 XML 大全,还有专门介绍 Web 上的 XML 的章节。

获得产品和技术

Voxeo.com:注册以获得出色的 VoiceXML 工具,这也是丰富的 VoiceXML 信息来源。

Voxeo Community Tools 主页:这个站点是寻找与 VoiceXML 相关的插件和实用工具的理想起点。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值