【问题标题】:How do I abstract away dependencies in Android library code?如何抽象出 Android 库代码中的依赖项?
【发布时间】:2016-07-17 15:18:44
【问题描述】:

这是我的场景。

我有一个 android 活动,我想在其中抽象我的 I/O 依赖项。依赖关系由这个接口表示(为简洁起见进行了编辑):

public interface ITimeDataServer {
    TimeRecord[] get(int userID);
    void save(TimeRecord record);
}

我希望我的活动能够调用这些接口方法,并让调用代码提供实现。 (我认为相当标准)。

ITimeDataServer myServer;
int myUserID;

void loadRecords() {
    TimeRecord[] records = myServer.get(myUserID);
    // etc...
}

我的困难是,如何确保myServer 被设置?

这似乎是一个常见问题,但我找不到干净的解决方案。

我的第一个想法是 myServer 将通过构造函数传入,但 Android 活动并没有真正用构造函数实例化。

我想出了几个解决方案,但它们在某些方面都很糟糕:

令人讨厌的解决方案 1

创建一个静态方法来启动活动类,该类接受一个 ITimeDataServer 参数并将其存储在一个静态变量中,活动可以从中访问它:

private static ITimeDataSource theDataSource;
public static void launch(Activity currentActivity, ITimeDataSource dataSource) {
    theDataSource = dataSource;
    Intent intent = new Intent(currentActivity, MainActivity.class);
    currentActivity.startActivity(intent);
}

这很棘手,因为 (a) 数据源是静态的,并且实际上与实例没有关联,并且 (b) 消费者可以通过标准活动 API 而不是这种静态方法来启动活动,这将导致 NullPointerException。

令人讨厌的解决方案 2

我可以创建一个Provider类,提供一个ITimeDataSource的单例实例,使用前需要调用库初始化:

public class TimeDataSourceProvider {

    private static ITimeDataSource myDataSource = null;

    public void initialize(ITimeDataSource dataSource) {
        myDataSource = dataSource;
    }

    public ITimeDataSource get() {
        if (myDataSource == null)
            throw new NullPointerException("TimeDataSourceProvider.initialize() must be called before .get() can be used.");
        else
            return myDataSource;
    }
}

这似乎不那么令人讨厌了,但仍然有点讨厌,因为活动的依赖关系并不明显,并且由于可能有很多启动它的路径,因此很可能其中一些人会忘记调用TimeDataSourceProvider.initialize()

棘手的解决方案 3

作为 #2 的变体,创建一个静态 IODependencyProvider 类,该类必须使用应用启动时的所有依赖项进行初始化。

public class IODependencyProvider {

    static ITimeDataSource myTimeData;
    static IScheduleDataSource myScheduleData; // etc

    public static void initialize(ITimeDataSource timeData, IScheduleDataSource scheduleData /* etc */) {
        myTimeData = timeData;
        myScheduleData = scheduleData;
        //etc
    }

    public static ITimeDataSource getTimeData() {
        if (myTimeData == null)
            throw new NullPointerException("IODependencyProvider.initialize() must be called before the getX() methods can be used.");
        else
            return myTimeData;
    }

    // getScheduleData(), etc
}

这似乎优于 #1 和 #2,因为初始化失败会更难偷偷摸摸,但它也会在数据类型之间产生相互依赖关系,否则这些数据类型不需要存在。

...以及该主题的其他变体。

使这些解决方案蹩脚的共同主题:

  • 需要使用静态字段将不可序列化的信息传递给活动
  • 缺乏强制初始化这些静态字段的能力(以及随后的随意性)
  • 无法清楚地识别活动的依赖关系(由于依赖静态数据)

什么是笨拙的 Android 开发者?

【问题讨论】:

  • 如果静态 initialize() 方法有一个注释说“必须在应用启动期间调用此方法”,那至少可以缓解第二点。

标签: android android-activity dependency-injection abstraction


【解决方案1】:

只要这些依赖项正确实现 Parcelable,您就应该能够将它们添加到您的意图中,然后将它们解包为 ITimeDataServer 并获得正确的类。

【讨论】:

  • 即使接口确实实现了 Parcelable(不是微不足道的),也没有办法要求在启动活动时将其包含在内,对吗? (这是我不喜欢 Android 活动的原因之一。)
  • 是的,如果我们不受框架约束,我们想使用的许多模式在我们无法控制框架的 Android 上不起作用。通过使用 parcelable,您可以访问在 Activity 之间传递对象的普通 Android 方法,这听起来像是您正在尝试做的事情,并且最终可能比滚动您自己的解决方案更好、更容易。
  • 你不能要求任何东西,但你应该能够通过为你的活动实现一个公共基类、在那里存储你的 I/O 对象以及实现发送和接收它的代码来鼓励它在开始活动时,它对您的具体活动类几乎是透明的。
【解决方案2】:

我在最不受欢迎的答案中找到了一个很好的解决方案 here

我将库活动定义为抽象的,没有默认构造函数,而是一个接受接口的构造函数,如下所示:

public abstract class TimeActivity extends AppCompatActivity {

    private ITimeDataSource myTimeDataSource;
    public TimeActivity(@NonNull ITimeDataSource dataSource) {
        myTimeDataSource = dataSource;
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_time);

        // do stuff with myTimeDataSource!
    }
}

然后,调用代码可以创建一个具体的子类,其选择的实现确实有一个无参数的构造函数。没有静态成员,简单易行!

这可以让你抽象和注入各种疯狂的行为!哇哦!

(注意具体的子类activity需要像所有activity一样手动添加到AndroidManifest.xml中,否则应用在尝试启动时会崩溃。)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-12-12
    • 1970-01-01
    • 1970-01-01
    • 2012-04-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多