我在使用Java设计时遇到以下问题:
我需要创建一个吉他店,为不同的吉他提供调音服务。 每种吉他类型的调音都有所不同。
我需要提供一个设计(类和关系),可以处理需要调整吉他的基本场景。
所以我得到了:
所以基本上工厂获得吉他类型并返回所需的吉他服务。但吉他服务包含一种方法:
public void tuneGuitar(Guitar guitarToTune) {...}
这意味着我需要在每项服务中每次都投出我的吉他(每首吉他的调音都不同)。
我试着通过模板化GuitarService来使用泛型 但是我的工厂不能再为我服务了(我无法安全地实例化运行时确定参数的对象)。
这种情况是否有最佳做法?
答案 0 :(得分:2)
主要问题是您的Guitar
界面完全隐藏了工厂用于选择相应GuitarTuningService
实施所需的详细信息。解决问题的一种方法是实现访客模式,a.k.a。双重调度。 Guitar
接口将获得以下方法:
public void accept(GuitarVisitor visitor);
每个Guitar
实现将分别以相同的方式实现该方法:
public void accept(GuitarVisitor visitor) {
visitor.visit(this);
}
GuitarVisitor
界面如下所示:
public interface GuitarVisitor {
public void visit(Fender guitar);
public void visit(Gibson guitar);
// other Guitar types as necessary
}
GuitarVisitor
的实现在每种方法中执行适当的吉他类型特定行为。例如,GuitarTuningServiceFactory
可以使用内部GuitarVisitor
实现为每个提供的吉他选择合适的GuitarTuningService
。您无法避免强制转换(尽管您可以通过Class.cast()
执行它来避免类型安全警告),但您可以在每个tune()
方法的开头只安全地执行一次,记录向下转发参考。
Visitor
模式的主要缺点是它锁定了特定访问类的可能类型。您可以添加一个catch-all方法来部分缓解(public void visit(Guitar guitar)
)。
答案 1 :(得分:1)
下面我提供了您要求的解决方案。但我不确定,如果这是您真正需要解决问题的解决方案。因为我无法理解,为什么你需要按类类型对不同吉他的属性进行建模。但我只能假设。
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
public class Main {
public static void main(String[] args) {
Guitar f = new Fender();
Guitar g = new Gibson();
TuneService<Guitar> fts = TuneServices.getService(f);
fts.tune(f);
TuneService<Guitar> gts = TuneServices.getService(g);
gts.tune(g);
}
static interface Guitar {
}
static class Fender implements Guitar {
}
static class Gibson implements Guitar {
}
// tune service that accepts only guitars as generic types
static interface TuneService<T extends Guitar> {
public void tune(T guitar);
}
static class FenderService implements TuneService<Fender> {
@Override
public void tune(Fender guitar) {
System.out.println("Tune Fender.");
}
}
static class GibsonService implements TuneService<Gibson> {
@Override
public void tune(Gibson guitar) {
System.out.println("Tune Gibson.");
}
}
// factory class
static final class TuneServices {
private static final Map<Class<?>, TuneService<?>> services;
static {
Map<Class<?>, TuneService<?>> tmp = new HashMap<Class<?>, TuneService<?>>();
tmp.put(Fender.class, new FenderService());
tmp.put(Gibson.class, new GibsonService());
// populate services
services = Collections.unmodifiableMap(tmp);
}
public static <T extends Guitar> TuneService<T> getService(T guitar) {
TuneService<?> s = services.get(guitar.getClass());
if (s == null)
throw new IllegalArgumentException();
// cast is safe at this point
@SuppressWarnings("unchecked")
TuneService<T> ts = (TuneService<T>) s;
return ts;
}
}
}
使用这种方法,您无需在添加新的吉他服务时重新编译工厂, 当你添加一把新吉他时,你甚至可以扩展这种方法以避免重新编译。
<强> ServiceLoaderExample.java 强>
package guitar;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.ServiceLoader;
public class ServiceLoaderExample {
public static void main(String[] args) {
Guitar f = new Fender();
Guitar g = new Gibson();
TuneService<Guitar> fts = TuneServices.getService(f);
fts.tune(f);
TuneService<Guitar> gts = TuneServices.getService(g);
gts.tune(g);
}
static interface Guitar {
}
static class Fender implements Guitar {
}
static class Gibson implements Guitar {
}
// tune service that accepts only guitars as generic types
static interface TuneService<T extends Guitar> {
public void tune(T guitar);
}
// factory class
static final class TuneServices {
private static final Map<Class<?>, TuneServiceProvider<?>> providers;
static {
@SuppressWarnings("rawtypes")
ServiceLoader<TuneServiceProvider> loader = ServiceLoader.load(TuneServiceProvider.class);
Map<Class<?>, TuneServiceProvider<?>> tmp = new HashMap<Class<?>, TuneServiceProvider<?>>();
for (TuneServiceProvider<?> s : loader) {
tmp.put(s.getGuitarType(), s);
}
// populate providers
providers = Collections.unmodifiableMap(tmp);
}
public static <T extends Guitar> TuneService<T> getService(T guitar) {
TuneServiceProvider<?> p = providers.get(guitar.getClass());
if (p == null)
throw new IllegalArgumentException();
TuneService<?> s = p.getService();
// cast is safe at this point
@SuppressWarnings("unchecked")
TuneService<T> ts = (TuneService<T>) s;
return ts;
}
}
}
<强> TuneServiceProvider.java 强>
package guitar;
import guitar.ServiceLoaderExample.Guitar;
import guitar.ServiceLoaderExample.TuneService;
public interface TuneServiceProvider<T extends Guitar> {
public TuneService<T> getService();
public Class<T> getGuitarType();
}
<强> FenderTuneServiceProvider.java 强>
package guitar;
import guitar.ServiceLoaderExample.Fender;
import guitar.ServiceLoaderExample.TuneService;
import guitar.TuneServiceProvider;
public class FenderTuneServiceProvider implements TuneServiceProvider<Fender> {
@Override
public TuneService<Fender> getService() {
return new FenderService();
}
@Override
public Class<Fender> getGuitarType() {
return Fender.class;
}
static class FenderService implements TuneService<Fender> {
@Override
public void tune(Fender guitar) {
System.out.println("Tune Fender.");
}
}
}
<强> GibsonTuneServiceProvider.java 强>
package guitar;
import guitar.ServiceLoaderExample.Gibson;
import guitar.ServiceLoaderExample.TuneService;
import guitar.TuneServiceProvider;
public class GibsonTuneServiceProvider implements TuneServiceProvider<Gibson> {
@Override
public TuneService<Gibson> getService() {
return new GibsonService();
}
@Override
public Class<Gibson> getGuitarType() {
return Gibson.class;
}
static class GibsonService implements TuneService<Gibson> {
@Override
public void tune(Gibson guitar) {
System.out.println("Tune Gibson.");
}
}
}
如果你使用maven,/META-INF/services/guitar.TuneServiceProvider 必须在classpath或main / java / resources上
guitar.FenderTuneServiceProvider
guitar.GibsonTuneServiceProvider
两种方法的输出相同。
Tune Fender.
Tune Gibson.
两种方法都有相同的缺点,如果客户可以访问该服务。
TuneService<Guitar> fts = TuneServices.getService(f);
fts.tune(f);
TuneService<Guitar> gts = TuneServices.getService(g);
gts.tune(g);
// use gibson service with fender
gts.tune(f);
如果服务与超类型ClassCastException
一起使用,则可以在运行时抛出Guitar
。 FenderService与Gibson一起使用,反之亦然。
为了避免这种情况,服务调用可以封装在一个方法中,获得正确的服务(这个方法已经存在)并将其应用到吉他上。
答案 2 :(得分:0)
你可以做到
public interface GuitarTuningService<T extends Guitar> {
void tune(T guitar);
}
然后
public class FenderTuningService implements GuitarTuningService<Fender> {
public void tune(Fender guitar) {
// do stuff
}
}
在工厂里
public class GuitarTuningServiceFactory {
public GuitarTuningService getTuningService(final Guitar guitar) {
if (guitar instanceof Fender) {
return fenderTuningService;
}
// if guitar is a Gibson, return GibsonTuningService
}
private final GuitarTuningService<Fender> fenderTuningService
= new FenderTuningService();
}
答案 3 :(得分:0)
我试图通过模板化GuitarService来使用泛型但是我的工厂不能再为我服务了(我不能安全地实例化在运行时确定参数的对象)。
您可以像这样实现:
// declare a common interface
public interface GuitarTuningService<GuitarGenericType extends Guitar> {
void tune(GuitarGenericType guitar)
}
public class FenderTuningService implements GuitarTuningService<FenderGuitar> {
public void tune(FenderGuitar guitar) {
// tune it
}
}
工厂反过来可能是抽象的:
public abstract GuitarServiceFactory<T extends GuitarTuningService> {
T getGuitarService();
}
// and the concrete implementation:
public class FenderServiceFactory extends GuitarServiceFactory<FenderTuningService> {
public FenderTuningService getGuitarService() {
// instantiate & return FenderTuningService
}
}
最后我们可以使用这样的东西:
public class GuitarTuner {
private GuitarServiceFactory factory;
private Guitar guitar;
public GuitarTuner(Guitar guitar) {
this.guitar = guitar;
switch (guitar.getClass()) {
case Fender.class:
factory = new FenderServiceFactory();
break;
// other guitar types
throw new IllegalArgumentException("Unknown guitar type added");
}
}
public void tune() {
factory.tune(guitar);
}
}