代码设计使以后添加类似任务变得容易

时间:2017-06-13 21:07:35

标签: java oop design-patterns

例如,我有一个可以下载视频的应用。由于下载任务类似,我创建了一个下载基类。

public abstract class Download {
  public abstract void run();
}

对于可以下载视频的每个具体网站,我从基类创建一个子类:

public class DownloadYouTube extends Download {
  public void run() {
  }
}

public class DownloadVimeo() extends Download {
  public void run() {
  }
}

要查看用户想要下载的站点,我创建一个枚举并切换它以创建正确的对象,然后我调用常用方法run()。

public enum WEBSITE {
  YOUTUBE,
  VIMEO
}

public void startDownload(WEBSITE website) {
  Download download;
  switch (website) {
    case YOUTUBE:
      download = new DownloadYoutube();
      break;
    case VIMEO:
      download = new DownloadVimeo();
      break;
  }
  download.run();
}

稍后其他人可能想要添加新网站。使用这种设计并不容易。人们必须在三个地方进行编辑:他们必须改变枚举,他们必须添加一个新案例,他们必须自己编写类。

如果只是写这个课程会更好。

是否有比这更好的处理这种情况的通用代码设计或其他建议?

5 个答案:

答案 0 :(得分:3)

作为一种可能的解决方案,您可以向枚举中添加一个抽象工厂方法,该方法将创建一个必要的Download对象。 因此WEBSITE不仅会成为您支持的网站列表,还会封装每个网站的行为:

public enum WEBSITE {
    YOUTUBE {
        @Override
        public Download createDownload() {
            return new DownloadYouTube();
        }
    },
    VIMEO {
        @Override
        public Download createDownload() {
            return new DownloadVimeo();
        }
    };

    public abstract Download createDownload(); 
}

public void startDownload(WEBSITE website) {
    website.createDownload().run();
}

使用这种方法,在不定义应如何处理的情况下添加新的WEBSITE是不可能的。

答案 1 :(得分:1)

制作地图!映射是一种数据结构,允许您使用键查找任何类型的值,因此可以通过提供字符串来访问每个下载类的实例。 (你的变量'网站'可以变成这个)

import java.util.HashMap;
import java.util.Map;


    Map<String, Download> downloaders = new HashMap<String, Download>();

    downloaders.put("Youtube", new DownloadYoutube());
    downloaders.put("Vimeo", new DownloadVimeo());


    // Iterate over all downloaders, using the keySet method.
    for(String key: downloaders.keySet())
        Download d = downloaders.get(key)
    System.out.println();

注意:如果您打算使用同一个Download类的多个实例,此解决方案将无法在此处发布。

答案 2 :(得分:0)

  

如果只是写这个课程会更好。

您可以使用class literals而不是枚举:

startDownload(DownloadYouTube.class);

只需让startDownload接受Class<T extends Download>并实例化它:

public void startDownload(Class<T extends Download> type) {
    try {
        Download download = type.newInstance();
        download.run();
    } catch(Exception e) {
        e.printStacktrace();
    }
}

现在你要做的就是创建一个扩展/实现Download

的新类
class DownloadYouTube extends/implements Download {

}

Download如果只包含interface方法,则应为public abstract

interface Download {
    void run();
}

class DownloadYouTube implements Download {
    //...
}

无论如何,上面的startDownload方法都会保持不变。

WEBSITE实际应该是Website。类型标识符应以大写字母开头并使用驼峰标准:

enum Website { }

虽然我的解决方案不需要enum

您应该检查每次通话是否真的需要一个新实例。如果你调整它,似乎你可以将URL传递给Download#run

如果每次通话都需要新实例

Website用于简单访问不同的下载程序。

这意味着YOUTUBE应该可以访问DownloadYoutube

您可以将Download变为interface

interface Download {
    void run();
}

DownloadYoutubeDownloadVimeo都可以实现这一点:

class DownloadYoutube implements Download {
    public void run() {
        //...
    }
}

class DownloadVimeo implements Download {
    public void run() {
        //...
    }
}

Website也可以实现这一点:

enum Website implements Download {
    public final void run() {

    }
}

现在要求每个Website值指定要使用的Download

enum Website implements Download {
    YOUTUBE(new DownloadYoutube()),
    VIMEO(new DownloadVimeo());

    private final Download download;

    Website(Download download) {
        this.download = download;
    }

    @Override
    public final void run() {
        download.run();
    }
}

答案 3 :(得分:0)

您之前要做的决定是什么。有一种称为模板方法

的设计模式

模板方法背后的关键思想是算法的骨架是强制的,但细节属于子类。你有一个

public interface DownloadTask {
    public List<Parameter> getParameters();
    public setParameters(Map<Parameter, Object> values);
    public Future<File> performDownload();
}

有两个具体的实现

public class VimeoDownload implements DownloadTask {
  ...
}

public class YoutubeDownload implements DownloadTask {
  ...
}

或者如果你真的想要一个枚举,那么

public enum DefaultDownload implements DownloadTask {
   YOUTUBE,
   VIMEO;
}

但我不认为enum会像你想象的那样买你。通过尝试更新枚举,您必须共享类似的方法

 public enum DefaultDownload implements DownloadTask {
   YOUTUBE,
   VIMEO;

   public void someMethod(...) {
   }
}

或单独声明

public enum DefaultDownload implements DownloadTask {        YOUTUBE(){          public void someMethod(...){          }        },        VIMEO(){          public void someMethod(...){          }        };     }

这两种情况都会让您有可能破坏所有下载以添加一个新的下载。

然后你有实际的模板方法将它们连接在一起

public class Downloader {

    /* asynchronous API */
    public File doDownload(DownloadTask task) {
        Map<Parameter, Object> paramValues = new Hashmap<>();
        List<Parameter> params = task.getParameters();
        for (Parameter param : task.getParameters()) {
            Object value = getValue(param);
            paramValues.put(param, value);
        }
        task.setParameters(paramValues);
        return task.performDownload();
    }

    /* synchronous API */
    public File doDownloadAndWait(DownloadTask task) {
        Future<File> future = doDownload(task);
        return future.get();
    }

}

我在DownloadTask中提供的实际步骤并不是您可能需要的步骤。改变它们以满足您的需求。

有些人没有使用DownloadTask的界面,这很好。您可以使用抽象类,如此

 public abstract class DownloadTask {

     public final Future<File> doDownload() {
        this.getParameters();
        this.setParameters(...);
        return this.performDownload();
     }

     public final File doDownloadAndWait() {
        Future<File> future = this.performDownload();
        return future.get();
     }

     protected abstract List<Parameter> getParameters();

     protected abstract void setParameters(Map<Parameter, Object> values);

     protected abstract Future<File> performDownload();

 }

这实际上是一种更好的面向对象设计,但必须注意。保持继承树浅(在访问标准Java库类之前一个或两个父级)以便将来进行代码维护。并且不要试图使模板方法(doDownload和doDownloadAndWait)非最终。毕竟,这是这种模式的关键,操作模板是固定的。如果没有它,那么几乎没有一种模式可以追随,而且东西可能变得混乱。

答案 4 :(得分:0)

你朝着正确的方向前进......

实现界面而不是扩展课程将使您的生活更轻松。你还必须更新一个枚举并修改一个case语句并编写一个类来添加一个新的处理程序,这有点令人讨厌。

编辑3个地方很烦人,2个很好,1个很棒。

我们可以很容易地将你的情况降到2: public enum WEBSITE { YOUTUBE(new DownloadYouTube()), VIMEO(new DownloadVimeo()) public final Download download; public WEBSITE(Download dl) { download = dl; } } public void startDownload(WEBSITE website) { website.download.run() } 现在你只编辑两个地方,ENUM定义和新类。枚举的一个大问题是你几乎总是需要编辑2个地方(你必须更新枚举以重定向到你想编辑的任何地方)。在这种情况下,enum根本不会帮助你。

我的第一个枚举规则是,如果您不在代码的其他位置单独处理枚举的每个值,则不应使用枚举。

你可以把它归结为1个编辑位置,但是新课程很难 - 难的是Java没有&#34; Discovery&#34;所以你可以& #39;只要问所有实现&#34;下载&#34;。

的课程

一种方法可能是使用Spring和注释。 Spring可以扫描类。另一个是运行时注释。最糟糕的可能是查看包含你的&#34;下载&#34;的目录。类并尝试实例化每一个。

原样,你的解决方案并不糟糕。 2个地点往往是一个非常好的平衡。