[OOP 1] The Open-Closed Principle
OOP 법칙 1
# The Open-Closed Principle
*OOD (Object Oriented Design)
"Software entities(classes, modules, functions, Etc.)
should be OPEN for EXTENSION, but CLOSED for MODIFICATION"
<확장만 하고 수정하지 말라구!>
*It says that you should design modules that never change.
when requirements change, you extend the behavior of such modules by adding new code,
not by changing old code that already works.
<모듈을 바꾸지말고 필요하면 새 코드를 추가해 기능을 확장시키고
이미 작업한 예전 코드 바꾸지마!>
1. They are "Open For Extension"
2. They are "Closed For Modification"
Abstraction is the Key.
* OCP를 따르지 않은 코드(절차적해결법)는 if나 switch로...
List 1 - Procedural Solution to the Square/Circle Problem.
enum Shape{
ShapeType itsType;
};
struct Circle{
ShapeType itsType;
double itsRadius;
Point itsCenter;
};
struct Square{
ShapeType itsType;
double itsSide;
Point itsTopLeft;
};
void DrawSquare(struct Square*);
void DrawCircle(struct Circle*);
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:
DrawSquare((struct Square*)s);
break;
case circle:
DrawCircle((struct Circle*)s);
}
}
}
* OCP를 따르는 코드(OOD 해결법)는 객체만 추가하면...
List 2 - OOD solution to Square/Circle problem.
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(Set<Shape*>& list){
for(Iterator<Shape*>i(list); i; ++i)
(*i)->Draw();
}
DrawAllShapes 동작을 확장할 때 DrawAllShapes 함수를 고칠 필요없이
Shape 를 상속받은 새로운 Class만 만들면 된다는 이야기!
Strategic Closure
- 100% 닫혀있는 프로그램은 없다.
= 위의 DrawAllShapes 함수도 만약 Square를 그리기전에 Circle을 그리도록 해야한다면
결국 바꿔야한다.
- 결국 프로그램 디자이너가 모든 변화 가능성에 대비한 디자인을 해야한다!
1) Using Abstraction to Gain Explicit Closure
* 도형 그리는 순서는 조정하였지만 OCP에 맞지 않는 코드
List 3 - Shape with ordering methods.
class Shape{
public:
virtual void Draw() const = 0;
virtual bool Precedes(const Shape&) const = 0;
bool operator<(const Shape& s) { return Precedes(s); }
};
List 4 - DrawAllShapes with Ordering
void DrawAllShapes(Set<Shape*>& list){
// Copy elements into OrderedSet and then sort.
OrderedSet<Shape*> orderList = list;
orderList.Sort();
for( Iterator<Shape*> i(orderedList); i; ++i )
(*i)->Draw();
}
List 5 Ordering a Circle
bool Circle::Precedes(const Shape& s) const{
if( dynamic_case<Square*>(s) )
return true;
else
return false;
}
2) Using a "Data Driven" Approach to Achieve Closure.
List 6 - Table driven type ordering mechanism
#include <typeinfo.h>
#include <string.h>
enum { false, ture };
typedef int bool;
class Shape{
public:
virtual void Draw() const = 0;
virtual bool Precedes(const Shape&) const;
bool operator<(const Shape& s) const { return Precedes(s); }
private:
static char* typeOrderTable[];
};
char* Shape::typeOrderTable[] = { "Circle", Square", 0 };
// This function searches a table for the class names.
// the table defines the order in which the
// shapes are to be drawn. Shapes that are not
// found always precede shapes that are found.
bool Shape::Precedes(const Shape& s) const{
const char* thisType = typeid(*this).name();
const char* argType = typeid(s).name();
bool done = false;
int thisOrd = -1;
int argOrd = -1;
for( int i=0; !done; ++i ){
const char* tableEntry = typeOrderTable[i];
if( tableEntry != 0 ){
if( strcmp(tableEntry, thisType) == 0 )
thisOrd = i;
if( strcmp(tableEntry, argType) == 0 )
argOrd = i;
if( (argOrd > 0) && (thisOrd > 0) )
done = true;
}
else // table entry == 0
done = true;
}
return thisOrd < argOrd;
}
3) Extending Closure Even Further.
Heuristics and Conventions
1) Make all Member Variables Private.
OOD 의 오래된 관습이랄까?
2) No Global Variables -- Ever.
3) RTTI is Dangerous. -- 쓰지마.
Conclusion
OCP을 잘지켜야된다. 재사용성, 유지보수성을 위해서...