Join us and get access to thousands of tutorials and a community of expert Pythonistas.

Unlock This Lesson

This lesson is for members only. Join us and get access to thousands of tutorials and a community of expert Pythonistas.

Unlock This Lesson

Open-Closed

00:00 In the previous lesson, I described the single-responsibility principle. In this lesson, I’ll talk about the open-closed principle. The open-closed principle, or OCP to its friends.

00:13 You down with OCP? Yeah, you know me. Okay, now I have to apologize twice. First, for doing that while being as pasty white as I am. Truly, I put mayonnaise to shame.

00:24 And second, for mangling the name of a thirty-year-old R & B hit and including it in an object-oriented programming course. Where was I? The open-closed principle is as follows: software entities should be open for extension but closed for modification.

00:42 In this case, open means I should be able to modify it without breaking anything dependent on it. In classes, this typically means any subclass shouldn’t break if I touch the parent class.

00:54 Closed for modification means the interface is stable. If someone is extending my class, the base class should be as stable as possible. Otherwise, it will muck with the subclass. Essentially, if adding features means you end up having to redesign your structure, you’re probably not embodying this principle.

01:14 Let’s look at a violation of OCP and then a better approach.

01:20 Another object-oriented day, another example using shapes. If you took part two of the course, you’ll remember it’s the law. This Shape class is a bit of a factory abstraction. To create a circle or rectangle, you pass in the type of the shape you want, and the class becomes a generic wrapper to it. Of course, if you take this approach, your code is going to be littered with if it’s a rectangle, then do …

01:48 The same goes for circle. This mess isn’t just in .__init__(), but any method that needs to understand the difference between the types of shapes is going to have the same big if then else block.

02:01 Besides being a bit ugly, how does this violate the open-closed principle? Well, think about adding a triangle. I now have to go back and do surgery on every one of the methods, adding an ifelse "triangle" clause.

02:14 This violates the closed for modification part of the open-closed principle. If someone extended this class and implemented, say, a perimeter method, they’d have to be aware of the internal mechanisms of the parent.

02:29 This violates the open part of open-closed. If I add the triangle to the parent, the child’s perimeter method would now be broken.

02:42 A better way to do this is to split out circles and rectangles into their own classes. If you want to enforce the interface that Shape should provide, you can do that through an abstract base class in Python.

02:55 By inheriting from ABC, I get to use the @abstractmethod decorator inside the class. When I use that to wrap a method, it tells the extender they must implement this method. If they forget, they’ll get an exception the first time they go to instantiate their class.

03:13 By separating the code out into different classes, you get another advantage. The previous version of Shape needed to use the **kwargs mechanism for initial arguments because different shapes have different needs.

03:25 By using a dedicated Circle class, I can be very specific about its need for a radius and only a radius.

03:32 If I still want the shape-type information, I can keep that in the parent and use super().__init__() to populate it. In this simple example, you really don’t need the shape-type info, as the class makes itself evident, but I left it there so you could see an example of the base class requiring an argument.

03:50 The .calculate_area() method now is specific to a circle without any extra if then else cruft.

03:58 The same goes for our rectangle. Its constructor can use width and height, and it can implement its own area formula.

04:09 If you’re finding you are violating the open-closed principle, you typically can fix it by reexamining your class hierarchy or sometimes just introducing one. You saw me use an abstract base class and inheritance to solve the Shape class problem.

04:24 You might also use delegation or dependency injection as well. Both of these are ways of doing composition that keeps the implementation objects isolated.

04:34 If you’re coming from another language besides Python and you skipped part two of this course, you may not have seen an abstract base class before. Python implements the concept as an actual class rather than as part of the language’s syntax.

04:50 One way to consider whether you’ve nailed the open-closed principle is to think about the consequences of adding something with the better implementation of the Shape class. Adding a new color attribute is fine.

05:02 It would just get passed to the children without breaking anything. Likewise, I can tell that it’s closed, as adding Triangle would mean a new class and wouldn’t require surgery on Shape, Circle, or Rectangle to get it to work.

05:19 That’s the O. Time for L.

Avatar image for fotoalin

fotoalin on Dec. 6, 2023

I watched Open-Closed principle lecture several time and I’m yet to understand how to use it as there’s no example at all. You defined a bunch of classes and now what? Why is the Shape class there? Why not define a Circle class without having a Shape class? I would still pass the radius into the circle wouldn’t I?

Would it be possible to redo the lectures and include one-two examples as this concepts are quite difficult for me to understand.

Avatar image for fotoalin

fotoalin on Dec. 6, 2023

I believe I now understand the purpose of the Shape class. While I can definitely create a new class with the required methods without inheriting from Shape base class, using inheritance can provide clearer structure and expectations in my code. In other words, the Shape class it’s there for making the code more readable and to set expectations for developers and it doesn’t have any functionality purposes. Am I getting this right?

Avatar image for Christopher Trudeau

Christopher Trudeau RP Team on Dec. 6, 2023

Hi fotoalin,

OO coding tends to get used for two purposes: 1) code re-use, 2) structure.

For example, in Django, a base class gets used to map class declarations to tables in a database in a system called an ORM. The base class includes all the code that interacts with the database, so your subclass only needs to declare fields (the table columns). So the base class takes care of all the hard work of talking SQL to the database, but your class only needs to say “I want a text field”. This is mostly the “code re-use” style.

One of the challenges of teaching OO code, is the real world examples tend to be more complicated than folks new to OO are going to want to take, so you end up dealing with simpler examples like shapes, where in the real world you probably wouldn’t code it that way.

The second case of “structure” is the idea of capturing when one kind of object is another kind of object. A square is a rectangle. You can use OO definitions to describe this kind of relationship in your code. Most of the time though, this causes problems. Although I can write a sqaure as being a subclass of a rectangle, now I have a length and a width, rather than just a “side”.

Most OO design of classes is going to have both some aspect of “code re-use” and some of “structure”. In the Django case I mentioned above as a “re-use” example, each subclass is a table, which is a structural idea. The concepts introduced in SOLID attempt to give you guidelines about whether you’re getting the mix between the ideas correctly, and whether you’re making good design decisions.

Remember that SOLID is not a Python specific thing. Some of the worries that SOLID is trying to address don’t show up as often in Python as they might in Java. Everything in Java is a class, so you often end up having to do procedural things in a class manner. While Python programmers don’t tend to break out OO tools unless it is necessary.

To get back to your original question: the Open-Closed principle can be thought of as a test for your OO design. If adding a new concept to the design means having to change your existing classes, you might be failing at the Open-Closed principle. If you come up with a new idea, and implement that idea as a new subclass, the test is: did you need to change the parent class to get it to work?

Back to my Django example: lets say I have a Person class that represents a table in the database with first and last name columns. I then want a new table called Employee that has employee number as well as first and last name columns, so I inherit from Person. If creating Employee meant coding changes were necessary to the Person class, then something I’ve done has violated Open-Closed. If I can just subclass Person and not worry about it, then I’ve probably done a good job with Open-Closed.

In the real world, the example I just gave is a bad idea, because now I have two tables and what if a Person is also an Employee? I’ve duplicated the data. This is the challenge with coming up with a simple example – in trying to explain the Open-Closed principle, I’ve violated the Single-Responsibility principle. This is why we have SOLID, to try and consider all these concepts together when designing your code.

Hope this helps.

Become a Member to join the conversation.