SOLID Design Principles

Open-Closed Principle (OCP)

The Open-Closed Principle states that classes should be open to extension, but closed to modification.

Changes to a program should occur primarily by writing new code, not changing code that already exists.

Example

  • What if we wanted our Hero to use a different type of weapon? Bow or MagicWand?
  • Requires us changing all the places we referenced the Sword class - lots of modifications, not much extension.

Fix is to use Strategy Pattern:

The Open-Closed Principle is closely related to the concept of Inheritance and Virtual Functions.

For example:

// GOOD: follows open-closed principle
int countHeavy(const vector<Book*>& v) {
	int count = 0;
	for (auto p: v) {
		if (p->isHeavy()) ++count;
	}
	return count;
}

This function is open to extension: we can add more functionality by defining new types of Books, and closed to modification: countHeavy never needs to change now that it has been written.

Compare this to WhatIsIt (taken from dynamic_cast)

// BAD
void whatIsIt(shared_ptr<Book> b) {
	if (dynamic_pointer_cast<Text>(b)) cout << "Text";
	else if (dynamic_pointer_cast<Comic>(b)) cout << "Comic";
	else cout << "Normal Book";
}
  • not open to extension: cannot get new behavior without modifying the source code
  • not closed to modification either

OCP is an Ideal

Open/closed principle is an ideal - writing a program will require modifications at some point.

Plan for things that are most likely to change, account for those.

Another Example

Saw this example from SE464 class.

BAD (violates OCP)

void Painter::paintComponent(Graphics g) {
    for(Circle c : circles) {
        g.drawArc(c.getCenterX()-c.getRadius(),
        c.getCenterY()-c.getRadius(),
        c.getRadius()*2, c.getRadius()*2,
        0, 360);
    }
    for(Triangle t : triangles) {
        //logic for drawing triangle t
    }
}

GOOD

void Painter::paintComponent(Graphics g) {
    for(Shape s : shapes) {
        s.draw(g);
    }
}

in order to support the drawing of a new Shape, e.g. Triangle, in the bad case you’d need to modify both the Painter class and add a new Triangle class, whereas in the good case you’d only need to implement the Triangle class without modification to the Painter class.

Next