贺利坚教程
这是关于SOLID作为Rock设计原理的五部分系列的第二部分。 SOLID设计原理结合在一起后,可使程序员轻松制作易于维护,重用和扩展的软件。 Ø笔游离缺失c ^ P rinciple(OCP)是在这个系列里面我将与简约例如在这里讨论的第二个原则的现代C ++有它的好处和通用准则一起。
/!\:最初发布于www.vishalchovatiya.com 。
顺便说一句,如果您还没有阅读我以前有关设计原则的文章,那么下面是快速链接:
您在本系列文章中看到的代码段是
简化不复杂。 所以你经常看到我不使用关键字
例如覆盖,最终,公共(继承时),只是为了使代码紧凑且可消耗(大部分时间)在单个标准屏幕尺寸内。
我也更喜欢使用struct而不是class来保存行,因为有时不写“ public:”,并且还错过了虚拟析构函数 ,构造函数, 复制构造函数 ,前缀std ::,故意删除了动态内存。 我也认为自己是一个务实的人,希望以最简单的方式传达想法
可能而不是标准方式或使用行话。
注意:
- 如果您直接在这里偶然发现,那么我建议您经历一下什么是设计模式? 首先,即使是微不足道的。 我相信它将鼓励您探索有关此主题的更多信息。
- 在本系列文章中遇到的所有这些代码都是使用C ++ 20编译的(尽管在大多数情况下,我使用的是Modern C ++的功能,直到C ++ 17为止)。 因此,如果您无权访问最新的编译器,则可以使用https://wandbox.org/ ,它也已预装了boost库。
意图
类应该打开以进行扩展,关闭以进行修改
- 从字面上看,这意味着您应该能够扩展类的行为,而无需对其进行修改。 这对您来说似乎很奇怪,并且可能引发一个问题,即如何在不修改类的情况下更改其行为?
- 但是在面向对象设计中对此有很多答案,例如动态多态 , 静态多态 ,模板等。
违反开放封闭原则
enum class COLOR { RED, GREEN, BLUE };
enum class SIZE { SMALL, MEDIUM, LARGE };
struct Product {
string m_name;
COLOR m_color;
SIZE m_size;
};
using Items = vector <Product*>;
# define ALL(C) begin(C), end(C)
struct ProductFilter {
static Items by_color (Items items, const COLOR e_color) {
Items result;
for ( auto &i : items)
if (i->m_color == e_color)
result.push_back(i);
return result;
}
static Items by_size (Items items, const SIZE e_size) {
Items result;
for ( auto &i : items)
if (i->m_size == e_size)
result.push_back(i);
return result;
}
static Items by_size_and_color (Items items, const SIZE e_size, const COLOR e_color) {
Items result;
for ( auto &i : items)
if (i->m_size == e_size && i->m_color == e_color)
result.push_back(i);
return result;
}
};
int main () {
const Items all{
new Product{ "Apple" , COLOR::GREEN, SIZE::SMALL},
new Product{ "Tree" , COLOR::GREEN, SIZE::LARGE},
new Product{ "House" , COLOR::BLUE, SIZE::LARGE},
};
for ( auto &p : ProductFilter::by_color(all, COLOR::GREEN))
cout << p->m_name << " is green\n" ;
for ( auto &p : ProductFilter::by_size_and_color(all, SIZE::LARGE, COLOR::GREEN))
cout << p->m_name << " is green & large\n" ;
return EXIT_SUCCESS;
}
/*
Apple is green
Tree is green
Tree is green & large
*/
- 因此,我们有一堆产品,并按其某些属性对其进行了过滤。 只要要求是固定的,上述代码就没有问题(在软件工程中永远不会这样)。
- 但是,请想象一下情况:您已经将代码交付给客户端了。 稍后,需要更改需求和一些新的过滤器。 在这种情况下,您再次需要修改类并添加新的过滤器方法。
- 这是一个有问题的方法,因为我们有2个属性(即颜色和大小)并且需要实现3个功能(即颜色,大小及其组合),又有一个属性并且需要实现8个功能。
- 您会看到它的去向。您需要在现有的已实现代码中再三遍一遍,还必须对其进行修改,这也可能破坏代码的其他部分。 这不是可扩展的解决方案。
- 开闭原理指出,您的系统应该可以扩展,但是应该关闭以进行修改。 不幸的是,我们在这里所做的是修改现有代码,这违反了OCP。
开闭原理示例
实现OCP的方法不止一种。 我在这里示范
流行的一种,即界面设计或抽象级别。 因此,这是我们的可扩展解决方案:
添加可扩展性的抽象级别
template < typename T>
struct Specification {
virtual ~Specification() = default ;
virtual bool is_satisfied (T *item) const = 0 ;
};
struct ColorSpecification : Specification<Product> {
COLOR e_color;
ColorSpecification(COLOR e_color) : e_color(e_color) {}
bool is_satisfied (Product *item) const { return item->m_color == e_color; }
};
struct SizeSpecification : Specification<Product> {
SIZE e_size;
SizeSpecification(SIZE e_size) : e_size(e_size) {}
bool is_satisfied (Product *item) const { return item->m_size == e_size; }
};
template < typename T>
struct Filter {
virtual vector <T *> filter( vector <T *> items, const Specification<T> &spec) = 0 ;
};
struct BetterFilter : Filter<Product> {
vector <Product *> filter( vector <Product *> items, const Specification<Product> &spec) {
vector <Product *> result;
for ( auto &p : items)
if (spec.is_satisfied(p))
result.push_back(p);
return result;
}
};
// ------------------------------------------------------------------------------------------------
BetterFilter bf;
for ( auto &x : bf.filter(all, ColorSpecification(COLOR::GREEN)))
cout << x->m_name << " is green\n" ;
- 如您所见,我们不必修改BetterFilter的过滤器方法。 现在,它可以适用于各种规格。
对于两个或多个组合规格
template < typename T>
struct AndSpecification : Specification<T> {
const Specification<T> &first;
const Specification<T> &second;
AndSpecification( const Specification<T> &first, const Specification<T> &second)
: first(first), second(second) {}
bool is_satisfied (T *item) const {
return first.is_satisfied(item) && second.is_satisfied(item);
}
};
template < typename T>
AndSpecification<T> operator &&( const Specification<T> &first, const Specification<T> &second) {
return {first, second};
}
// -----------------------------------------------------------------------------------------------------
auto green_things = ColorSpecification{COLOR::GREEN};
auto large_things = SizeSpecification{SIZE::LARGE};
BetterFilter bf;
for ( auto &x : bf.filter(all, green_things &&large_things))
cout << x->m_name << " is green and large\n" ;
// warning: the following will compile but will NOT work
// auto spec2 = SizeSpecification{SIZE::LARGE} &&
// ColorSpecification{COLOR::BLUE};
- SizeSpecification {SIZE :: LARGE} && ColorSpecification {COLOR :: BLUE}
不管用。 经验丰富的C ++眼睛可以轻松识别原因。
尽管临时对象的创建在这里是一个提示。 如果这样做,您可能
得到纯虚函数的错误如下:
purevirtual method called
terminate called without an active exception
The terminal process terminated with exit code: 3
- 对于两个以上的规范,可以使用可变参数模板。
开放封闭原则的好处
=>可扩展性
“如果对程序的单个更改导致对相关模块的级联更改,则该程序会表现出我们与“不良”设计相关联的不良属性。该程序变得脆弱,僵化,不可预测且不可重用。封闭原则以一种非常直接的方式对此进行了攻击。它表示您应该设计永不更改的模块。当需求更改时,您可以通过添加新代码而不是通过更改已经起作用的旧代码来扩展此类模块的行为。”
— 罗伯特·马丁
=>可维护性
- 这种方法的主要好处是接口引入了
额外的抽象级别,可以实现松散耦合。 的
接口的实现彼此独立且不相互独立
需要共享任何代码。 - 因此,您可以轻松应对客户不断变化的需求。 在敏捷方法学中非常有用。
=>灵活性
- 开闭原则也适用于插件和中间件
建筑。 在这种情况下,您的基本软件实体就是您的应用程序核心功能。 - 对于插件,您有一个基本模块或核心模块,可以通过公共网关接口插入新功能。 Web浏览器扩展就是一个很好的例子。
- 二进制兼容性在后续版本中也将保持不变。
制作开放式封闭原理友好软件的标尺
- 在SRP中,您需要对分解以及在代码中绘制封装边界的位置进行判断。 在OCP中,您将对模块中要进行抽象的内容并由模块的使用者进行具体化以及要提供自己的具体功能进行判断。
- 有许多设计模式可以帮助我们扩展代码而无需更改代码。 例如, Decorator模式可以帮助我们遵循Open Close原则。 同样,可以使用Factory Method , Strategy模式或Observer模式来设计易于更改的应用程序,而只需对现有代码进行最少的更改即可。
结论
请记住,永远不能完全关闭类。 总是会有无法预料的更改,需要对类进行修改。
但是,如果可以预见到更改,例如上面的过滤器所示,那么当这些更改请求出现时,您就有绝佳的机会将OCP应用于将来。
先前发布在 http://www.vishalchovatiya.com/open-closed-principle-in-cpp-solid-as-a-rock/
翻译自: https://hackernoon.com/open-closed-principle-solid-as-a-rock-xyd43y0g
贺利坚教程