面向对象解决方案 无论采用哪种范式,我们都不可能为问题找到完美的解决方案。因此以下代码组织方式仅仅属于我的个人建议。 从直觉出发 我们已经完成了业务逻辑与表现的分离工作,甚至将doUserAction()方法作为一个独立单元。那么我的直觉是先创建三个类,Presenter、Logic与Router。三者以后可能都需要进行调整,但我们不妨先从这里着手,对吧? Router中将只包含一个方法,且实现方式与之前提到的方法非常相似。
现在我们要做的是利用刚刚创建的Presenter对象调用putMenu()方法,其它行为则利用Logic对象加以调用。不过这样会马上产生问题——我们的一项行为并不包含在Logic类当中。putHome()存在于Presenter类中,我们需要在Logic中引入一项行为,借以在Presenter中作为putHome()方法的委托。请记住,目前我们要做的只是将现有代码整理到三个类当中,并将三者作为面向对象设计的备选对象。现在所做的一切只是为了让设计方案能够正常运作,待代码编写完成后、我们将进一步加以调试。 在将putHome()方法引入Logic类后,我们又遇上新的难题。怎样才能从Presenter中调用方法?我们可以创建一个Presenter对象,并将其传递至Logic当中。下面我们从Router类入手。
现在我们可以向Logic添加一个构造函数,并将其添加到Presenter内指向putHome()的委托当中。
通过对index.php的一些小小调整、让Presenter包含原有display方法、Logic包含原有业务逻辑函数、Router包含原有行为选择符,我们已经可以让自己的代码正常运行并具备“Home”菜单元素。
下面就是其执行效果。 接下来,我们需要在Logic类中适当变更指向display逻辑的调用指令,从而与$this->presenter相符。现在我们有两个方法——isCurrentEvent()与retrieveEvents()——二者只被用于Logic类内部。我们将其作为专用方法,并据此变更调用关系。 下面我们对Presenter类进行同样处理,并将所有指向方法的调用都变更为指向$this->something。由于putTitle()、putLink()与putBlock()都只由Presenter使用,因此需要将其变为专用。如果感到上述变更过程难于理解及操作,请大家查看附件源代码内GoogleCalObjectOrientedInitial文件夹中的已完成代码。 现在我们的应用程序已经能够正常运行,这些按面向对象语法整理过的程序化代码仍然使用$client全局变量,且拥有大量其它非面向对象式特性——但仍然能够正常运行。 如果要为目前的代码绘制依赖关系类图,则应如下所示: 控制流与源代码的依赖关系都通过Router、然后是Logic、最后通过表示层。最后一步变更削弱了我们在之前步骤中所观察到的依赖倒置特性,但大家千万不要因此受到迷惑——原理依然如故,我们要做的是使其更加清晰。 恢复源代码依赖关系 很难界定基础性原则之间哪一条更重要,但我认为依赖倒置原则对我们的应用设计影响最大也最直接。该原则规定: A:高层模块不应依赖于低级模块,二者都应依赖于抽象。 B:抽象不应依赖于细节,细节应依赖于抽象。 简单来说,这意味着具体实施应依赖于抽象类。类越趋近抽象,它们就越不容易发生改变。因此我们可以这样理解:变更频繁的类应依赖于其它更为稳定的类。所以任何应用中最不稳定的部分很可能是用户界面,这在我们的应用示例中通过Presenter类来实现。让我们再来明确一下依赖倒置流程。 首先,我们让Router仅使用Presenter,并打破其对Logic的依赖关系。
然后我们变更Presenter,使其使用Logic实例并由此获取需要的信息。在我们的例子中,我认为由Presenter来建立该Logic实例也可以接受,但在生产系统当中、大家可能通常会利用Factories来创建与对象相关的业务逻辑,并将其注入表示层当中。 现在,原本同时存在于Logic与Presenter两个类中的putHome()函数将从Logic中消失。这一现象说明我们已经开始进行重复数据清除工作。指向Presenter的构造函数与引用也从Logic中消失了。另一方面,由构造函数所创建的Logic对象则必须被写入Presenter。
以上变更完成之后,点击Show Calendars,屏幕上会出现错误提示。由于我们链接内部的所有行为都指向Logic类中的函数名称,因此必须通过更多一致性调整来恢复二者之间的依赖关系。下面我们对方法进行一一修改,先来看第一条错误信息:
我们的Router希望调用Presenter中某个并不存在的方法,也就是printCalendars()。我们在Presenter中创建这样一个方法,并检查它会对Logic造成哪些影响。在结果中大家可以看到,它输出了一条标题,并在重复循环之后再次调用putCalendars()。在Presenter类中,printCalendars()方法如下所示:
在Logic方面,该方法则非常单纯——直接调用谷歌API库。
这可能让大家心中出现两个问题,“我们真的需要Logic类吗?”以及“我们的应用程序是否存在任何逻辑?”好吧,目前我们还不知道答案,现在能做的只是继续上述过程,直到所有代码都能正常工作且Logic不再依赖于Presenter。 接下来,我们将使用Presenter中的printCalendarContents()方法,如下所示:
这将反过来允许我们简化Logic中的getEventsForCalendar(),并将其转化为如下形式:
现在应用已经不再报错,但我却又发现了新的问题。$_GET变量同时被Logic与Presenter类所使用——$_GET应该只被Presenter类使用才对。我的意思是,由于需要创建用于填充$_GET变量的链接,Presenter是肯定需要感知$_GET的。这就意味着$_GET与HTTP密切相关。现在,我们希望自己的代码能与命令行或者桌面图形用户界面协同运作。 |