Object oriented programming refers to a type of programming language. Object oriented programming, or OOP allows programmers to store data into “objects”.
Objects
Objects are defined as a package that contain both data and procedures.
Anything under a object is usually referred to as a member.
The data of an object is usually referred to as an attribute, but can also be referred to as a property or field.
The procedures/functions of an object is referred to as a method.
Example
public class myObject{ // Object's data, also known as a property or attribute or field. int x = 5; // A procedure for the object, also known as a method. static void myMethod(){ System.out.println("I just got executed!") }}myObject createdObject = new myObject() // creates an instance of the object.System.out.println(createdObject.x) // prints value of property x of the object, 5createdObject.myMethod() // runs the myMethod() method defined in the object.// output:// 5// I just got executed!
OOP is built upon 4 principles: Encapsulation, Abstraction, Polymorphism and Inheritance.
Encapsulation
Encapsulation refers to containing data and encapsulating it into separate objects/classes which can be called and accessed through accessor methods. This is, for example, having functions within a class that returns a private variable within that same class. This is to ensure that code isn’t jumbled and is easy to access.
using System;public class Info{ private string first_name = "first name"; //private variable private string last_name = "last name"; public string retrieveFirstName(){ //public callable variable to retrieve return first_name; } public string retrieveLastName(){ return last_name; } // The functions above defined in this class are known as 'methods' - a core // part of reusable code, encapsulation and abstraction.}class Program{ static void Main(string[] args){ Info info = new Info(); Console.WriteLine(info.retrieveFirstName()); // prints "first name" Console.WriteLine(info.retrieveLastName()); // prints "last name" }}
Abstraction
Abstraction refers to the fact that you do not need to know how some processes are done, just know that the which processes are done. We only need to be aware of a class/object/functions’ intent and not its process.
That way, development time shouldn’t be spent on trying to figure out what code does - it should be clear and easy to modify, maintain and especially used.
In simpler words - abstraction ‘simplifies’ the code by taking similar implementations of code, extracting those instances and creates an ‘abstraction’ where all implementations of that code can be easily managed and reused. Those prior ‘implementations’ doesn’t need the implementation, it just needs to give an input to something, and receive an output; simplifying the process.
Example
Think of a TV remote. You as the user uses the remote by interacting with the buttons. You don’t need to know how the inside works (circuitry, power, etc.), you just need to know what inputs do what.
The same concept is used in code.
Imagine you have a remote control class. One uses Bluetooth, the other uses IR.
class BluetoothRemoteControl{// ... Bluetooth communication logic ... void powerOn() { /* code for turning on */ } void changeChannel(int channel) { /* code for changing channels */ } void adjustVolume(int volume) { /* code for adjusting volume*/}class IRRemoteControl{ // ... Infrared signal transmission logic ... void powerOn() { } void changeChannel(int channel) { } void adjustVolume(int volume) { }}
They are separate classes, but you realize that they all share similar features. So to simplify, you create a “umbrella” class for all these common methods:
abstract class RemoteControl { abstract void powerOn(); abstract void changeChannel(int channel); abstract void adjustVolume(int volume);}class BluetoothRemoteControl extends RemoteControl{// ... Bluetooth communication logic ... void powerOn() { /* code for turning on */ } void changeChannel(int channel) { /* code for changing channels */ } void adjustVolume(int volume) { /* code for adjusting volume*/ }}class IRRemoteControl extends RemoteControl{ // ... Infrared signal transmission logic ... void powerOn() { } void changeChannel(int channel) { } void adjustVolume(int volume) { }}
By doing so, you have now ‘abstracted’ the core features required for a remote control. This helps with managing those core features by creating a common ‘pattern’ for subclasses in the future (in this case, new remotes) by using inheritance and polymorphism to manipulate its differences.
However, this can cause issues with coupling, which is the interdependence between implementations and objects which will result in more boilerplate code, too much responsibility for certain objects - which will again cause more complexity, more work in maintenance and cascading errors.
Objects are able to have relationships with each other. It’s an object (the child)‘s ability to acquire code and data from another object (the parent). Inheritance promotes code reusability, allowing child classes to ‘share’ code from its parent.
using System;public class Parent{ public int sum(int x, int y){ return(x + y); }}public class Child : Parent{ //the colon operator means inherit. public int subtract(int x, int y){ return(x - y); }}public class Program{ static void Main(string[] args){ Child childObject = new Child(); Console.WriteLine(childObject.sum(1, 1)); // even though sum isn't defined as a method in the child class, // it is inherited from its parent class. Console.WriteLine(childObject.subtract(4, 3)); // It still maintains any new method added to it, allowing flexibility. }}
Composition over inheritance
Prefer composition over inheritance. The problem with inheritance is that with each new addition you make in a parent class, the subclass will inherit everything.
This means that once you get to a point where a inherited member cannot be used or isn’t necessary, you’re forced to create implementations for those members even if it doesn’t work. Yet, if you take it out of the parent class, then it just breaks all other subclasses that actually use those members.
Let’s say you’ve:
created something new.
Well, now you have to go through all your old subclasses and check that it works properly for those subclasses. If not, you have to refactor.
deleted a member.
Well, now you have to go through all your old subclasses and check if that subclass needs it. If so, you have to refactor.
edited a member.
Well, now you have to go through all your old subclasses and check that it works properly for all subclasses. If not, you have to refactor
With every inherit, you’ve coupled the subclass to the parent class. Its now dependent on the parent class and with every change of the parent class, you risk expensive refactoring.
This is where composition comes in.
Instead of relying on inheritance (a parent class with the required method) for reusable code, you can just write a function with dependency injection that can be used in those classes optionally. If your subclass needs to use a function? Just call it in the subclass. If not? Then don’t call it. Simple as that.
This is known as the “is a” relationship or the “has a” relationship respectively:
Inheritance
A bird “is an” animal. All features in animal is inherited by the bird.
Composition
A bird “has an” ability to fly. So composition should be used to ensure that other animals who shouldn’t be able to fly aren’t inherited alongside the animal class.
Composition
In this example, animals Bird and Dog both share the ability to eat() as defined in the parent class and they share Tail. Dog cannot fly though, so it just doesn’t inherit/use the code at all, ensuring a distinct separation (no coupling) between them. This reduces the work needed to refactor.
public class main{ public static void main(String[] args){ BirdWings pidgeon_wings = new BirdWings(); Bird pidgeon = new Bird(pidgeon_wings); pidgeon.fly(); /* Outputs "Flying..." */ Dog minature_schanuzer = new Dog(); minature_schanuzer.wag(); /* Outputs "Dog is wagging tail..." */ pidgeon.tail.fan(); /* Outputs "Bird is fanning tail..." */ }}public class Animal{ /* All animals inherit these traits */ public void eat(){/* code for eating */};}public class Bird extends Animal{ private Wings wings; public BirdTail tail = new BirdTail(); public Bird(Wings wings){ this.wings = wings; } public void fly(){ /* Bird has the local ability to fly */ wings.fly(); }}interface Wings{ public void fly();}class BirdWings implements Wings{/* Bird wings are obviously on for birds */ public void fly(){ System.out.println("Flying..."); }}public class Dog extends Animal{ private DogTail tail = new DogTail(); public void wag(){ tail.wag(); }}interface Tails{ public void wag();}public class DogTail implements Tails{ public void wag(){ System.out.println("Dog is wagging tail..."); }}public class BirdTail implements Tails{ public void wag(){ System.out.println("Bird is wagging tail..."); } public void fan(){ System.out.println("Bird is fanning tail..."); }}
Inheritance
In this example, every single animal defined inherits the ability to fly(), wag(), even if they don’t do those things, as well as the fact that each method must be manually implemented as well.
This causes a lot of repetition, coupling and any change to the parent class Animal, such as adding a new method like bark() will force you change each subclass - or write it as a local method of that subclass, reducing code reusability.
public abstract class Animal{ /* All animals inherit these traits */ public void eat(); public void fly(); /* Not relevant to all animals */ public void wag(); /* Not relevant to all animals */ public void fan(); /* Not relevant to all animals */}public class Bird extends Animal{ @override public void fly(){ System.out.println("Flying...") } @override public void wag(){ System.out.println("Bird wagging tail..."); } @override public void fan(){ System.out.println("bird fanning tail..."); }}public class Dog extends Animal{ @override public void fly(){ throw new Exception("Dog cannot fly!"); /* Forced to inherit, yet impossible */ } @override public void wag(){ System.out.println("Dog wagging tail..."); } @override public void fan(){ System.out.println("Dog fanning tail...") }}public class Cat extends Animal{ @override public void fly(){ throw new Exception("Cat cannot fly!"); } @override public void wag(){ System.out.println("Cat wagging tail..."); } @override public void fan(){ System.out.println("Cat fanning tail..."); }}
Polymorphism means one name, many forms. It refers to an object’s ability to take more than a single form, or the ability to perform a function in different ways. Polymorphism is applied in 2 different ways: method overriding and method overloading.
Method overloading
Method overloading refers to having multiple methods with the same name, but with different parameters - therefore resulting in a different output and demonstrating polymorphism.
Languages
Vanilla Python doesn’t support method overloading! Use multiple dispatch decorator to handle it.
using System;class Program{ public class Sum{ // All methods here have the same name, but they all have different parameters. public int sum(int x, int y){ return (x + y); } public int sum(int x, int y, int z){ return (x + y + z); } public double sum(double x, double y){ return (x + y); } } static void Main(string[] args) { Sum sumObject = new Sum(); Console.WriteLine(sumObject.sum(1, 2)); Console.WriteLine(sumObject.sum(1, 2, 3)); // uses the appropriate method. }}/****Output**36**/
Method overriding
Method overriding refers to another method of polymorphism. However, this only works in a subclass or a sister class with the same method as the parent class. However, the 2 must have the same parameter list and same return type.
public class Parent{ public void add(){ int sum = 1+1; System.out.println(sum); }}class Child extends Parent{ // extends is the keyword to create a subclass. @Override public void add(){ int sum = 2+2; System.out.println(sum); }}class Main{ public static void main(String[] args){ Parent parent = new Parent(); Child child = new Child(); parent.add(); child.add(); }}/****Output**24**/