Mark As Completed Discussion

Fundamental Properties of Object-Oriented Programming

In this lesson, we will discuss the four fundamental properties of Object-Oriented Programming. These are the building blocks of programming in the OOP paradigm. In fact, OOP was actually primarily invented to help programmers enforce and follow these four basic properties while writing code.

The four properties of OOP we'll cover are:

  1. Encapsulation
  2. Abstraction
  3. Inheritance
  4. Polymorphism

If a programming language supports these four properties, then it is classified as an OOP language. Java, C++, Python, and Javascript all belong in this category. However, for a language to be considered a Pure Object Oriented Programming Language, it must follow three additional rules. Those are:

  1. Each primitive type must be an object.
  2. Each user-defined type must be an object.
  3. All operations on objects must be performed by invoking a method associated with an object.

Pure OOP languages have a lot of limitations in terms of practical usage which is why they are pretty rare. The only pure OOP language that is close to popular is Smalltalk.

In this lesson, we will go through the four fundamental properties one by one and give examples of each. Instead of using the keyboard-monitor example, we will use a more trivial Shape example. We will design an object-oriented architecture to represent different kinds of shapes.

Fundamental Properties of Object Oriented Programming

Encapsulation

This rule/property is the easiest to understand among the four. This property dictates that all objects should be capsules and that all data related to an object should be inside that object.

Encapsulation

The code below does not follow the rule of encapsulation. An object cannot access a property if the property is outside itself (i.e., outside the capsule). Thus, this code will not even compile since encapsulation says that all objects and methods in Java must be within an object.

1/* This is wrong, shapeMaxWidth is external to the class */
2var shapeMaxWidth = 0
3type Shape struct {
4	maxHeight     int
5}
6
7func (s Shape) getMaxArea() int {
8	return shapeWidth*height
9}

Abstraction

Abstraction in OOP is similar to the abstract data types (ADTs) we discussed in an earlier lesson. With ADTs, we do not need to know how a data structure is implemented. We only know the specifications of how it should behave.

Abstraction

Abstraction is also a way to tell the user of the class how to use the class without letting them know how it is implemented internally. With abstraction, you can "hide" all the decisions and processing steps within your Keyboard class.

Most programming languages have special keywords to utilize the concept of abstraction (e.g., public and private in Java). When designing a class, we can use these keywords to prevent anyone from calling certain functions from outside of the class. The outside user should never be able to know all the properties and methods of a class. They should only know the methods that are public and needed for the usage of the class.

Let's improve our Shape class by adding some abstractions to it. The end-user (inside the Main class) can only call the methods that are needed to use a Shape object.

1// Shape.go
2type Shape struct {
3    // Go does not have classes, only structs. Field name capitalized (made public) to mimic inherited class access.
4    MaxWidth  int
5    MaxHeight int
6}
7
8func (s *Shape) getMaxWidth() int {
9    return s.MaxWidth
10}
11
12func (s *Shape) getMaxHeight() int {
13    return s.MaxHeight
14}
15
16func (s *Shape) setMaxWidth(maxWidth int) {
17    s.MaxWidth = maxWidth
18}
19
20func (s *Shape) setMaxHeight(maxHeight int) {
21    s.MaxHeight = maxHeight
22}
23
24func (s *Shape) getMaxArea() int {
25    return s.MaxWidth * s.MaxHeight
26}
27
28// Now another programmer will use the Shape's public methods
29
30func main() {
31    shape := Shape{}
32
33    // Access public methods
34    fmt.Println(shape.getMaxHeight())
35    fmt.Println(shape.getMaxWidth())
36
37    // Try to access private data. Go does not have private variables in the same sense, but unexported (lowercased) variables would be equivalent.
38    fmt.Println(shape.MaxWidth)
39}

A method that returns a property of an object is known as a getter method. A method that sets a property value is known as a setter method. These are often used in Java to make properties private and accessible only through methods. You could also do some validation or calculation before returning a property if you are using a getter or setter. We will learn more about getters and setters in a later lesson.

Inheritance

The biggest draw of OOP is its inheritance property.

Inheritance

Inheritance is the mechanism that binds two classes in a parent-child relationship. In this relationship, the child can access all the properties and methods of the parent. An object can originate from a more specific type of the parent class which allows you to inherit that object as a child from its parent class. If the behavior of the child class needs to change, we can override or add to the methods of the parent class to work as we want. We cannot change the function signature though.

In the Shape class example, think of a Circle or a Rectangle. Both of these objects also need a getMaxArea method, a maxWidth property, and a maxHeight property. Instead of reimplementing the same methods for these new classes by copy-pasting, we can just inherit these two classes from the Shape class.

Let's implement it now! Java has a keyword extends which applies inheritance to a class. See the example below.

1// Circle.go
2type Circle struct {
3    // Here, we don't need to define maxHeight and maxWidth. They are already defined
4    // Later we will learn how to initialize these when we learn about constructors
5
6    // Adding new properties
7    Shape
8    radius int
9}
10
11// Adding new methods
12func (c *Circle) getRadius() int {
13    return c.radius
14}
15
16func (c *Circle) setRadius(radius int) {
17    c.radius = radius
18}
19
20func (c *Circle) getArea() float64 {
21    return 3.14 * float64(c.radius) * float64(c.radius)
22}
23
24// Rectangle.go
25type Rectangle struct {
26    Shape
27    height int
28    width  int
29}
30
31// Adding new methods
32func (r *Rectangle) getHeight() int {
33    return r.height
34}
35
36func (r *Rectangle) getWidth() int {
37    return r.width
38}
39
40func (r *Rectangle) setWidth(width int) {
41    r.width = width
42}
43
44func (r *Rectangle) setHeight(height int) {
45    r.height = height
46}
47
48func (r *Rectangle) getArea() int {
49    return r.height * r.width
50}
51
52// main.go
53func main() {
54    circle := Circle{}  // Instantiating Circle Struct
55    // Calling circle's own methods
56    fmt.Println(circle.getArea())
57    // Calling parent classes methods
58    fmt.Println(circle.getMaxArea())
59}

Note that, inheritance is only allowed if the two objects can be thought of as the same or similar. A circle is a shape. A rectangle is also a shape.

On the other hand, if we wanted to implement a Line class, then it could not be inherited from Shape since a line is not a shape. Also, it should not have methods like getArea. However, a shape can contain a number of lines. We will discuss this more when we talk about Composition and Association in a later lesson.

Try this exercise. Fill in the missing part by typing it in.

Which keyword in Java is used to inherit class A from class B?

Write the missing line below.

Polymorphism

The last of the four fundamental properties of OOP is polymorphism. Polymorphism is a property that says all objects must act like all its parent types from which it is inherited. It can also act like other types (e.g., interfaces). In this section, we will discuss only the first part and leave interfaces for another lesson.

Polymorphism

Let's take our Shape class example and build a program with user inputs. In our console program, the user will select a number of different shapes and input the necessary properties of those shapes. Our program will then calculate the total area of all the shapes and output the sum!

The first thing we need for each shape is a method that takes values from the user. Below is the code for it. Notice that we are using an annotation @Override to override a method. This helps the IDE to identify your mistake if you accidentally create a new method instead of overriding an existing method (e.g. change parameter order or type).

JAVA
OUTPUT
:001 > Cmd/Ctrl-Enter to run, Cmd/Ctrl-/ to comment

The Wrong Way

Our classes are ready. Now we can write the main program. If we did not know about polymorphism, we would have had to do something like the program below.

JAVA
OUTPUT
:001 > Cmd/Ctrl-Enter to run, Cmd/Ctrl-/ to comment
GO
OUTPUT
:001 > Cmd/Ctrl-Enter to run, Cmd/Ctrl-/ to comment

The Polymorphism Way

The implementation above has several drawbacks. What if we wanted to do some other operations on the user's given shapes? We would have to do that operation on each object in each case block. What if you had hundreds of different shapes? You would need to update hundreds of blocks just to add a similar-looking line! We would also need more ArrayLists to hold the results.

Polymorphism to the rescue! Let's re-implement this program using the concept of polymorphism. We know that all rectangles, circles, and squares are a type of Shape, so we can declare all of these as shapes and use a common rule for them. We will do the following things in our code:

  1. Create a shapeClasses array where all the blueprints (class references like Rectangle, Square, etc.) will be kept.
  2. When a user gives inputs to create an ith shape, we will automatically create an instance of that class from the shapeClasses array.
    • Note that we create the (i-1)th shape according to the user input i since arrays are 0 indexed.
JAVA
OUTPUT
:001 > Cmd/Ctrl-Enter to run, Cmd/Ctrl-/ to comment

javascript

JAVASCRIPT
OUTPUT
:001 > Cmd/Ctrl-Enter to run, Cmd/Ctrl-/ to comment

python

PYTHON
OUTPUT
:001 > Cmd/Ctrl-Enter to run, Cmd/Ctrl-/ to comment

cpp

CPP
OUTPUT
:001 > Cmd/Ctrl-Enter to run, Cmd/Ctrl-/ to comment

csharp

CSHARP
OUTPUT
:001 > Cmd/Ctrl-Enter to run, Cmd/Ctrl-/ to comment

go

GO
OUTPUT
:001 > Cmd/Ctrl-Enter to run, Cmd/Ctrl-/ to comment

Some notes:

  • Ignore the getDeclaredConstructor part for now.
  • We are instantiating an object from the class with the newInstance method instead of the new keyword. There are many more ways to instantiate an object. There are also several design patterns (e.g. Factory Design Pattern) to create objects.
  • Notice that we are putting all the subclasses of Shape (Rect, Circle, etc.) into the Shape type. This means that Rectangle, Circle, and Square act as Shape types which is an example of polymorphism.
  • If a class in shapeClasses is not inherited from Shape, then the compiler will panic which is why we need a try-catch block.

Notice how much more concise the code becomes when compared to the previous one. Also, if we had to do some other stuff and call other methods, we can just add that command after the for loop just like how we called the getArea() method: writing it once and applying it to all shapes. This is the power of polymorphism. Later we will learn more about what polymorphism can do when we understand interfaces and adapters.

Build your intuition. Click the correct answer from the options.

What is the console output of the below code snippet?

1class A {
2    public void methodCall() {
3        System.out.println("Call from A");
4    }
5}
6
7class B extends A {
8    public void methodCall() {
9        System.out.println("Call from B");
10    }
11}
12
13public class Main {
14    public static void main(String[] args) {
15        A a = new B();
16        a.methodCall();
17    }
18}

Click the option that best answers the question.

  • Call from A
  • Call from B
  • Call from A Call from B
  • Error

Conclusion

Whoa! Today was a big day for you. You have covered all the fundamental concepts of OOP. Although the basics are now out of the way, there are still a ton of things you need to know. In our next lesson, we will cover more core aspects of OOP including constructors, destructors, virtual methods, and abstract classes.

One Pager Cheat Sheet

  • This lesson covers the four fundamental properties of Object-Oriented Programming and explains how Encapsulation, Abstraction, Inheritance, and Polymorphism help create an OOP language such as Java, C++, Python, or JavaScript, and the conditions for it to become a Pure OOP Language.
  • Encapsulation dictates that all data related to an object should be inside the object, meaning that an object cannot access properties if they are outside itself.
  • Abstraction in OOP is utilizing special keywords to prevent anyone from calling certain functions from outside of the class and letting users of the class only use the methods that are public and needed to use the class.
  • Inheritance allows code to be reused by creating a parent-child relationship between two classes, where one class can access all the properties and methods of the other, enabling objects to be created from a more specific type of the parent class.
  • Java's keyword extends allows programmers to create more specific classes by inheriting properties and methods from a parent class, while adding or overriding existing methods and properties as needed.
  • The last of the four fundamental properties of OOP is Polymorphism, which enables objects to act like their parent types, as well as other types (e.g. interfaces).
  • Polymorphism allows us to avoid writing cumbersome code for our main program.
  • We can use the concept of Polymorphism to create an array of shapes, where each input from the user is an instance of the corresponding class from the array.
  • We can use polymorphism to more concisely apply a single command to multiple types of objects, such as the getArea() method, with the for loop in this example.
  • The code snippet demonstrates polymorphism, allowing an object of type A to refer to a different type B, and use the corresponding implementation of the same method when called.
  • You have learned the fundamentals of Object-Oriented Programming (OOP), and in the next lesson, you will explore more advanced concepts such as constructors, destructors, virtual methods, and abstract classes.