我的程序从外部源获取信息(可以是文件,数据库或我将来决定的任何其他内容)。
我想定义一个包含我所有数据需求的接口,以及实现它的类(例如,一个用于从文件获取数据的类,另一个用于DB的数据等)。
我希望项目的其余部分不关心数据来自何处,而不需要创建任何对象来获取数据,例如调用“DataSource.getSomething();”
为此,我需要DataSource包含接口类型的变量,并使用其中一个具体实现对其进行初始化,并将其所有方法(来自接口)公开为静态方法。
因此,假设接口名称为K,具体实现为A,B,C。
我今天的做法是:
public class DataSource {
private static K myVar = new B();
// For **every** method in K I do something like this:
public static String getSomething() {
return myVar.doSomething();
}
...
}
这非常糟糕,因为我需要复制界面的所有方法并将它们设置为静态,这样我就可以将它委托给myVar,以及许多其他明显的原因。
这样做的正确方法是什么? (也许它有一个设计模式?)
**注意 - 因为这将是许多其他项目的支柱,我将使用来自数千(如果不是数万)代码行的这些调用,我坚持像“DataSource.getSomething(); “,我不想要任何像”DataSource.getInstance()。getSomething();“ **
编辑: 我在这里被提议使用像Guice这样的DI框架,这是否意味着我需要在我的所有项目中的每个入口点(即“主”方法)中添加DI代码,或者有一种方法可以为所有项目执行一次?
答案 0 :(得分:7)
使用您的数据源的类应该通过接口访问它,并在构造时提供给类的正确实例。
首先让DataSource
成为一个界面:
public interface DataSource {
String getSomething();
}
现在具体实施:
public class B implements DataSource {
public String getSomething() {
//read a file, call a database whatever..
}
}
然后你的调用类看起来像这样:
public class MyThingThatNeedsData {
private DataSource ds;
public MyThingThatNeedsData(DataSource ds) {
this.ds = ds;
}
public doSomethingRequiringData() {
String something = ds.getSomething();
//do whatever with the data
}
}
您的代码中的其他位置可以实例化此类:
public class Program {
public static void main(String[] args) {
DataSource ds = new B(); //Here we've picked the concrete implementation
MyThingThatNeedsData thing = new MyThingThatNeedsData(ds); //And we pass it in
String result = thing.doSomethingThatRequiresData();
}
}
如果你想获得花哨的话,你可以使用像Spring或Guice这样的依赖注入框架来完成最后一步。
奖励积分:在您的单元测试中,您可以提供DataSource的模拟/存根实现,而您的客户端类将更加明智!
答案 1 :(得分:2)
我想在你的问题中重点关注我的答案中的一个重要方面;你写道:
注意 - 我坚持像“DataSource.getSomething();”一样保持简单,我不想要任何类似“DataSource.getInstance()。getSomething();”
事情是:简单 不测量字符数。简单来自良好设计;良好的设计来自以下最佳实践。
换句话说:如果你认为 DataSource.getSomething()比使用(例如)依赖注入“魔法”的东西“更容易”为你提供一个实现某个特定的对象接口;那么:你错了!
反过来说:那些是分开的问题:一方面;另一方面;你应该声明一个描述所需功能的界面。另一方面,您的客户端代码需要该接口的对象。这是你应该关注的所有。 “创造”该对象的步骤;并使其可用于您的代码可能看起来比调用静态方法更复杂;但我向您保证:按照Paolo的回答,您的产品将更好。
有时容易做错事!
编辑:我正在使用的一种模式:
interface SomeFunc {
void foo();
}
class SomeFuncImpl implements SomeFunc {
...
}
enum SomeFuncProvider implements SomeFunc {
INSTANCE;
private final SomeFunc delegatee = new SomeFuncImpl();
@Override
void foo() { delegatee.foo(); }
此模式允许您编写像
这样的客户端代码class Client {
private final SomeFunc func;
Client() { this(SomeFuncProvider.INSTANCE); }
Client(SomeFunc func) { this.func = func; }
含义:
答案 2 :(得分:1)
我的程序从外部源获取信息(可以是文件,数据库或我将来决定的任何其他内容)。
这是数据访问对象(简短DAO)或存储库模式等模式背后的思想。差异是模糊的。两者都是关于抽象统一接口背后的数据源。一种常见的方法是为每个业务或数据库实体提供一个DAO / Repository类。如果您希望它们的行为类似(例如CRUD方法)或特定于特殊查询和内容,则由您自己决定。在Java EE中,模式通常使用 Java Persistence API (简短JPA)实现。
为此我需要DataSource来包含一个类型的变量 接口并使用其中一个具体实现来初始化它,
对于此初始化,您不想知道或定义使用类中的类型。这就是控制反转(短期IOC)发挥作用的地方。实现此目的的一种简单方法是将所有依赖项放入构造函数参数中,但这样您只需将问题向上移动一步。在Java环境中,您经常会听到术语上下文和依赖注入(简称CDI),它基本上是IOC理念的实现。特别是在Java EE中,有CDI包,它允许您根据实现的接口注入类的实例。在有效使用CDI时,您基本上不再调用任何构造函数。你只定义你的班级'使用注释的依赖关系。
并公开其所有方法(来自界面)
这是一种误解。您确实希望它仅公开接口定义的方法。该类中的所有其他公共方法都是无关紧要的,仅用于测试,或者在您希望使用特定行为的极少数情况下。
作为静态方法。
只有静态方法的有状态类才是反模式。由于您的数据源类必须包含对基础数据源的引用,因此它们具有状态。也就是说,这个班需要一个私人领域。这使得通过静态方法无法使用。此外,静态类很难测试,并且在多线程环境中表现不佳。