例如,我有一个可以下载视频的应用。由于下载任务类似,我创建了一个下载基类。
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();
}
稍后其他人可能想要添加新网站。使用这种设计并不容易。人们必须在三个地方进行编辑:他们必须改变枚举,他们必须添加一个新案例,他们必须自己编写类。
如果只是写这个课程会更好。
是否有比这更好的处理这种情况的通用代码设计或其他建议?
答案 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();
}
DownloadYoutube
和DownloadVimeo
都可以实现这一点:
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个地点往往是一个非常好的平衡。