电脑故障

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

敏捷开发的必要技巧:处理不合适的依赖


发布日期:2024/4/28
 

摘要

要判断一个代码是不是包含了不合适的依赖共有四个方法看代码有没有互相依赖?认真想想它真正需要的是什么?推测一下它在以后的系统中可以重用吗?到要重用的时候就知道了现在我要重用这个类能不能重用?

如果现在有一个类Parent里面有个属性的类型是Childadd的方法里面还有个参数的类型是Girl

class Parent{

Child child;

void add(Girl girl){

}

}

因为上面Parent里面用到了Child跟Girl这两个类我们就说Parent引用了类Child跟类Girl现在的问题是如果Child这个类或者Girl这个类编译不过的话那么Parent这个类也编译不了了也就是说Parent依赖于Child跟Girl这章讲述的就是因为一些类的依赖造成的无法重用的问题

示例

这是一个处理ZIP的程序用户可以在主窗口中先输入要生成的目标zip的路径比如c:\fzip 然后输入他想压缩到这个zip的源文件的路径比如

c:\fdoc和c:\fdoc 然后这个程序就会开始压缩fdoc和fdoc生成fzip文件在压缩各个源文件的时候主窗口下的状态栏都要显示相关的信息比如在压缩c:\fdoc的时候状态栏就显示正在压缩 c:\fzip

目前的代码就是

class ZipMainFrame extends Frame {

StatusBar sb;

void makeZip() {

String zipFilePath;

String srcFilePaths[];

//根据UI上给zipFilePath和srcFilePaths赋值

ZipEngine ze = new ZipEngine();

zemakeZip(zipFilePath srcFilePaths this);

}

void setStatusBarText(String statusText) {

sbsetText(statusText);

}

}

class ZipEngine {

void makeZip(String zipFilePath String srcFilePaths[] ZipMainFrame f) {

//在该路径上创建zip文件

for (int i = ; i < srcFilePathslength; i++) {

//将srcFilePaths[i]的文件加到压缩包中

fsetStatusBarText(Zipping +srcFilePaths[i]);

}

}

}

我们还有一个存货管理系统里面有一些程序的数据文件经常需要压缩起来备份这些源数据文件都有固定的路径所以就不需要用户特地去输入路径了现在我们想直接把上面的这个ZipEngine类拿过来重用这个存货管理系统也有一个主窗口同样在压缩待备份文件时状态栏上面也要显示目前正在压缩的文件名称

现在问题来了我们希望可以在不用修改代码的情况下直接重用ZipEngine这个类但看了上面的代码以后我们发现在调用makeZip这个方法时还需要一个传递一个ZipMainFrame类型的参数进来可是很明显我们现在的这个存货管理系统里面并没有ZipMainFrame这样的类也就是说现在ZipEngine这个类在我们的这个存货管理系统中用不了了

再往远一点想好像其他的系统一般也不会有ZipMainFrame这个类即使类名一样的里面所做的功能也不一样那其他的系统也重用不了这个ZipEngine类了

不合适的依赖让代码很难被重用

因为ZipEngine引用了ZipMainFrame这个类当我们想重用ZipEngine的时候我们就需要将ZipMainFrame也加进来调用ZipEngine的makeZip方法时还要构造一个ZipMainFrame对象传给它而在新的环境中我们不可能有一个同样的ZipMainFrame也不可能特地为了调用这个方法随便创建一个ZipMainFrame对象给它

一般来说如果一个类A引用了一个类B当我们想要重用A这个类时我们就还得将B这个类也加进我们的系统如果B引用了C那么B又将C也一起拉了进来而如果B或者C在一个新的系统中没有意义或者压根儿不应该存在的情况下真正我们想要用的A这个类也用不了了

因此不合适的依赖让代码很难被重用

为了可以重用ZipEngine首先我们得让ZipEngine不再引用ZipMainFrame或者说让ZipEngine不用依赖于ZipMainFrame

那怎么做呢?回答这个问题之前我们先回答另一个问题给你一段代码你怎么判断这段代码是不是包含了不合适的依赖不合适这个词定义的标准又是什么?

怎么判断是不合适的依赖

方法

一个简单的方法就是我们先看一下这段代码里面有没有一些互相循环的引用比如ZipMainFrame引用了ZipEngine这个类而ZipEngine又引用了ZipMainFrame我们管这样的类叫互相依赖互相依赖也是一种代码异味我们就认定这样的代码不合适的依赖

这个方法很简单不过这种方法并不能包含全部情况并不是所有有不合适的依赖的代码都是这种互相依赖

方法

另一个方法比较主观在检查代码的时候我们问自己对于它已经引用的这些类是它真正需要引用的吗?对于ZipEngine它真的需要ZipMainFrame这个类吗?ZipEngine只是改变ZipMainFrame的状态栏上的信息是不是只有引用了ZipMainFrame才能满足这样的需求其他类行不行?有没有一个类可以取代ZipMainFrame呢?而实际上ZipEngine并不是一定要引用ZipMainFrame的它想引用的其实只是一个可以显示信息的状态栏而已

因此我们就将代码改为

class ZipEngine {

void makeZip(String zipFilePath String srcFilePaths[] StatusBar statusBar) {

//在该路径上创建zip文件

for (int i = ; i < srcFilePathslength; i++) {

//将srcFilePaths[i]的文件加到压缩包中

statusbarsetText(Zipping +srcFilePaths[i]);

}

}

}

现在ZipEngine只是引用了StatusBar而不再是ZipMainFrame了可是这样好吗?相对好一些!因为StatusBar比较通用(至少有StatusBar这个类的系统比ZipMainFrame多多了)这样的话ZipEngine这个类的可重用性就大幅改观了

不过这样的方法还是太主观了没有一个既定的标准可以判断ZipEngine到底需要的是什么样的东西比如我们就说ZipEngine其实想要的也不是一个状态栏它只是想调用一个可以显示一些信息的接口而已(而不是一个状态栏这么大的一个对象)

方法

种方法也很主观在设计类的时候我们先预测一个以后可能会重用这个类的系统然后再判断在那样的系统中这个类能不能被重用?如果你自己都觉得以后的系统不能重用这个类的话你就断定这个类包含不合适的依赖

比如我们在设计完ZipEngine这个类时我们就想一下这个类能在别的系统重用吗?可是好像别的系统不会有ZipMainFrame这个类至少一个没有GUI的系统会有这样的类!这样的话那它就不应该引用ZipMainFrame这个类了这个方法其实也很主观不怎么实用每个人预测的可能性都不一样

方法

个方法比较简单而且客观了当我们想在一个新系统中重用这个类却发现重用不了时我们就判断这个类包含了不合适的依赖比如我们在存货管理系统中要重用ZipEngine的时候我们才发现这个类重用不了这时我们就认定这个类有不合适的依赖

后一种方法是个懒惰而被动的方法因为我们真正想在具体的项目中重用的时候才能判断出来不过这也是个很有效的方法

总结

要判断一个代码是不是包含了不合适的依赖共有四个方法

看代码有没有互相依赖?

认真想想它真正需要的是什么?

推测一下它在以后的系统中可以重用吗?

到要重用的时候就知道了现在我要重用这个类能不能重用?

方法是最简单的方法推荐初学者可以这样来判断有更多的设计经验了再用方法会好一些

怎么让ZipEngine不再引用(依赖于)ZipMainFrame

现在我们来看看怎么让ZipEngine不再引用ZipMainFrame其实在介绍方法的时候我们就已经通过思考发现ZipEngine这个类真正需要的是什么也找出了解决办法不过因为方法相对来讲并不是那么简单就可以用好的所以我们先假装不知道方法的结果

我们用方法现在我们是在做一个文字模式的系统(没有状态栏了我们只能直接在没有图形的屏幕上显示这些信息)发现ZipEngine不能重用了怎么办?

因为我们不能重用ZipEngine我们只好先将它的代码复制粘贴出来然后再修改成下面的代码

class TextModeApp {

void makeZip() {

String zipFilePath;

String srcFilePaths[];

ZipEngine ze = new ZipEngine();

zemakeZip(zipFilePath srcFilePaths);

}

}

class ZipEngine {

void makeZip(String zipFilePath String srcFilePaths[]) {

//在该路径上创建zip文件

for (int i = ; i < srcFilePathslength; i++) {

//将srcFilePaths[i]的文件加到压缩包中

Systemoutprintln(Zipping +srcFilePaths[i]);

}

}

再看一下原来的代码是

class ZipEngine {

void makeZip(String zipFilePath String srcFilePaths[] ZipMainFrame f) {

//在该路径上创建zip文件

for (int i = ; i < srcFilePathslength; i++) {

//将srcFilePaths[i]的文件加到压缩包中

fsetStatusBarText(Zipping +srcFilePaths[i]);

}

}

}

很明显这里面有很多的重复代码(代码异味)要消除这样的代码异味我们就先用伪码让这两段

代码看起来一样比如改成

class ZipEngine {

void makeZip(String zipFilePath String srcFilePaths[]) {

//在该路径上创建zip文件

for (int i = ; i < srcFilePathslength; i++) {

//将srcFilePaths[i]的文件加到压缩包中

显示信息

}

}

}

因为显示信息具体出来有两种实现所以我们现在创建一个接口里面有一个方法用来显示信息这个方法可以直接取名为showMessage而根据这个接口做的事我们也可以直接将接口名取为MessageDisplay或者MessageSink之类的

interface MessageDisplay {

void showMessage(String msg);

}

将ZipEngine改为

class ZipEngine {

void makeZip(String zipFilePath String srcFilePaths[] MessageDisplay

msgDisplay) {

//在该路径上创建zip文件

for (int i = ; i < srcFilePathslength; i++) {

//将srcFilePaths[i]的文件加到压缩包中

msgDisplayshowMessage(Zipping +srcFilePaths[i]);

}

}

}

而MessageDisplay这个接口的两个实现类就是

class ZipMainFrameMessageDisplay implements MessageDisplay {

ZipMainFrame f;

ZipMainFrameMessageDisplay(ZipMainFrame f) {

thisf = f;

}

void showMessage(String msg) {

fsetStatusBarText(msg);

}

}

class SystemOutMessageDisplay implements MessageDisplay {

void showMessage(String msg) {

Systemoutprintln(msg);

}

}

现在两个系统也相应的做了修改

class ZipMainFrame extends Frame {

StatusBar sb;

void makeZip() {

String zipFilePath;

String srcFilePaths[];

//根据UI上给zipFilePath和srcFilePaths赋值

ZipEngine ze = new ZipEngine();

zemakeZip(zipFilePath srcFilePaths new ZipMainFrameMessageDisplay(this));

}

void setStatusBarText(String statusText) {

sbsetText(statusText);

}

}

class TextModeApp {

void makeZip() {

String zipFilePath;

String srcFilePaths[];

ZipEngine ze = new ZipEngine();

zemakeZip(zipFilePath srcFilePaths new SystemOutMessageDisplay());

}

}

改进后的代码

下面就是改进完的代码为了让代码看起来清楚一些我们用了Java的内类

interface MessageDisplay {

void showMessage(String msg);

}

class ZipEngine {

void makeZip(String zipFilePath String srcFilePaths[] MessageDisplay

msgDisplay) {

//在该路径上创建zip文件

for (int i = ; i < srcFilePathslength; i++) {

//将srcFilePaths[i]的文件加到压缩包中

msgDisplayshowMessage(Zipping +srcFilePaths[i]);

}

}

}

class ZipMainFrame extends Frame {

StatusBar sb;

void makeZip() {

String zipFilePath;

String srcFilePaths[];

//根据UI上给zipFilePath和srcFilePaths赋值

ZipEngine ze = new ZipEngine();

zemakeZip(zipFilePath srcFilePaths new MessageDisplay() {

void showMessage(String msg) {

setStatusBarText(msg);

}

});

}

void setStatusBarText(String statusText) {

sbsetText(statusText);

}

}

class TextModeApp {

void makeZip() {

String zipFilePath;

String srcFilePaths[];

ZipEngine ze = new ZipEngine();

zemakeZip(zipFilePath srcFilePaths new MessageDisplay() {

void showMessage(String msg) {

Systemoutprintln(msg);

}

});

}

}

引述

依赖反转原则(Dependency Inversion Principle )表述抽象不应该依赖于具体高层的比较抽象的类不应该依赖于低层的比较具体的类当这种问题出现的时候我们应该抽取出更抽象的一个概念然后让这两个类依赖于这个抽取出来的概念更多的信息可以看

;

;

上一篇:类的基本概念

下一篇:JVM指令系统的组成及使用