电脑故障

位置:IT落伍者 >> 电脑故障 >> 浏览文章

使用设计模式改善程序结构


发布日期:2022/3/20
 

设计模式是对特定问题经过无数次经验总结后提出的能够解决它的优雅的方案但是如果想要真正使设计模式发挥最大作用仅仅知道设计模式是什么以及它是如何实现的是很不够的因为那样就不能使你对于设计模式有真正的理解也就不能够在自己的设计中正确恰当的使用设计模式本文试图从另一个角度(设计模式的意图动机)来看待设计模式通过这种新的思路设计模式会变得非常贴近你的设计过程并且能够指导简化你的设计最终将会导出一个优秀的解决方案

介绍

在进行项目的开发活动中有一些设计在项目刚刚开始工作的很好但是随着项目的进展发现需要对已有的代码进行修改或者扩展导致这样做的原因主要有新的功能需求的需要以及对系统进一步理解在这个时候我们往往会发现进行这项工作比较困难即使能完成也要付出很大的代价此时一个必须要做的工作就是要对现有的代码进行重构(refactoring)通过重构使得我们接下来的工作变得相对容易

重构就是在不改变软件系统代码的外部行为的前提下改善它的内部结构重构的目标就是使代码结构更加合理富有弹性能够适应新的需求新的变化对于特定问题给出优美解决方案的设计模式往往会成为重构的目标而且一旦我们能够识别出能够解决我们问题的设计模式将会大大简化我们的工作因为我们可以重用别人已经做过的工作但是在我们的原始设计和最终可能会适用于我们的设计模式间的过渡并不是平滑的而是有一个间隙这样的结果就是即使我们已经知道了很多的设计模式面对我们的实际问题我们也没有一个有效的方法去判断哪一个设计模式适用于我们的系统我们应该去怎样应用它

造成上述问题的原因往往是由于过于注重设计模式所给出的解决方案这个结果而对于设计模式的意图以及它产生的动机却忽略了然而正是设计模式的意图动机促使人们给出了一个解决一类问题的方案这个结果设计模式的动机意图体现了该模式的形成思路所以更加贴近我们的实际问题从而会有效的指导我们的重构历程本文将通过一个实例来展示这个过程

在本文中对例子进行了简化这样做是为了突出问题的实质并且会使我们的思路更加清晰思路本身才是最重要最根本的简化了的例子不会降低我们所展示的思路方法的适用性

问题描述

一个完善的软件系统必须要对出现的错误进行相应的处理只有这样才能使系统足够的健壮我准备以软件系统中对于错误的处理为例来展示我所使用的思路方法

在一个分布式的网管系统中一个操作往往不会一定成功常常会因为这样或者那样的原因失败此时我们就要根据失败的原因相应的处理使错误的影响局限在最小的范围内最好能够恢复而不影响系统的正常运行还有一点很重要那就是在对错误进行处理的同时一定不要忘记通知系统的管理者因为只有管理者才有能力对错误进行进一步的分析从而查找出错误的根源从根本上解决错误

下面我就从错误处理的通告管理者部分入手开始我们的旅程假定一个在一个分布式环境中访问数据库的操作那么就有可能因为通信的原因或者数据库本身的原因失败此时我们要通过用户界面来通知管理者发生的错误简化了的代码示例如下

/* 错误码定义 */

class ErrorConstant

{

public static final int ERROR_DBACCESS = ;

public static final int ERROR_COMMUNICATION = ;

}

/* 省略了用户界面中的其他的功能 */

class GUISys

{

public void announceError(int errCode) {

switch(errCode) {

case ErrorConstantERROR_DBACCESS:

/* 通告管理者数据库访问错误的发生*/

break;

case ErrorConstantERROR_COMMUNICATION:

/* 通告管理者通信错误的发生*/

break;

}

}

}

开始这段代码工作的很好能够完成我们需要的功能但是这段代码缺少相应的弹性很难适应需求的变化

问题分析

熟悉面向对象的读者很快就会发现上面的代码是典型的结构化的方法结构化的方法是以具体的功能为核心来组织程序的结构它的封装度仅为即仅有对于特定的功能的封装(函数)这使得结构化的方法很难适应需求的变化面向对象的方法正是在这一点上优于结构化的方法在面向对象领域是以对象来组成程序结构的一个对象有自己的职责通过对象间的交互来完成系统的功能这使得它的封装度至少为即封装了为完成自己职责的方法和数据另外面向对象的方法还支持更高层次的封装比如通过对于不同的具体对象的共同的概念行为进行描述我们可以达到级的封装度- 抽象的类(在Java中就是接口)封装的层次越高抽象的层次就越高使得设计代码有越高的弹性越容易适应变化

考虑对上一节中的代码如果在系统的开发过程中发现需要对一种新的错误进行处理比如用户认证错误我们该如何做使得我们的系统能够增加对于此项功能的需求呢?一种比较简单直接的做法就是在增加一条用来处理此项错误的case语句是的这种方法的确能够工作但是这样做是要付出代价的

首先随着系统的进一步开发可能会出现更多的错误类型那么就会导致对于错误的处理部分代码冗长不利于维护其次也是最根本的一点修改已经能够工作的代码很容易引入错误并且在很多的情况下错误都是在不经意下引入的对于这种类型的错误很难定位有调查表明我们在开发过程中用于修正错误的时间并不多大部分的时间是在调试发现错误在面向对象领域有一个很着名的原则OCP(OpenClosed Principle)它的核心含意是一个好的设计应该能够容纳新的功能需求的增加但是增加的方式不是通过修改又有的模块(类)而是通过增加新的模块(类)来完成的如果一个设计能够遵循OCP那么就能够有效的避免上述的问题

要是一个设计能够符合OCP原则就要求我们在进行设计时不能简单的以功能为核心要实现OCP的关键是抽象抽象表征了一个固定的行为但是对于这个行为可以有很多个不同的具体实现方法通过抽象我们就可以用一个固定的抽象的概念来代替哪些容易变化的数量众多的具体的概念并且使得原来依赖于哪些容易变化的概念的模块依赖于这个固定的抽象的概念这样的结果就是系统新的需求的增加仅仅会引起具体的概念的增加而不会影响依赖于具体概念的抽象体的其他模块在实现的层面上抽象体是通过抽象类来描述的在Java中是接口(interface)关于OCP的更详细描述请参见参考文献[]

既然知道了问题的本质以及相应的解决方法下面就来改善我们的代码结构

初步方案

让我们重新审视代码看看该如何进行抽象在错误处理中需要处理不同类型的错误每个具体的错误具有特定于自己本身的一些信息但是它们在概念层面上又是一致的比如都可以通过特定的方法接口获取自已内部的错误信息每一个错误都有自己的处理方法由此可以得到一个初步的方案可以定义一个抽象的错误基类在这个基类里面定义一些在概念上适用于所有不同的具体错误的方法每个具体的错误可以有自己的不同的对于这些方法的实现代码示例如下

interface ErrorBase

{

public void handle()

public String getInfo();

}

class DBAccessError implements ErrorBase

{

public void handle() {

/* 进行关于数据库访问错误的处理 */

}

public String getInfo() {

/* 构造返回关于数据库访问错误的信息 */

}

}

class CommunicationError implements ErrorBase

{

public void handle() {

/* 进行关于通信错误的处理 */

}

public String getInfo() {

/* 构造返回关于通信错误的信息 */

}

}

这样我们就可以在错误发生处构造一个实际的错误对象并以ErrorBase引用它然后交给给错误处理模块此时错误处理模块就仅仅知道一个类型ErrorBase而无需知道每一个具体的错误类型这样就可以使用统一的方式来处理错误了代码示例如下

class GUISys

{

public void announceError(ErrorBase error) {

/* 使用一致的方式处理错误 */

er

上一篇:多线程从线程继承

下一篇:多线程中的死锁问题