Getty Images

Tip

Object-oriented vs. functional programming explained

While plenty of developers entertain the idea of adopting a functional programming model, it's important to first know exactly how it differs from the object-oriented approach.

Committing to a programming paradigm is an important step in any application development effort. While they are hardly the only two options when it comes to overarching development models, the choice between functional programming and object-oriented programming is one that an increasing number of developers face today.

In this piece, we'll review the major differences between functional and object-oriented programming, offer up a few examples of how they work, and review the considerations that matter most when making the choice between these coding paradigms.

Object-oriented vs. functional programming methods

In essence, functional programs behave like common math functions, such as the calculations behind a conversion from Celsius to Fahrenheit. With functions, the same inputs consistently lead to the same result. A "pure" function is deterministic and will not produce side effects -- in other words, it will always return the same value when called and will not modify anything outside of its scope or parameters.

One example of the functional method is Google's implementation of MapReduce and its approach to returning results to certain search terms. MapReduce connects search terms in the form of key-value pairs using a function called reduce. This process aggregates the search terms and assigns each one a value that will indicate what type of result it should return. Given the same data set, Google will spit out the same answer every time without side effects.

On the other hand, object-oriented programming can contain state-dependent variables, which means objects don't necessarily retain consistent values. For example, imagine a method designed to return an average salary rate, which is $50,000. If a developer runs a method that adds 10% to that total, then asks for the salary again, the returned value is $55,000. Object-oriented programs can also contain global and static variables that will make responses different every time.

Object-oriented programming is a well-known development approach, and often underlies the structured programs most developers learn to write in the early stages of their career. Many of these languages include elements that are almost indistinguishable from functions, but they're a far cry from the mechanisms found in a purely functional programming language like Haskell.

Code examples for object-oriented vs. functional programming

The following is a code example for functional programming that features the FizzBuzz coding challenge. This is a common coding test where developers create a function that prints a series of letters and numbers based on a simple set of rules:

  1. Replace numbers divisible by three with the word Fizz.
  2. Replace numbers divisible by five with the word Buzz.
  3. Replace numbers divisible by both three and five with the word FizzBuzz.

Many functional languages can structure this logic as individual functions, as shown in the example below (written in F#).

open System

let isFizzBuzz value =
 (value % 15) = 0

let isFizz value =
 (value % 3) = 0

let isBuzz value =
 (value % 5) = 0

let output (value : int) =
 if isFizzBuzz value then
   "FizzBuzz"
 else if isFizz value then
   "Fizz"
 else if isBuzz value then
   "Buzz"
 else
   string value

[<EntryPoint>]
let main argv =
 let message =
   seq { for i in 1..100 do output i}
   |> String.concat " "
 printfn "%s" message
 printfn "Done"

However, what if we try to accomplish the same thing using an object-oriented language like C#? While the logic is similar, the big difference with the object-oriented approach is that the code transforms the logic into an object that stores the numbers we need to replace as individual variables. There are two specific advantages to this object-oriented approach. First, if the overall application encapsulates a series of logic, the objects can interact through simplified interfaces. Second, a programmer could use inheritance to create an entire series of tests similar to FizzBuzz, adding or changing logic as needed.

The example below demonstrates how to do this in C#, which creates a class and routine that dictates the replacement process.

public class Fizzer {
   private int _val;
        
   public Fizzer() {
     _val = 1;
   }
 
   public string getNewVal() {
       string answer = "";
       if (_val%5==0) answer = "Fizz";

       if (_val%3==0) answer+= "Buzz";

       if (!(_val%3==0 || _val%5==0)) answer = Convert.ToString(_val);
 
       answer+="\n";
       _val+=1;
       return answer;
   }
}

class Program {
    static void Main(string[] args) {
        Fizzer f = new Fizzer();
        for (int idx=1;idx<100;idx++){
            Console.Write(f.getNewVal());
        }
    }   
}

For a higher level of abstraction, an object could loop through from start to finish and call the method. This is useful if the programmer wants to build the program using rules from a file that might change over time.

Use cases for object-oriented vs. functional programming

Object-oriented programming covers a wide spectrum of basic application development use cases. User interface design is a particularly good fit for the object-oriented approach. The windows that appear on a user's screen are often built using buttons, text boxes and menus. These windows have state -- for example, with the text on a page in a word processor, the state changes as the user types. Once the basic layout of a window is available, other programmers can inherit and reuse that code, starting with a shell and filling in the objects.

An unfortunate downside of object-oriented programming is the risk of creating a complex code base that becomes increasingly difficult to manage over time. Even for sophisticated development shops that use the object-oriented approach, the code often resembles the example above -- a few objects scattered throughout large sections of procedural code. As such, it becomes difficult to test or debug objects that create other objects or have ties to external databases and APIs. In the event of an error, the programmer is unlikely to know the values in all the objects or exactly how to replicate them, even with a full record of the error. There are certainly some design patterns that address these issues, but industry adoption of these patterns is somewhat limited. At any rate, development shops committed to the object-oriented approach are often challenged by difficult code reviews and maintenance projects.

Functional programming, meanwhile, presents a different challenge: It can be very hard to learn and put into practice. The functional approach requires an entirely different form of thinking about code, a considerable time investment and rigorous attention to detail. For these reasons, IT leadership might see functional programming as a risk. However, functional programming is a good option when the application contains functions that build upon one other. For example, in Unix, developers will often tie together a large number of small programs, sending the results of a process listing to a specific search through a grep command (or to one page at a time through a less command).

There is also a potential "middle ground" between object-oriented and functional programming. For one, programmers can use a functional approach to create individual components or modules of a major application, even if developers originally used an object-oriented approach to design the application. Processes that compile data and produce batched results are a good candidate for this strategy, such as insurance quoting routines, product scheduling or extract, transform and load procedures. It's also sometimes possible to add functions to programs built in traditional, object-oriented languages. For instance, languages such as C# and Java now possess features that allow developers to take a functional approach within a structure that resembles object-oriented programming. One of these features includes the ability for developers to write code in F# that is interoperable with C# code through a Common Language Runtime.

Matt Heusser is managing director at Excelon Development, where he recruits, trains and conducts software testing and development. The initial lead organizer of the Great Lakes Software Excellence Conference and lead editor of "How to Reduce the Cost of Software Testing," Matt served a term on the board of directors for the Association for Software Testing.

Dig Deeper on Application development and design