PHP开发:从程序化到面向对象编程
$this->businessLogic->getEventById(
foreach (retrieveEvents($client, $_GET['showThisCalendar']) as $event) {
[...]
简单来说,这意味着具体实施应依赖于抽象类。类越趋近抽象,它们就越不容易发生改变。因此我们可以这样理解:变更频繁的类应依赖于其它更为稳定的类。所以任何应用中最不稳定的部分很可能是用户界面,这在我们的应用示例中通过Presenter类来实现。让我们再来明确一下依赖倒置流程。
return;
这个例子恐怕要算我们此次编写的代码中最为复杂的函数。它所调用的是名为putTitle()的辅助函数,其作用是将某些经过格式调整的HTML输出以充当标题。标题中将包含我们日程表的实际名称,这是通过调用来自functions_google_api.php文件中的getCalendar()函数来实现的。返回的日历信息是一个数组,其中包含一个summary字段,而这正是我们要找的内容。
print('<br>');
$this->businessLogic = new Logic();
private $presenter;
接下来,我们将使用Presenter中的printCalendarContents()方法,如下所示:
然而仅仅消除函数指针还远远不够。面向对象的编程机制必然带来替代方案,事实也确实如此,它包含着多态特性与一套简单语法。重点来了,多态性正是面向对象编程的核心价值,即:控制流与源代码在依赖关系上正好相反。
红色线条则引入了完全不同的概念,它们代表着最初的源代码依赖关系。之所以说“最初”,是因为随着应用的运行其指向将变得愈发复杂、难以把握。putMenu()方法中包含着被特定关系所调用的函数的名称。这是一种依赖关系,同时也是适用于所有其它关系创建方法的基本规则。它们的具体关系取决于其它函数的行为。
global $client;
return getEventList($client, htmlspecialchars($_GET['showThisCalendar']))['items'];
require_once __DIR__ . '/../apiAccess.php';
摆脱全局变量
谷歌专门针对PHP提供一套API客户端,我们将利用它与自己的谷歌账户进行对接,从而对日程表服务加以操作。要想让代码正确起效,大家需要通过设定让自己的谷歌账户接受来自日程表的查询。
putMenu();
• functions_google_api.php – 囊括所有前面提到的谷歌API。
$client = createClient();
return;
function printCalendarContents($client) {
试手任务
[...]
(new Router($presenter))->doUserAction();
global $client;
print('<div display="block">'.$text.'</div>');
}
function printMenu() {
对于当前代码我们可以绘制出一份关系图,内容如下所示。通过这幅关系图,我们可以看到应用程序运行流程的前几个步骤。当然,把整套流程都画下来就太过复杂了。
}
$this->putCalendarListElement($calendar);
function isCurrentEvent($event) {
}
现在惟一漏网的潜在设计缺陷就只剩下$client全局变量了。应用程序中的所有代码都会对其进行访问,但与之形成鲜明对比的是,真正有必要访问$client的只有Logic类一个。最直观的解决办法肯定是使其变更为专用类变量,但这样一来我们就需要将$client经由Router传递至Presenter处,从而使presenter能够利用$client变更创建出Logic对象——这对于解决问题显然无甚作用。我们的设计初衷是在独立环境下建立类,并准确为其分配依赖关系。
putBlock('This event has status ' . $event['status']);
foreach ($this->businessLogic->getEventsForCalendar() as $event) {
以上变更完成之后,点击Show Calendars,屏幕上会出现错误提示。由于我们链接内部的所有行为都指向Logic类中的函数名称,因此必须通过更多一致性调整来恢复二者之间的依赖关系。下面我们对方法进行一一修改,先来看第一条错误信息:
恢复源代码依赖关系
}
范式局限
}
$_GET['showThisEvent'],
}
require_once './Presenter.php';
B:抽象不应依赖于细节,细节应依赖于抽象。
(new Presenter())->putMenu();
如果要为目前的代码绘制依赖关系类图,则应如下所示:
putLink('?action=printCalendars', 'Show Calendars');
function foo() {
function doUserAction() {
$function(); // Goes into foo()
require_once './google-api-php-client/src/Google_Client.php';
if(!authenticate($client)) return;
$eventsForCalendar = $this->businessLogic->getEventsForCalendar(htmlspecialchars($_GET['showThisCalendar']));
}
foreach (getCalendarList($client)['items'] as $calendar) {
}
' and last updated at ' .
function putTitle($text) {
global $client;
尽管该方法只负责显示信息,但其功能仍需在对$event结构非常了解的前提下方能实现。不过对于我们的简单实例来说,这已经足够了。对于其余方法,大家可以通过类似的方式进行分离。
这里有一项名为doUserAction()的函数,它的生效与否取决于一条很长的if-else声明;其它方法则根据GET变量中的参数决定调用情况。这些方法随后利用API与谷歌日程表对接,并在屏幕上显示出我们需要的任何结果。
putTitle('Details for event: '. $event['summary']);
我们当前的代码完全没问题,但我想我们可以通过调整使其以更合适的方式组织起来。大家可能已经从附带的源代码中发现,该项目所有已经组织完成的代码都被命名为“GoogleCalProceduralOrganized”。
因此我们需要保证只有Presenter感知到这一情况,即将以上两个方法变换为下列内容:
对于任何大型类结构,我们都倾向于使用Factories;但在本文的小小范例中,index.php文件已经足以容纳逻辑创建了。作为应用程序的入口点,这个类似于高层体系结构中“main”的文件仍然处于业务逻辑的范畴之外。
A:高层模块不应依赖于低级模块,二者都应依赖于抽象。
$this->putEvent(
function printCalendarContents() {
function getEventById($eventId, $calendarId) {
就是这样,我们已经成功完成了依赖倒置。
function putHome() {
每种编程范式都限制了我们将想象转化为现实的能力。这些范式去掉了一部分可行方案,却纳入另一些方案作为替代,但这一切都是为了实现同样的表示效果。模块化编程令程序规模受到制约,强迫程序员只能在对应模块范畴之内施展拳脚,且每个模块结尾都要以“go-to”来指向其它模块。这种设定直接影响了程序成品的规模。另外,结构化编程与程序化编程方式去掉了“go-to”声明,从而限制了程序员对序列、选择以及迭代语句的调整能力。序列属于变量赋值,选择属于if-else判断,而迭代则属于do-while循环。这些已经成为当下编程语言与范式的构建基石。
putLink('?home', 'Home');
}
其余部分代码与我们之前讨论过的内容相近,甚至更容易理解。大家可以抱着轻松的心情随便看看,然后抖擞精神进军下一章。
}