设计一个模块时,应当使该模块在不被修改的前提下被扩展,即可在不必修改源代码的情况下改变该模块的行为。
陈述:
软件实体(类、模块、函数等)应该是可以扩展的,同时还可以是不必修改的,更确切的说,函数实体应该:
(1)对扩展是开放的
当应用的需求变化时,我们可以对模块进行扩展,使其具有满足改变的新的行为。即:我们可以改变模块的功能
(2)对更改是封闭的
对模块进行扩展时,不必改动模块已有的源代码或二进制代码。
分析:
实现OCP的关键是抽象:
例1:既不开放也不封闭的Client:
问题:
client和server都是具体类,接口与实现没有实现分离。如果我们想要让client调用一个新的server类,那么我们不得不修改client的源代码。从而带来编译、链接、部署等一系列的问题。
class client{ server& s; public: client(server& SER):s(SER) {} void useServer(){ s.ServerFunc(); } };
class server{ int serverData; public: void ServerFunc(); };修改后的设计:
class client{ ClientInterface& ci; public: client(ClientInterface & CI):ci(CI){} void useServer(){ ci.ServerFunc(); } };
class ClientInterface{ virtual void ServerFunc()=0; }; class server:public ClientInterface{ int serverData; public: void ServerFunc(); };问题:
——client类中更多的描述了高层的策略,而Server类中是对这些策略的一种具体实现。
例2:
//---------shape.h----------------- emum ShapeType{circle,square}; struct Shape{ ShapeType itsType; }; //---------circle.h----------------- struct Circle{ ShapeType itsType; double itsRadius; CPoint itscenter; }; //---------square.h----------------- struct Square{ ShapeType itsType; double itsSide; CPoint itsTopLeft; };
//---------drawAllShapes.cpp---------- typedef struct Shape * ShapePointer; void DrawAllShapes(ShapePointer list[], int n){ int i; for(i=0;i<n;i++){ struct Shape* s=list[i]; switch (s->itsType){ case square: s->Square(); break; case circle: s->DrawCircle(); break; } }}
例2的问题:
这个程序不符合OCP,如果需要处理的几何图形中再加入“三角形”将引发大量的修改。
例2 修改后的设计:
class Shape{ public: virtual void Draw() const=0; }; class Square:public Shape{ public: virtual void Draw() const; }; class Circle:public Shape{ public: virtual void Draw() const; };
void DrawAllShapes(Vector<Shape*>& list){ vector<Shape*>::iterator i; for(i=list.begin();i!=list.end();i++) (*i)->Draw(); }
小结:
OCP----封装思想的体现
对可变性的封装原则:
具体的:
相应设计模式:
参考资源:
《设计模式:可复用面向对象软件的基础》,ERICH GAMMA RICHARD HELM RALPH JOHNSON JOHN VLISSIDES著作,李英军 马晓星 蔡敏 刘建中译,机械工业出版社,2005.6
《敏捷软件开发:原则、模式与实践》,Robert C. Martin著,邓辉译,清华大学出版社,2003.9
《设计模式解析》,Alan Shalloway等著(徐言声译),人民邮电出版社,2006.10