gpt4 book ai didi

java - 依赖注入(inject) : templates (/generics) or virtual functions?

转载 作者:搜寻专家 更新时间:2023-10-31 00:46:40 26 4
gpt4 key购买 nike

这是一个关于公认的编码实践的好奇心问题。我(主要)是一名 Java 开发人员,并且越来越努力地对我的代码进行单元测试。我花了一些时间研究如何编写最易测试的代码,特别关注 Google 的 How to write untestable code。指南(非常值得一看,如果您还没有看过的话)。

自然地,我最近和一个更偏向于 C++ 的 friend 争论每种语言的继承模型的优点,我想我会拿出一张王牌,说 C++ 程序员通过以下方式测试他们的代码有多难经常忘记 virtual 关键字(对于 C++ 用户 - 这是 Java 中的默认设置;您可以使用 final 摆脱它)。

我发布了一个代码示例,我认为它可以很好地展示 Java 模型的优势(full thing is over on GitHub)。简短版本:

class MyClassForTesting {
private final Database mDatabase;
private final Api mApi;

void myFunctionForTesting() {
for (User u : mDatabase.getUsers()) {
mRemoteApi.updateUserData(u);
}
}

MyClassForTesting ( Database usersDatabase, Api remoteApi) {
mDatabase = userDatabase;
mRemoteApi = remoteApi;
}
}

不管我在这里写的内容的质量如何,我的想法是该类需要对数据库和一些 API(可能在远程 Web 服务器上)进行一些(可能非常昂贵的)调用。 myFunctionForTesting() 没有返回类型,那么如何对其进行单元测试呢?在 Java 中,我认为答案并不太难——我们模拟:

/*** Tests ***/

/*
* This will record some stuff and we'll check it later to see that
* the things we expect really happened.
*/
ActionRecorder ar = new ActionRecorder();


/** Mock up some classes **/

Database mockedDatabase = new Database(ar) {

@Override
public Set<User> getUsers() {
ar.recordAction("got list of users");
/* Excuse my abuse of notation */
return new Set<User>( {new User("Jim"), new User("Kyle")} );
}

Database(ActionRecorder ar) {
this.ar = ar;
}
}

Api mockApi = new Api() {

@Override
public void updateUserData(User u) {
ar.recordAction("Updated user data for " + u.name());
}

Api(ActionRecorder ar) {
this.ar = ar;
}
}

/** Carry out the tests with the mocked up classes **/
MyClassForTesting testObj = new MyClassForTesting(mockDatabase, mockApi);
testObj.myFunctionForTesting();

// Check that it really fetches users from the database
assert ar.contains("got list of users");

// Check that it is checking the users we passed it
assert ar.contains("Updated user data for Jim");
assert ar.contains("Updated user data for Kyle");

通过模拟这些类,我们将依赖项注入(inject)我们自己的轻量级版本,我们可以对这些版本进行断言以进行单元测试,并避免对数据库/api 进行昂贵、耗时的调用。 DatabaseApi的设计者不用太在意,这就是我们要做的,MyClassForTesting的设计者> 当然不必知道!这对我来说似乎是一种很好的做事方式。

然而,我的 C++ friend 反驳说,这是一个可怕的 hack,C++ 不允许您这样做是有充分理由的!然后他提出了一个基于 Generics 的解决方案,它做的事情大致相同。为了简洁起见,我将只列出他给出的解决方案的一部分,但您同样可以找到 the whole thing over on Github .

template<typename A, typename D>
class MyClassForTesting {
private:
A mApi;
D mDatabase;

public MyClassForTesting(D database, A api) {
mApi = api;
mDatabase = database;
}

...
};

然后将像以前一样进行测试,但重要的位被替换如下所示:

class MockDatabase : Database {
...
}

class MockApi : Api {
...
}

MyClassForTesting<MockApi, MockDatabase>
testingObj(MockApi(ar), MockDatabase(ar));

所以我的问题是:首选方法是什么?我一直认为基于多态性的方法更好——而且我认为没有理由它不会在 Java 中出现——但通常认为使用泛型比在 C++ 中虚拟化所有内容更好吗? 你的代码中做了什么(假设你单元测试)?

最佳答案

我可能有偏见,但我会说 C++ 版本更好。除其他事项外,多态性会带来一些成本。在这种情况下,您是在让您的用户支付这笔费用,即使他们没有从中直接受益。

例如,如果您有一个多态对象列表,并且想通过基类操作所有对象,那么使用多态性是合理的。然而,在这种情况下,多态性被用于用户从未见过的东西。您已经内置了操纵多态对象的能力,但从未真正使用过它——为了测试,您将拥有模拟对象,而对于实际使用,您将只有真实的对象。永远不会有(例如)数据库对象数组,其中一些是模拟数据库,另一些是真实数据库。

这不仅仅是一个效率问题(或者至少是一个运行时效率问题)。代码中的关系应该是有意义的。当有人看到(公共(public))继承时,应该告诉他们一些关于设计的事情。然而,正如您在 Java 中概述的那样,所涉及的公共(public)继承关系基本上是一个谎言——即他应该从中知道的(您正在处理多态后代)是一个彻头彻尾的谎言.相比之下,C++ 代码正确地向读者传达了意图。

当然,在某种程度上,我夸大了那里的情况。通常阅读 Java 的人几乎肯定已经习惯了继承通常被滥用的方式,所以他们根本不认为这是一个谎言。不过,这有点像把婴儿连同洗澡水一起倒掉——他们没有看到“谎言”的真相,而是学会了完全忽视遗传的真正含义(或者根本不知道,尤其是如果他们上了大学其中 Java 是教授 OOP 的主要工具)。正如我所说,我可能有点偏见,但对我来说,这使(大多数)Java 代码更难理解。您基本上必须小心忽略 OOP 的基本原则,并习惯于不断滥用它。

关于java - 依赖注入(inject) : templates (/generics) or virtual functions?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/4877405/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com