Java - 委托方法的正确方法

时间:2016-09-06 14:16:57

标签: java design-patterns software-design

我的程序从外部源获取信息(可以是文件,数据库或我将来决定的任何其他内容)。

我想定义一个包含我所有数据需求的接口,以及实现它的类(例如,一个用于从文件获取数据的类,另一个用于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代码,或者有一种方法可以为所有项目执行一次?

3 个答案:

答案 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; }

含义:

  • 有一个很好的(单身修正)访问一个给你功能的对象
  • impl类完全可以单元测试
  • 客户端代码使用依赖注入,因此也是完全可单元测试的

答案 2 :(得分:1)

  

我的程序从外部源获取信息(可以是文件,数据库或我将来决定的任何其他内容)。

这是数据访问对象(简短DAO)存储库模式等模式背后的思想。差异是模糊的。两者都是关于抽象统一接口背后的数据源。一种常见的方法是为每个业务或数据库实体提供一个DAO / Repository类。如果您希望它们的行为类似(例如CRUD方法)或特定于特殊查询和内容,则由您自己决定。在Java EE中,模式通常使用 Java Persistence API (简短JPA)实现。

  

为此我需要DataSource来包含一个类型的变量   接口并使用其中一个具体实现来初始化它,

对于此初始化,您不想知道或定义使用类中的类型。这就是控制反转(短期IOC)发挥作用的地方。实现此目的的一种简单方法是将所有依赖项放入构造函数参数中,但这样您只需将问题向上移动一步。在Java环境中,您经常会听到术语上下文和依赖注入(简称CDI),它基本上是IOC理念的实现。特别是在Java EE中,有CDI包,它允许您根据实现的接口注入类的实例。在有效使用CDI时,您基本上不再调用任何构造函数。你只定义你的班级'使用注释的依赖关系。

  

并公开其所有方法(来自界面)

这是一种误解。您确实希望它仅公开接口定义的方法。该类中的所有其他公共方法都是无关紧要的,仅用于测试,或者在您希望使用特定行为的极少数情况下。

  

作为静态方法。

只有静态方法的有状态类才是反模式。由于您的数据源类必须包含对基础数据源的引用,因此它们具有状态。也就是说,这个班需要一个私人领域。这使得通过静态方法无法使用。此外,静态类很难测试,并且在多线程环境中表现不佳。