C++中的立体设计原则:开闭原则

2020-05-11 13:16:31

这是关于Solid As Rock设计原则的五篇系列文章的第二部分。可靠的设计原则结合在一起,使得程序员可以很容易地制作出易于维护、重用和扩展的软件。开闭原则(OCP)是本系列的第二个原则,我将在这里用现代C++的极简示例及其优点&通用准则来讨论它。

顺便说一下,如果您还没有读过我以前关于设计原则的文章,那么下面是快速链接:

您在本系列文章中看到的代码片段是简化的,而不是复杂的。所以你经常看到我不使用诸如Override、Final、Public(虽然继承)这样的关键字,只是为了使代码紧凑和可使用(在大多数情况下)在单一的标准屏幕大小中。我也更喜欢struct而不是class,因为有时不编写“public:”来节省行,而且还故意遗漏了虚拟析构函数、构造函数、复制构造函数、前缀std::、删除动态内存。我也认为自己是一个务实的人,希望以尽可能简单的方式传达一个想法,而不是用标准的方式或使用行话。

如果您是直接在这里跌跌撞撞的,那么我建议您检查一下什么是设计模式?首先,即使它是微不足道的。我相信它会鼓励你在这个话题上进行更多的探索。

您在本系列文章中遇到的所有代码都是使用C++20编译的(尽管我在大多数情况下使用的是最高可达C++17的现代C++特性)。因此,如果您无法访问最新的编译器,您可以使用https://wandbox.org/,它也预装了Boost库。

从字面上看,这意味着您应该能够扩展类行为,而无需修改它。这可能对您来说很奇怪&可能会提出一个问题,即如何才能在不修改类的情况下更改类的行为呢?

但是在面向对象的设计中有很多解决方案,比如动态多态性、静态多态性、模板等等。

枚举类颜色{红,绿,蓝};枚举类大小{小,中,大};结构产品{string m_name;color m_color;size m_size;};Using Items=Vector<;Product*>;;#定义所有(C)BEGIN(C),END(C)struct ProductFilter{Static Items by_COLOR(Items,Const color e_color){Items Result;For(AUTO&;I:Items)IF(I-&>。}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&。m_color==e_color)result t.push_back(I);return result;}};int main(){const Items all{new Product{";Apple";,color::green,size::mall},new Product{";tree";,color::green,size::Large},new Product{";house";,color::Blue,size::Large},};(自动&;p:ProductFilter::by_color(all,color::green)cout<;<;p->;m_name<;<;";为(AUTO&;p:ProductFilter::by_size_and_color(all,size::Large,color:green))cout<;<;p->;m_name<;}/*苹果是绿树是绿树是绿&;大*/

所以我们有一系列的产品&我们根据它的一些属性对其进行了过滤。只要需求是固定的,上面的代码就没有错(在软件工程中永远不会出现这种情况)。

但是可以想象一下这样的情况:您已经将代码发送给了客户端。稍后,需求更改&&需要一些新的过滤器。在这种情况下,您再次需要修改类&;add new filter方法。

这是一个有问题的方法,因为我们有2个属性(即颜色和大小)&;需要实现3个功能(即颜色、大小及其组合),另外一个属性&;需要实现8个功能。你知道这是怎么回事了吧。

您需要再次执行&;在现有的已实现代码中&;必须修改它,这可能还会破坏代码的其他部分。这不是一个可扩展的解决方案。

开放-关闭原则规定,您的系统应该对扩展开放,但是对于修改应该关闭。不幸的是,我们在这里做的是修改现有代码,这违反了OCP。

实现OCP的方法不止一种。在这里,我将演示流行的界面设计或抽象层。下面是我们的可扩展解决方案:

Template<;TypeName T>;struct Specification{virtual~Specification()=default;Virtual bool is_subsured(T*Item)const=0;};struct ColorSpecification:Specification<;Product>;{color e_color;ColorSpecification(Color E_Color):E_COLOR(E_Color){}bool is_subsured(Product*Item)const{Return Item->;m_color==e_color;}}。SizeSpecification(Size E_Size):E_Size(E_Size){}bool is_subsued(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;Product*>;Items,Const Specification<;Product>;&;spec){Vector<;Product*>;Result;for(auto&;p:Items)if(spec.is_ssured(P))result.Push_back(P);Return Result;}};//--BetterFilter bf;For(AUTO&;x:bf.filter(ALL,色彩规格(COLOR::GREEN)cout<;<;X->;m_name<;<;";为绿色\n&34;;

如您所见,我们不必修改BetterFilter的Filter方法。它现在可以与各种规格一起使用。

Template<;TypeName T&>;struct&>Specification:Specification<;T>;&;First Const Specification<;T>;&;First Const Specification<;T>;&;Second AndSpecification(Const Specification<;T>;&;First,Const Specification<;T>;&;Second):First(First),Second(Second){}bool。}};Template<;TypeName T>;AndSpecification<;T>;Operator&;&;(Const Specification<;T>;&;First,Const Specification<;T>;&;Second){return{first,Second};}//--auto GREEN_ITHES=颜色规格{COLOR::GREEN};AUTO LARGE_ITHES=尺寸规格{SIZE::LARGE};BetterFilter bf;for(auto&;x:bf.filter(all,green_Things&;&;Large_Things))cout<;<;x->;m_name<;<;";;//警告:以下内容可以编译,但不起作用//auto spec2=SizeSpecification{size::Large}&;&;//ColorSpecification{。

SizeSpecification{size::Large}&;&;ColorSpecification{color::Blue}将不起作用。有经验的C++眼睛可以很容易地识别其中的原因。虽然临时对象创建在这里是一个提示。如果这样做,可能会得到如下纯虚函数的错误:

名为Terminate的纯虚方法在没有活动异常的情况下调用终端进程终止,退出代码为:3。

“当对程序的一次更改导致对相关模块的一系列更改时,该程序就会显示出我们已经将其与‘糟糕’设计联系在一起的不良属性。程序变得脆弱、僵硬、不可预测和不可重用。开放-封闭原则以一种非常直截了当的方式攻击这一点。它说你应该设计永远不变的模块。当需求发生变化时,您可以通过添加新代码来扩展这些模块的行为,而不是通过更改已经可以工作的旧代码。“-Robert Martin。

这种方法的主要好处是,接口引入了额外的抽象级别,从而实现了松散耦合。接口的实现彼此独立,不需要共享任何代码。

因此,您可以很容易地应付客户不断变化的要求。在敏捷方法中非常有用。

开闭原则也适用于插件和中间件架构。在这种情况下,您的基础软件实体就是您的应用程序核心功能。

在插件的情况下,您有一个基本或核心模块,可以通过公共网关接口插入新特性和新功能。Web浏览器扩展就是一个很好的例子。

在SRP中,您可以判断代码中的分解和在何处绘制封装边界。在OCP中,您可以判断您将把模块中的哪些内容抽象,并留给模块的使用者来具体,以及您自己要提供哪些具体的功能。

有许多设计模式可以帮助我们在不更改代码的情况下扩展代码。例如,装饰者模式帮助我们遵循开闭原则。此外,工厂方法、策略模式或观察者模式也可用于设计易于更改的应用程序,只需对现有代码进行最少的更改。

请记住,课程永远不能完全停课。总会有不可预见的变化需要修改类。然而,如果更改是可以预见的,比如上面看到的过滤器,那么当这些更改请求滚滚而来时,您就有一个完美的机会来应用OCP来为将来做好准备。

你喜欢吗,☝️?将此类文章直接放入收件箱…。📥