使用 Djinni 替代接口继承
Alternative to interface inheritance with Djinni
我正在使用 Djinni 在 android 和 ios 之间共享一些大型 c++ 代码库。各种组件之一(我们称之为 Foo
!!!)在 android 和 ios 上有不同的实现。 Foo
是一个接口,c++ 代码无需了解其内部结构即可使用它。
android 实现虽然 (FooAndroid
),但有一些额外的方法,android 客户端可以使用这些方法以仅对 [=] 有意义的方式修改组件的行为38=] 平台。
由于缺少接口继承,这使 djinni 的事情变得复杂。我想出了一个解决方案,它依赖于 FooAndroid
可以子类化两个不同的 djinni 接口,这些接口的大部分方法都具有相同的签名,但结果并不漂亮。
这是djinni界面描述:
foo = interface +c {
static create() : foo;
doSomething();
}
foo_android = interface +c {
static create() : foo_android;
doSomething();
doAnotherThing();
asFoo(): foo;
}
bar = interface +c {
static create() : bar;
useFoo(f: foo);
}
您会注意到 Bar
一个只需要访问 Foo
实例的组件。
然后以这种方式实现生成的接口:
// Foo android implementation:
class FooAndroidImpl : public Foo, public FooAndroid {
public:
void doSomething() override { LOGI("do something"); }
void doAnotherThing() override { LOGI(" do something"); }
std::weak_ptr<Api::Foo> fooSelfReference;
std::shared_ptr<Api::Foo> asFoo() override { return fooSelfRef.lock(); }
};
// instance creation methods:
std::shared_ptr<FooAndroidImpl> createImpl() {
auto p = std::make_shared<FooAndroidImpl>();
p->fooSelfRef = p;
return p;
}
std::shared_ptr<FooAndroid> FooAndroid::create()
{
return createImpl();
}
std::shared_ptr<Foo> Foo::create()
{
return createImpl();
}
// Bar implementation:
class BarImpl : public Bar {
public:
void useFoo(const std::shared_ptr<Foo> & f) override { f->doSomething(); }
};
std::shared_ptr<Bar> Bar::create()
{
return std::make_shared<BarImpl>();
}
然后我可以通过这种方式在 java 中使用 FooAndroid 和 Bar:
FooAndroid fooAndroid = FooAndroid.create();
fooAndroid.doAnotherThing();
Bar bar = Bar.create();
bar.useFoo(fooAndroid.asFoo());
这行得通,但它很丑,我必须手动定义两个几乎相同的 djinni 接口,而且我仍然不确定存储 weak_ptr 对对象生命周期的所有影响。
我觉得我在滥用 Djinni 的目的和模型,所以也许有更好的方法来实现我想要做的事情?
Djinni 不支持接口继承,所以这里没有很好的答案。在 Dropbox,这并不经常出现,我们通常通过向单个接口添加 Android/iOS 特定方法来解决它,并使用在其他平台上抛出异常的存根来实现它们。不过,我们通常 运行 进入相反的情况,其中实施在 Java/ObjC.
如果您不喜欢这种方法,并且不喜欢方法重复和多重继承,我认为我推荐的另一种方法是使用相关的子对象。例如。有一个带有 getAndroidStuff() 方法的 Foo 接口,returns 一个 FooAndroid 接口只包含 Android-specific 方法。 FooAndroid 对象可以在内部保留对具有所有功能的 FooImpl 对象的引用,并将方法转发给它。
这里似乎更广泛的问题是这些方法真正特定于平台的是什么?如果实现是跨平台的,但只在一个平台上需要,那么只让一个 C++ class 站进去也没什么坏处,未使用的方法也没有危险。如果您有特定于平台的 C++ 代码,那么事情就会变得有点尴尬,因为这不是 Djinni 的设计目标。不过,我认为这个特定案例需要一个不同于接口继承的一般案例的解决方案。
我正在使用 Djinni 在 android 和 ios 之间共享一些大型 c++ 代码库。各种组件之一(我们称之为 Foo
!!!)在 android 和 ios 上有不同的实现。 Foo
是一个接口,c++ 代码无需了解其内部结构即可使用它。
android 实现虽然 (FooAndroid
),但有一些额外的方法,android 客户端可以使用这些方法以仅对 [=] 有意义的方式修改组件的行为38=] 平台。
由于缺少接口继承,这使 djinni 的事情变得复杂。我想出了一个解决方案,它依赖于 FooAndroid
可以子类化两个不同的 djinni 接口,这些接口的大部分方法都具有相同的签名,但结果并不漂亮。
这是djinni界面描述:
foo = interface +c {
static create() : foo;
doSomething();
}
foo_android = interface +c {
static create() : foo_android;
doSomething();
doAnotherThing();
asFoo(): foo;
}
bar = interface +c {
static create() : bar;
useFoo(f: foo);
}
您会注意到 Bar
一个只需要访问 Foo
实例的组件。
然后以这种方式实现生成的接口:
// Foo android implementation:
class FooAndroidImpl : public Foo, public FooAndroid {
public:
void doSomething() override { LOGI("do something"); }
void doAnotherThing() override { LOGI(" do something"); }
std::weak_ptr<Api::Foo> fooSelfReference;
std::shared_ptr<Api::Foo> asFoo() override { return fooSelfRef.lock(); }
};
// instance creation methods:
std::shared_ptr<FooAndroidImpl> createImpl() {
auto p = std::make_shared<FooAndroidImpl>();
p->fooSelfRef = p;
return p;
}
std::shared_ptr<FooAndroid> FooAndroid::create()
{
return createImpl();
}
std::shared_ptr<Foo> Foo::create()
{
return createImpl();
}
// Bar implementation:
class BarImpl : public Bar {
public:
void useFoo(const std::shared_ptr<Foo> & f) override { f->doSomething(); }
};
std::shared_ptr<Bar> Bar::create()
{
return std::make_shared<BarImpl>();
}
然后我可以通过这种方式在 java 中使用 FooAndroid 和 Bar:
FooAndroid fooAndroid = FooAndroid.create();
fooAndroid.doAnotherThing();
Bar bar = Bar.create();
bar.useFoo(fooAndroid.asFoo());
这行得通,但它很丑,我必须手动定义两个几乎相同的 djinni 接口,而且我仍然不确定存储 weak_ptr 对对象生命周期的所有影响。
我觉得我在滥用 Djinni 的目的和模型,所以也许有更好的方法来实现我想要做的事情?
Djinni 不支持接口继承,所以这里没有很好的答案。在 Dropbox,这并不经常出现,我们通常通过向单个接口添加 Android/iOS 特定方法来解决它,并使用在其他平台上抛出异常的存根来实现它们。不过,我们通常 运行 进入相反的情况,其中实施在 Java/ObjC.
如果您不喜欢这种方法,并且不喜欢方法重复和多重继承,我认为我推荐的另一种方法是使用相关的子对象。例如。有一个带有 getAndroidStuff() 方法的 Foo 接口,returns 一个 FooAndroid 接口只包含 Android-specific 方法。 FooAndroid 对象可以在内部保留对具有所有功能的 FooImpl 对象的引用,并将方法转发给它。
这里似乎更广泛的问题是这些方法真正特定于平台的是什么?如果实现是跨平台的,但只在一个平台上需要,那么只让一个 C++ class 站进去也没什么坏处,未使用的方法也没有危险。如果您有特定于平台的 C++ 代码,那么事情就会变得有点尴尬,因为这不是 Djinni 的设计目标。不过,我认为这个特定案例需要一个不同于接口继承的一般案例的解决方案。