依赖倒置原则:试图理解
Dependency inversion principle: trying to understand
我正在学习设计模式和相关的东西(特别是 SOLID 和 依赖倒置原则),看起来我我丢了东西:
遵循 DIP 规则,我应该能够通过不在 class 中创建对象(composition) 但将对象 reference/pointer 发送到 class 构造函数 (aggregation)。但这意味着我必须在其他地方创建一个实例:因此具有聚合功能的 class 越灵活,另一个就越脆弱。
请解释我哪里错了。
您只需要遵循这个想法,直到得出合乎逻辑的结论。是的,您必须在其他地方创建实例,但这可能不仅仅是在 class 之上的 class 中,它一直需要被推出和推出,直到仅在以下位置创建对象应用程序的最外层。
理想情况下,您在一个地方创建所有对象,这称为 composition root(例外情况是从工厂创建的对象,但工厂是在组合根中创建的)。具体位置取决于您正在构建的应用类型。
- 在桌面应用程序中,这将在 Main 方法中(或非常接近它)
- 在 ASP.NET(包括 MVC)应用程序中,这将在 Global.asax
- 在 WCF 中,它将在 ServiceHostFactory 中
- 等等
这个地方可能最终会成为 'fragile',但您只有一个地方可以更改内容以便能够重新配置您的应用程序,然后所有其他 class 都是可测试和可配置的.
参见 this excellent answer(上面引用)
是的,您需要在某处实例化 class。如果你正确地遵循 DIP,你最终会在一个地方创建所有的实例。我称这个地方为class作文。阅读我的博客以更深入地了解这个主题 Here
您错过的一个重要可能性是注入工厂,而不是 class 本身。这样做的一个好处是它可以让您对实例的所有权更加清晰。请注意,代码有点难看,因为我明确地赋予了其组件的容器所有权。如果您使用 shared_ptr
而不是 unique_ptr
,事情可能会更整洁一些,但是所有权不明确。
所以从看起来像这样的代码开始:
struct MyContainer {
std::unique_ptr<IFoo> foo;
MyContainer() : foo(new Foo() ) {};
void doit() { foo->doit(); }
}
void useContainer() {
MyContainer container;
container.doit();
}
非出厂版本看起来像这样
struct MyContainer {
std::unique_ptr<IFoo> foo;
template<typename T>
explicit MyContainer(std::unique_ptr<T> && f) :
foo(std::move(f))
{}
void doit() { foo->doit(); }
}
void useContainer() {
std::unique_ptr<Foo> foo( new Foo());
MyContainer container(std::move(foo));
container.doit();
}
出厂版本看起来像
struct FooFactory : public IFooFactory {
std::unique_ptr<IFoo> createFoo() override {
return std::make_unique<Foo>();
}
};
struct MyContainer {
std::unique_ptr<IFoo> foo;
MyContainer(IFooFactory & factory) : foo(factory.createFoo()) {};
void doit() { foo->doit(); }
}
void useContainer() {
FooFactory fooFactory;
MyContainer container(fooFactory);
container.doit();
}
IMO 明确 C++ 中的所有权和生命周期很重要 - 它是 C++ 身份的关键部分 - 它位于 RAII 和许多其他 C++ 模式的核心。
我正在学习设计模式和相关的东西(特别是 SOLID 和 依赖倒置原则),看起来我我丢了东西:
遵循 DIP 规则,我应该能够通过不在 class 中创建对象(composition) 但将对象 reference/pointer 发送到 class 构造函数 (aggregation)。但这意味着我必须在其他地方创建一个实例:因此具有聚合功能的 class 越灵活,另一个就越脆弱。
请解释我哪里错了。
您只需要遵循这个想法,直到得出合乎逻辑的结论。是的,您必须在其他地方创建实例,但这可能不仅仅是在 class 之上的 class 中,它一直需要被推出和推出,直到仅在以下位置创建对象应用程序的最外层。
理想情况下,您在一个地方创建所有对象,这称为 composition root(例外情况是从工厂创建的对象,但工厂是在组合根中创建的)。具体位置取决于您正在构建的应用类型。
- 在桌面应用程序中,这将在 Main 方法中(或非常接近它)
- 在 ASP.NET(包括 MVC)应用程序中,这将在 Global.asax
- 在 WCF 中,它将在 ServiceHostFactory 中
- 等等
这个地方可能最终会成为 'fragile',但您只有一个地方可以更改内容以便能够重新配置您的应用程序,然后所有其他 class 都是可测试和可配置的.
参见 this excellent answer(上面引用)
是的,您需要在某处实例化 class。如果你正确地遵循 DIP,你最终会在一个地方创建所有的实例。我称这个地方为class作文。阅读我的博客以更深入地了解这个主题 Here
您错过的一个重要可能性是注入工厂,而不是 class 本身。这样做的一个好处是它可以让您对实例的所有权更加清晰。请注意,代码有点难看,因为我明确地赋予了其组件的容器所有权。如果您使用 shared_ptr
而不是 unique_ptr
,事情可能会更整洁一些,但是所有权不明确。
所以从看起来像这样的代码开始:
struct MyContainer {
std::unique_ptr<IFoo> foo;
MyContainer() : foo(new Foo() ) {};
void doit() { foo->doit(); }
}
void useContainer() {
MyContainer container;
container.doit();
}
非出厂版本看起来像这样
struct MyContainer {
std::unique_ptr<IFoo> foo;
template<typename T>
explicit MyContainer(std::unique_ptr<T> && f) :
foo(std::move(f))
{}
void doit() { foo->doit(); }
}
void useContainer() {
std::unique_ptr<Foo> foo( new Foo());
MyContainer container(std::move(foo));
container.doit();
}
出厂版本看起来像
struct FooFactory : public IFooFactory {
std::unique_ptr<IFoo> createFoo() override {
return std::make_unique<Foo>();
}
};
struct MyContainer {
std::unique_ptr<IFoo> foo;
MyContainer(IFooFactory & factory) : foo(factory.createFoo()) {};
void doit() { foo->doit(); }
}
void useContainer() {
FooFactory fooFactory;
MyContainer container(fooFactory);
container.doit();
}
IMO 明确 C++ 中的所有权和生命周期很重要 - 它是 C++ 身份的关键部分 - 它位于 RAII 和许多其他 C++ 模式的核心。