Instruction

Programming paradigms help determine how data is organized and manipulated, as well as the structure and flow of programs. Different paradigms are used based on the nature and complexity of the project. We have procedural, object-oriented, and also functional programming. In this module, you’ll focus on procedural and object-oriented programming, as functional programming features are already baked into Kotlin.

Learning About Procedural Programming

Procedural programs are written as step-by-step instructions to solve problems. These sets of instructions are called procedures, which are analogous to functions, hence the name procedural programming. At the core, you use variables, functions, loops, and conditional statements to drive the execution of the programs. In this approach, data and functions are regarded as separate entities. So, a variable that holds some data can exist independently, and a function can also exist independently.

One problem with the procedural paradigm is limited code reusability, and this is because functions are designed to work with specific data structures. To make a function that does similar task work on other data structures, the function code would have to be duplicated and updated.

This led to the creation of Object-Oriented Programming to solve issues like code reusability that came with procedural languages.

Learning About Object-Oriented Programming

The Object-Oriented Programming paradigm’s core is based on programs that are organized and driven by instances of classes called objects. They contain their own data and functions, and they interact with each other. So, instead of having one large sequence of code or functions scattered all over our code, the programs are broken into smaller chunks that hold related data and functions. You define these chunks in something called classes.

With this, both data and behavior are encapsulated within objects which in turn allows code to be modular and resuable. So, if your code breaks, you can more easily find bugs because you know exactly how and where data can be changed. This is better when compared to going over thousands of lines of code if the code was written using the procedural approach.

In the demo, you’ll take a quick look at the difference between procedural programming and object-oriented programming. But before you do that, take a moment to gain some foundational knowledge about classes.

Creating Objects

Object-oriented programming organizes app development around objects. Most apps manage objects like users and the items they use or create in the app. For example, notes, TODOs, comments, items to buy or sell, images or videos, and social connections like followers or followed people etc.

Here’s how you create objects:

  • Classes are blueprints for creating objects. They can encapsulate properties (data) and functions (behaviors). Functions contained inside a class are usually called methods. A class is where you define the structure of the object you intend to create.
  • An object is an instance of a class. Each instance has actual values for its properties and can call its methods. So, if you have a Car class, you can create a toyota object and call its members toyota.color or toyota.drive(). Do note that you can create multiple objects from a class and each of them can have their unique states. For example, the value of toyota.color could be different from honda.color.

Storing Classes and Objects in Memory

A class is a reference type, so an instance of a class actually contains a reference, that is, the location in memory where its data is stored. Kotlin stores the reference to the object in memory in something called a stack, while you store the actual object in the heap. So, if you call an instance of a class in your code, the program knows the location of its data and can change the values. With this, you can see that Kotlin stores all variables in memory twice, that is, both in the stack and heap, so accessing variables behind the scenes is a two-step process.

Take a look at the diagram below:

Memory Stack Heap Car toyota (Reference) Corolla, 2017, blue
Stack and Heap Memory 1

You can see that the toyota variable just points to the address of the location of the Car object in memory. These two steps are advantageous because the actual object could hold large data, so if we pass the instance multiple times in our code, it won’t be copying the large object. Instead, it would just be referencing it. This approach is more efficient because accessing the heap is slower than accessing the stack.

Also, since classes are reference types, if we assign the value of toyota to honda like so:

var toyota: Car = Car(model = "corolla", year = 2017 color = "blue")
var honda: Car = Car(model = "civic", year = 2010, color = "red")

honda = toyota

Then, the memory representation would look something like this:

Memory Stack Heap Car toyota (Reference) Car honda (Reference) Corolla, 2017, blue Civic, 2010, red
Stack and Heap in Memory 2

This is because, if you remember, the variables point to the object in the heap. So, a reassignment would mean rerouting the pointer of honda to toyota, which points to a toyota object in the heap.

Note: This concept also applies to other data types in Kotlin because everything is an object in Kotlin.

Principles of OOP

Four fundamental principles underlie OOP. Here’s what they are and how they can benefit your development process:

  • Encapsulation: A class bundles an object’s properties and methods in one place. It then gives access to selected data externally via its public methods. Access to only public members of the object helps provide security and control over the data state changes and reduces the risk of bugs in your code.

  • Abstraction: When working with large programs, there are so many details that go into making the program work but aren’t necessary for the programmer to understand exactly how they work. For example, the println() function prints a message passed to it in the console. We don’t know how it works internally, so the inner implementation details of that feature have been hidden from the outer world. In OOP, you can use Abstraction to define things in simple terms. For example, the println() prints to the console while the setContent() function in Jetpack Compose sets the main composable for the screen. Abstraction can also be seen as an extension of encapsulation in that inner details are also hidden.

  • Inheritance: You can create subclasses of a class to share properties and methods. In a subclass, you can add properties or methods or customize the parent’s methods. Kotlin doesn’t allow multiple inheritance: A class can inherit from at most one other class.

  • Polymorphism: Polymorphism means existing in many forms. In OOP, it simply means creating objects with shared behaviors. So you can have the same method for parent and subclasses called, but the behavior of the execution is different depending on the object that calls it. You could say that method is polymorphic because it exists in many forms.

You’ll explore the first two principles in the demo and the last two in a later lesson.

Benefits of OOP

So, what are the advantages of OOP? Some reasons why OOP is a powerful tool in app development include:

  • Modularity: Encapsulation and Abstraction make it easier to debug your app. All of an object’s information is in one place, and you know exactly how this information can change. It’s also easier to distribute work among team members, with each developer “owning” one or more object types.

  • Problem-solving: OOP’s modularity enables you to divide your app’s complexity into smaller problems that are easier to solve.

  • Reusability: Inheritance lets you reuse code. If you need to add properties or modify methods, you do it in only one place and have all the subclasses inherit the change. Same thing with Abstraction.

  • Productivity & efficiency: Reusable code, along with easier debugging, collaboration and problem-solving, makes you a more productive and efficient developer.

Now, I’ll introduce a concept known as interfaces in Kotlin, which is a powerful tool for making your code flexible and reusable without relying on traditional inheritance.

Using Interfaces

To model your data with inheritance, you must use classes. However, you can’t have multiple inheritance. Now, how do you create reusable code without inheritance?

The answer is interfaces.

An interface defines a functionality that a class can conform to. It’s more like a contract a class must abide by, and there’s no limit to how many interfaces a class can adopt.

Note: Interfaces can’t hold data, they are simply a contract. You create classes to hold data, and they, in turn, abide by the interfaces.

You’ll learn about interfaces in a later lesson.

See forum comments
Download course materials from Github
Previous: Introduction Next: Demo