从逻辑中分离表示 某些函数的作用非常明确——只用于在屏幕上输出信息,但有些函数则用于判断触发条件,更有些函数身兼两种作用。面对这种情况,我们往往最好把这些存在特殊用途的函数放在属于自己的文件当中。我们首先整理只用于屏幕信息输出的函数,并将其转移到functions_display.php文件当中。具体做法如下所示: - function printHome() {
- print('Welcome to Google Calendar over NetTuts Example');
- }
-
- function printMenu() {
- putLink('?home', 'Home');
- putLink('?showCalendars', 'Show Calendars');
- putLink('?logout', 'Log Out');
- print('<br><br>');
- }
-
- function putLink($href, $text) {
- print(sprintf('<a href="%s" style="font-size:12px;margin-left:10px;">%s</a> | ', $href, $text));
- }
-
- function putTitle($text) {
- print(sprintf('<h3 style="font-size:16px;color:green;">%s</h3>', $text));
- }
-
- function putBlock($text) {
- print('<div display="block">'.$text.'</div>');
- }
要完成剩余的表示分离工作,我们需要从方法中提取出表示部分。下面我们就以单一方法为例演示这一过程: - function printEventDetails() {
- global $client;
- foreach (retrieveEvents($_GET['calendarId']) as $event)
- if ($event['id'] == $_GET['showThisEvent']) {
- putTitle('Details for event: '. $event['summary']);
- putBlock('This event has status ' . $event['status']);
- putBlock('It was created at ' .
- date('Y-m-d H:m', strtotime($event['created'])) .
- ' and last updated at ' .
- date('Y-m-d H:m', strtotime($event['updated'])) . '.');
- putBlock('For this event you have to <strong>' . $event['summary'] . '</strong>.');
- }
- }
我们可以明显看到,无论if声明中的内容如何、其代码都属于表示代码,而余下的部分则属于业务逻辑。与其利用一个庞大的函数处理所有事务,我们更倾向于将其拆分为多个不同函数: - function printEventDetails() {
- global $client;
- foreach (retrieveEvents($_GET['calendarId']) as $event)
- if (isCurrentEvent($event))
- putEvent($event);
- }
-
- function isCurrentEvent($event) {
- return $event['id'] == $_GET['showThisEvent'];
- }
分离工作完成后,业务逻辑就变得简单易懂了。我们甚至提取了一个小型方法来检测该事件是否就是当前事件。所有表示代码现在都由名为putEvent($event)函数负责,且被保存在functions_display.php文件当中: - function putEvent($event) {
- putTitle('Details for event: ' . $event['summary']);
- putBlock('This event has status ' . $event['status']);
- putBlock('It was created at ' .
- date('Y-m-d H:m', strtotime($event['created'])) .
- ' and last updated at ' .
- date('Y-m-d H:m', strtotime($event['updated'])) . '.');
- putBlock('For this event you have to <strong>' . $event['summary'] . '</strong>.');
- }
尽管该方法只负责显示信息,但其功能仍需在对$event结构非常了解的前提下方能实现。不过对于我们的简单实例来说,这已经足够了。对于其余方法,大家可以通过类似的方式进行分离。 清除过长的if-else声明 目前代码整理工作还剩下最后一步,也就是存在于doUserAction()函数中的过长if-else声明,其作用是决定每项行为的实际处理方式。在元编程方面(通过引用来调用函数),PHP具备相当出色的灵活性。这种特性使我们能够将$_GET变量的值与函数名称关联起来。如此一来,我们可以在$_GET变量中引入单独的action参数,并将该值作为函数名称。 - function doUserAction() {
- putMenu();
- if (!isset($_GET['action'])) return;
- $_GET['action']();
- }
基于这种方式,我们生成的菜单将如下所示: - function putMenu() {
- putLink('?action=putHome', 'Home');
- putLink('?action=printCalendars', 'Show Calendars');
- putLink('?logout', 'Log Out');
- print('<br><br>');
- }
如大家所见,经过重新整理之后,代码已经呈现出面向对象式设计的特性。虽然目前我们还不清楚其面向的是何种对象、会执行哪些确切行为,但其特征已经初露端倪。 我们已经让来自业务逻辑的数据类型成为表示的决定性因素,其效果与我们在文首介绍环节中谈到的依赖倒置机制比较类似。控制流的方向仍然是从业务逻辑指向表示,但源代码依赖性则与之相反。从这一点上看,我认为整套机制更像是一种双向依赖体系。 设计倾向上的面向对象化还体现在另一个方面,即我们几乎没有涉及到元编程。我们可以调用一个方法,但却对其一无所知。该方法可以拥有任何内容,且过程与处理低级多态性非常相近。 依赖性分析 
对于当前代码我们可以绘制出一份关系图,内容如下所示。通过这幅关系图,我们可以看到应用程序运行流程的前几个步骤。当然,把整套流程都画下来就太过复杂了。
蓝色线条代表程序调用。如大家所见,这些线条与始终指向同一个方向。图中的绿色线条则表示间接调用,可以看到所有间接调用都要经过doUserAction()函数。这两种线条代表控制流,显然控制流的走向基本不变。 红色线条则引入了完全不同的概念,它们代表着最初的源代码依赖关系。之所以说“最初”,是因为随着应用的运行其指向将变得愈发复杂、难以把握。putMenu()方法中包含着被特定关系所调用的函数的名称。这是一种依赖关系,同时也是适用于所有其它关系创建方法的基本规则。它们的具体关系取决于其它函数的行为。 上图中我们还能看到另一种依赖关系,即对数据的依赖。我前面曾经提到过$calendar与$event,输出函数需要清楚了解这些数组的内部结构才能实现既定功能。 完成了以上内容之后,我们已经做好充分准备、可以迎来本篇教程中的最后一项挑战。 |