Programming paradigms

A programming paradigm is a way of classifying a program or programming language based on common features. Each paradigm contains guidelines for how to structure programmes and solve problems. Some paradigms are better suited to certain project requirements than others. For this reason, it is recommended to know multiple programming paradigms so you can select the most suitable paradigm for your project.

programming-paradigms.jpeg

Object-oriented programming

Object-oriented programming (OOP) is a programming paradigm that seeks to model data using logical representations such as classes. The classes are used to create objects, which contain information (properties) and actions (methods). OOP can help organise applications by delegating processes to different classes.

Languages that can use an OOP programming paradigm include Java, Kotlin and C++. For example, the below Kotlin code demonstrates how you can define a class called Person that contains a property called name. The code then creates an object of the Person class, sets the object's name property to James, then retrieves the object's name property and prints its value to the console.

data class Person(
	val name: String
)

fun main(args: Array<String>) {
	// Create an object of the Person class
	// Set the Person object's name property to James
	val person = Person("James")
	
	// Retrieve the Person object's name property and print it to the console
	println(person.name)
}

Imperative programming

Imperative programming is a style of programming wherein the state of a program (or application) is changed through explicit commands; the instructions for completing tasks are detailed and ordered. For example, imagine we had a list of Person objects, as defined in the object-oriented-programming section, and our task was to find all the persons with a name beginning with the letter G. In adopting an imperative programming approach to this task, we would provide clear and detailed instructions for how to implement each step. For instance, we could create an empty mutable list, iterate through each element in the list of Person objects, assess whether each Person object has a name property beginning with G, and add any matching Person objects to the new list we created in the first step.

data class Person(
	val name: String
)

fun main(args: Array<String>) {
	// Define the source list of Person objects
	val listOfPersons = listOf(Person("James"), Person("Georgina"), Person("Phil"), Person("Gary"), Person("Sophie"))
	
	// Create an empty list to store the filtered Person objects
	val listOfPersonsWithNamesBeginningWithG = mutableListOf<Person>()
	
	// Iterate through the list of Person objects
	for (person in listOfPersons) {
		// Assess whether the Person object has a name value beginning with G
		if (person.name.startsWith("G")) {
			// Add any Person objects with name values beginning with G to 
			// the listOfPersonsWithNamesBeginningWithG list
			listOfPersonsWithNamesBeginningWithG.add(person)
		}
	}
	
	// Will print Georgina and Gary
	for (person in listOfPersonsWithNamesBeginningWithG) println(person.name)
}

Procedural programming

Procedural programming is derived from imperative programming: both paradigms involve defining an ordered list of instructions. Procedural programming goes further than imperative programming with regards to how the application code should be organised. Namely, procedural programming advises splitting the application tasks into procedures/functions, where each function tackles a subtask of the broader programme. For example, if we were to convert the coding example from the imperative programming to a procedural programming style, then we could write code like the following:

data class Person(
	val name: String
)

fun main(args: Array<String>) {
	// Define the source list of Person objects
	val listOfPersons = getListOfPersons()
	
	// Get all the persons with names beginning with G
	val listOfPersonsWithNamesBeginningWithG = getPersonsWithNamesBeginningWithG(listOfPersons)
	
	// Will print Georgina and Gary
	printPersonNames(listOfPersonsWithNamesBeginningWithG)
}

fun getListOfPersons(): List<Person> {
	return listOf(Person("James"), Person("Georgina"), Person("Phil"), Person("Gary"), Person("Sophie"))
}

fun getPersonsWithNamesBeginningWithG(listOfPersons: List<Person>): List<Person> {
	// Create an empty list to store the filtered Person objects
	val listOfPersonsWithNamesBeginningWithG = mutableListOf<Person>()
	
	// Iterate through the list of Person objects
	for (person in listOfPersons) {
		// Assess whether the Person object has a name value beginning with G
		if (person.name.startsWith("G")) {
			// Add any Person objects with name values beginning with G to 
			// the listOfPersonsWithNamesBeginningWithG list
			listOfPersonsWithNamesBeginningWithG.add(person)
		}
	}
	
	return listOfPersonsWithNamesBeginningWithG
}

fun printPersonNames(listOfPersons: List<Person>) {
	for (person in listOfPersons) println(person.name)
}

Functional programming

Functional programming revolves around data transformation, often via functions. Unlike object-oriented programming, functional programming does not concern itself with global mutable states, which describe the broader application system and its components. For example, while classes and objects can perform differently based on how they are constructed and the runtime environment, functional programming focuses on pure functions, which depend solely on the information that is supplied to the function. If the supplied information remains the same, then the function's output will remain unchanged also, regardless of what else may be happening in the runtime environment.

Languages that can use a functional programming paradigm include Kotlin and JavaScript. For example, the below Kotlin code defines a function called joinFirstAndLastNames. The joinFirstAndLastNames accepts two String arguments called firstName and lastName, respectively. In the body of the function, the code joins both strings together, separates them by a space, and then returns the completed name as a single String. The joinFirstAndLastNames method is unaffected by the broader application state, and will always return the same result, providing the input values remain unchanged. For example, supplying the arguments "James" and "Smith" will always prompt the function to return "James Smith".

fun joinFirstAndLastNames(firstName: String, lastName: String): String {
	return "$firstName $lastName"
}

Reactive programming

Reactive programming is characterised by asynchronous (non-blocking) data streams. These data streams can be observed, transformed and responded to. Reactive programming can be incorporated into many programming languages. For example, Java and Kotlin feature dedicated libraries called RxJava and RxKotlin, respectively, which facilitate reactive programming.

The below code demonstrates how to use the reactive programming library RxKotlin to process a stream of numbers. The list of numbers is converted to an Observable object. A subscriber is then registered to the Observable, which processes each element in the stream of data. Each element in the data stream is multiplied by 2 and then printed to the console. Once all elements in the data stream have been processed, a message of "Done!" is printed to the console. Altogether, the below code uses reactive programming principles to interact with each element in a stream of data asynchronously without blocking other application processes.

val observableNumbers = listOf(1, 2, 3, 4).toObservable()

// Process the stream of numbers and print each number multiplied by 2
observableNumbers.subscribeBy(
	onNext = {
		println(it * 2)
	}, onError = {
		error -> error.printStackTrace()
	}, onComplete = {
		println("Done!")
	}
)

Declarative programming

Declarative programming is a contrasting approach to imperative programming. While imperative programming provides detailed instructions on how to complete a task, declarative programming focuses on what the outcome of a task should be but does not specify how that outcome should be achieved.

Many coding languages support declarative programming. For example, JavaScript, Java and Kotlin allow you to interact with lists and arrays using methods such as filter, map, reduce and sort. These methods state what action should be performed on the list/array but do not specify how that action will be achieved. Other coding languages such as HTML are more heavily based on declarative programming. For instance, the below HTML code uses an img element to state that an image file called profile-image.jpg should be displayed, but does not provide instructions for the actual rendering of the image:

<img src="profile-picture.jpg" />