Job*_*Job 19 c++ oop interface
假设我的项目使用了一个库,LibFoo
它通过许多类来提供它的功能,比如说FooA
和FooB
.现在,有许多类似的库(例如,LibBar
提供BarA
和提供BarB
)提供相同的功能LibFoo
,我希望我的项目用户能够选择使用哪个库,最好是在运行时.
为了实现这一点,我创建了一个"包装层",它定义了我期望从库中获得的接口.在我的示例中,该层包含两个接口:IfaceA
和IfaceB
.然后,对于我想要支持的每个库,我创建了一个"实现层",使用其中一个库实现接口.
我现在的问题在于如何很好地实现实现层.为了演示我的问题,请考虑我们有以下接口(以C++显示,但应适用于类似语言):
class IfaceA
{
public:
virtual void doSomethingWith(IfaceB& b) = 0;
...
};
class IfaceB
{
...
};
Run Code Online (Sandbox Code Playgroud)
实现层中的类LibFoo
将保存来自相应类的对象LibFoo
.应使用这些对象实现接口中的操作.因此(请原谅我可怕的名字):
class ConcreteAForFoo : public IfaceA
{
public:
void doSomethingWith(IfaceB& b) override
{
// This should be implemented using FooA::doSomethingWith(FooB&)
}
private:
FooA a;
};
class ConcreteBForFoo : public IfaceB
{
public:
FooB& getFooB() const
{
return b;
}
private:
FooB b;
};
Run Code Online (Sandbox Code Playgroud)
问题在于实现ConcreteAForFoo::doSomethingWith
:它的参数有类型IfaceB&
但我需要访问实现细节ConcreteBForFoo
才能正确实现该方法.我发现这样做的唯一方法就是在整个地方使用丑陋的垂头丧气:
void doSomethingWith(IfaceB& b) override
{
assert(dynamic_cast<ConcreteBForFoo*>(&b) != nullptr);
a.doSomethingWith(static_cast<ConcreteBForFoo&>(b).getFooB());
}
Run Code Online (Sandbox Code Playgroud)
由于不得不贬低通常被认为是代码气味,我不禁认为应该有更好的方法来做到这一点.或者我是从错误的设计开始的?
TL; DR
给定一层相互依赖的接口(在一个接口中的方法接收对其他接口的引用).这些接口的实现如何共享实现细节,而无需在接口中向下转换或公开这些细节?
正确答案是:
你现在的基本问题是接口/继承是一个"过度简化谎言",我真正的意思是你的接口实际上并没有跟踪LSP.
如果你想修复这个跟随LSP并使库混合,你需要做1:修复你的代码不使用偷偷摸摸的实现细节,并且真正遵循LSP.但是这个选项基本上被问题陈述所排除,其中明确表示类的实现是不兼容的,我们应该假设情况总是如此.
假设库永远不会混合,正确答案是2. 继续使用动态强制转换.让我们考虑一下原因:
OP的接口定义字面上说ConcreteAForFoo
可以成功地doSomethingWith
任何 IFaceB
对象.但OP知道这不是真的 - 它确实必须是ConcreteBForFoo,因为需要使用在IFaceB的接口中找不到的某些实现细节.
在这个特定的描述场景中,向下转换是最好的事情.
请记住:向下转换是指您知道对象的类型,但编译器不知道.编译器只知道方法签名.您知道运行时类型比编译器更好,因为您知道一次只加载一个库.
您希望隐藏编译器中的真实类型,因为您希望将库抽象为接口并隐藏库中用户的基础类型,我认为这是一个很好的设计决策.向下转换是你的抽象的实现部分,你告诉编译器"信任我,我知道这将起作用,这只是一个抽象".
(并且使用dynamic_cast而不是static_cast说'并且哦,是的,因为我有点偏执,请让运行时如果我碰巧错误的话请抛出错误',例如,如果有人因为试图混合而误用库不兼容的ConcreteImplementations.)
选项3被抛入其中,尽管我认为它不是OP真正想要的,因为它意味着打破"接口兼容性"约束,它是另一种阻止违反LSP的方式,并满足强类型的粉丝.
看起来像一个经典的双重调度用例.
class IfaceA
{
public:
virtual void doSomethingWith(IfaceB& b) = 0;
...
};
class IfaceB
{
virtual void doSomethingWith(IfaceA & a) = 0;
...
};
struct ConcreteAForFoo : public IfaceA {
virtual void doSomethingWith (IfaceB &b) override {
b.doSomethingWith(*this);
}
};
struct ConcreteBForFoo : public IfaceB
{
virtual void doSomethingWith(IfaceA & a) override {
//b field is accessible here
}
private:
FooB b;
};
Run Code Online (Sandbox Code Playgroud)
IfaceB :: doSomethingWith签名可以变化,以获得耦合和访问级别之间的最佳平衡(例如,ConcreteAForFoo可以用作参数,以紧密耦合为代价访问其内部).
首先,我要感谢你,因为你的问题很好.
我的第一感觉是,如果你想传递一个IfaceB&
但需要使用具体类型,你就会遇到架构问题.
但是,我理解你想要做的事情的复杂性,所以我会尝试给你一个比垂头丧气更好的解决方案.
你的架构的开始是一个很好的选择,因为这是一个适配器模式(链接在C#中,但即使我暂时不使用这个语言,它仍然是我在这个主题上找到的最好的!).而这正是你想要做的.
在下述溶液中,添加了另一个对象c
负责之间的相互作用a
和b
:
class ConcreteAForFoo : public IfaceA
{
private:
FooA a;
};
class ConcreteBForFoo : public IfaceB
{
private:
FooB b;
};
class ConcreteCForFoo : public IfaceC
{
private:
ConcreteAForFoo &a;
ConcreteBForFoo &b;
public:
ConcreteCForFoo(ConcreteAForFoo &a, ConcreteBForFoo &b) : a(a), b(b)
{
}
void doSomething() override
{
a.doSomethingWith(b);
}
};
class IfaceC
{
public:
virtual void doSomething() = 0;
};
Run Code Online (Sandbox Code Playgroud)
您应该使用工厂来获取对象:
class IFactory
{
public:
virtual IfaceA* getA() = 0;
virtual IfaceB* getB() = 0;
virtual IfaceC* getC() = 0;
};
class IFactory
{
public:
virtual IfaceA* getA() = 0;
virtual IfaceB* getB() = 0;
virtual IfaceC* getC() = 0;
};
class FooFactory : public IFactory
{
private:
IfaceA* a;
IfaceB* b;
IfaceC* c;
public:
IfaceA* getA()
{
if (!a) a = new ConcreteAForFoo();
return a;
}
IfaceB* getB()
{
if (!b) b = new ConcreteBForFoo();
return b;
}
IfaceC* getC()
{
if (!c)
{
c = new ConcreteCForFoo(getA(), getB());
}
return c;
}
};
Run Code Online (Sandbox Code Playgroud)
当然,您肯定必须调整此代码,因为您可能有很多b或a.
此时,您将能够doSomething
像这样处理方法:
factory = FooFactory();
c = factory.getC();
c->doSomething();
Run Code Online (Sandbox Code Playgroud)
可能有更好的解决方案,但我需要一个真实的代码来告诉你.我希望这能帮到您.
最后,我想为我的C++(以及我的英语)道歉,很长一段时间我没有用C++编写代码(而且我知道我至少犯过一些错误(这意味着null == this.a没有指针??)).
编辑:
另一种可能性,以避免提供可能的不确定性,而你想一个具体类型是使用一种调解员(负责实例之间的相互作用来传递的接口A
和B
)有一个名为注册表相关:
class FooFactory : public IFactory
{
private:
IMediator* mediator;
public:
IfaceA* getNewA(name)
{
a = new ConcreteAForFoo();
mediator = getMediator();
mediator->registerA(name, *a);
return a;
}
IfaceB* getNewB(name)
{
b = new ConcreteBForFoo();
mediator = getMediator();
mediator->registerB(name, *b);
return b;
}
IMediator* getMediator()
{
if (!mediator) mediator = new ConcreteMediatorForFoo();
return mediator;
}
};
class ConcreteMediatorForFoo : public IMediator
{
private:
std::map<std::string, ConcreteAForFoo> aList;
std::map<std::string, ConcreteBForFoo> bList;
public:
void registerA(const std::string& name, IfaceA& a)
{
aList.insert(std::make_pair(name, a));
}
void registerB(const std::string& name, IfaceB& b)
{
bList.insert(std::make_pair(name, b));
}
void doSomething(const std::string& aName, const std::string& bName)
{
a = aList.at(aName);
b = bList.at(bName);
// ...
}
}
Run Code Online (Sandbox Code Playgroud)
然后,您可以处理的情况下的相互作用A
和B
这样的:
factory = FooFactory();
mediator = factory.getMediator();
a = factory.getNewA('plop');
bPlap = factory.getNewB('plap');
bPlip = factory.getNewB('plip');
// initialize a, bPlap and bPlip.
mediator->doSomething('plop', 'plip');
Run Code Online (Sandbox Code Playgroud)
这不是一件容易的事.关于C++的类型系统还不够.原则上没有任何东西可以(静态地)阻止用户IFaceA
从一个库和IFaceB
另一个库中实例化,然后根据需要混合和匹配它们.你的选择是:
不要使库动态可选,即不要使它们实现相同的接口.相反,让他们实现一系列接口的实例.
template <typename tag>
class IfaceA;
template <typename tag>
class IfaceB;
template <typename tag>
class IfaceA
{
virtual void doSomethingWith(IfaceB<tag>& b) = 0;
};
Run Code Online (Sandbox Code Playgroud)
每个库都使用不同的标记实现接口.用户可以在编译时轻松选择标记,但不能在运行时选择.
这是双重调度的基本示例:
//=================
class IFaceBDispatcher;
class IFaceB
{
IFaceBDispatcher* get() = 0;
};
class IfaceA
{
public:
virtual void doSomethingWith(IfaceB& b) = 0;
...
};
// IFaceBDispatcher is incomplete up until this point
//=================================================
// IFaceBDispatcher is defined in these modules
class IFaceBDispatcher
{
virtual void DispatchWithConcreteAForFoo(ConcreteAForFoo*) { throw("unimplemented"); }
virtual void DispatchWithConcreteAForBar(ConcreteAForBar*) { throw("unimplemented"); }
};
class ConcreteAForFoo : public IfaceA
{
virtual void doSomethingWith(IfaceB& b) { b.DispatchWithConcreteAForFoo(this); }
}
class IFaceBDispatcherForFoo : public IFaceBDispatcher
{
ConcreteBForFoo* b;
void DispatchWithConcreteAForFoo(ConcreteAForFoo* a) { a->doSomethingWith(*b); }
};
class IFaceBDispatcherForBar : public IFaceBDispatcher
{
ConcreteBForBar* b;
void DispatchWithConcreteAForBar(ConcreteAForBar* a) { a->doSomethingWith(*b); }
};
class ConcreteBForFoo : public IFaceB
{
IFaceBDispatcher* get() { return new IFaceBDispatcherForFoo{this}; }
};
class ConcreteBForBar : public IFaceB
{
IFaceBDispatcher* get() { return new IFaceBDispatcherForBar{this}; }
};
Run Code Online (Sandbox Code Playgroud)