工厂和仿制药
我有以下课程:
public interface IDataSource { public List getData(int numberOfEntries); } public class MyDataSource implements IDataSource { public List getData(int numberOfEntries) { ... } } public class MyOtherDataSource implements IDataSource { public List getData(int numberOfEntries) { ... } }
我想使用一个工厂,根据数据类型返回正确的实现。 我写了以下但我得到了“未经检查的演员”警告:
public static IDataSource getDataSource(Class dataType) { if (dataType.equals(MyData.class)) { return (IDataSource) new MyDataSource(); } else if (dataType.equals(MyOtherData.class)) { return (IDataSource) new MyOtherDataSource(); } return null; }
我做错了吗? 我该怎么做才能摆脱警告?
如果没有@SuppressWarnings("unchecked")
我不知道有什么方法可以摆脱这些警告。
您传入的是Class
对象,因此可以捕获T
但是您必须在运行时检查Class
以确定要返回的IDataSource
。 此时,类型擦除早已发生。
在编译时,Java无法确定类型安全性。 它不能保证运行时Class
中的T
与返回的IDataSource
中的IDataSource
相同,因此它会产生警告。
当您被迫使用@SuppressWarnings("unchecked")
注释该方法以删除警告时,这看起来就像其中一次。 该警告是有原因的,因此您需要提供并确保类型安全。 如上所述,看起来您提供了类型安全性。
@SuppressWarnings("unchecked") public static IDataSource getDataSource(Class dataType) {
你做得对,你应该简单地压制警告。 工厂是generics中棘手的领域之一,你确实需要手动转换为generics类型,并且必须通过任何方式确保返回的值与传入的Class
相匹配。例如,在这种情况下你正在硬编码几个IDataSource
实现,所以我建议编写unit testing来validation类型是否正确,这样如果MyData
实现以不兼容的方式发生变化,你将在构建时遇到错误。
只需使用@SuppressWarnings("unchecked")
注释getDataSource
方法,在抑制警告时添加解释性注释总是一个好主意。
generics用于编译时类型安全。 它们不能像那样用于运行时类型确定。 要消除警告,您可以执行类似@SuppressWarnings("unchecked")
或使用-Xlint:-unchecked
编译器标志,如Java教程的“原始类型”部分所述 。
当你提出问题时,其他答案已经回答了问题。 但是我想退后一步,了解你用这种工厂方法想要实现的目标。 该工厂基本上提供了IDataSource
参数的数据类型映射。 dependency injection可能是一种更合适的模式,因为这是一组众所周知的小数据类型和实现(如您的示例所示)。
假设您要将所有Widgets
存储在Mongo中但是所有Gadgets
都存储在Mysql中,您可能有两个类:实现IDataSource
的MysqlGadgetDataSource
和实现IDataSource
的MysqlGadgetDataSource
。
我没有在数据使用者中硬编码像MyFactory.getDataSource(Widget.class)
这样的工厂方法调用,而是注入适当的IDataSource
依赖项。 我们可能有MyService
使用小部件(存储在mongo中)。 使用你提出的工厂看起来像这样:
public class MyService { public void doSomething() { String value = MyFactory.getDataSource(Widget.class).getSomething(); // do something with data returned from the source } }
相反,您应该将适当的数据源作为构造函数arg注入到服务中:
public class MyService { private final IDataSource widgetDataSource; public MyService(IDataSource widgetDataSource) { this.widgetDataSource = widgetDataSource; } public void doSomething() { String value = widgetDataSource.getSomething(); // now do something with data returned from the source } }
这样做的另一个好处是使您的代码更易于重用,并且更容易进行unit testing(模拟依赖项)。
然后,在实例化MyService
,您还可以连接数据源。 许多项目使用dependency injection框架(如Guice )来使这更容易,但这不是一个严格的要求。 但就个人而言,如果没有任何实际尺寸或持续时间的项目,我从不工作。
如果您不使用DI框架,则只需在创建调用服务时实例化依赖项:
public static void main(String[] args) { IDataSource widgetDataSource = new MongoWidgetDataSource(); IDataSource gadgetDataSource = new MysqlGadgetDataSource(); MyService service = new MyService(widgetDataSource, gadgetDataSource); service.doSomething(); }
在Guice中,您可以像这样连接这些数据源:
public class DataSourceModule extends AbstractModule { @Override protected void configure() { bind(new TypeLiteral>() {}).to(MongoWidgetDataSource.class); bind(new TypeLiteral>() {}).to(MysqlGadgetDataSource.class); } }
依赖倒置是思考问题的一种不同方式,但它可以导致更加分离,可重用和可测试的代码库。
这似乎有效:
public static IDataSource getDataSource(MyData dataType) { System.out.println("Make MyDataSource"); return (IDataSource ) new MyDataSource(); } public static IDataSource getDataSource(MyOtherData dataType) { System.out.println("Make MyOtherDataSource"); return (IDataSource ) new MyOtherDataSource(); } public void test() { IDataSource myDataSource = getDataSource((MyData) null); IDataSource myOtherDataSource = getDataSource((MyOtherData) null); }
您可能更喜欢创建空的原型而不是像我一样抛出null
但我认为这是一种可行的技术。