Polymorphism

  • Last updated on 2nd Sep 2023

Introduction

Polymorphism is an Object-Oriented Programming (OOP) pattern, where the most important idea is objects of different types being treated as a common of a superclass or interface. This means that a system can interact with objects of various types through a single point of reference, which makes our code design flexible, and extensible.

Overview

Ultimately, it’s usage is up to preference, but generally, we use it when we have a set of classes with related functionality but variations in behavior. Or when we want an extensible design, but it’s closed for modification.

As with any solution, there are cases where it isn’t the best at what it’s trying to solve. Such as when performance is critical and you know exactly what type of object you’re dealing with. Then in that case, using direct method calls might be better. But for most cases, the readability and maintainability benefits outweigh this minor unnecessary overhead performance hit.

Another case to be cautious of would be using the design pattern to prematurely optimize by forcing polymorphism if there’s minimal variation or when your code is simple. This would have the opposite effect; makes the code less readable.

Javascript

"use strict";

// Abstract base class for Search
class Search {
  constructor(searchTerm) {
    this.searchTerm = searchTerm.replace(/\s/g, "%20"); // Encode spaces 
  }

  async performSearch() {
    throw new Error("This method must be implemented by subclasses");
  }

  storeResult(result) {
    throw new Error("This method must be implemented by subclasses");
  }
}

// Abstract base class for SearchResult
class SearchResult {
  constructor(data) {
    this.data = data;
  }

  getInfo() {
    throw new Error("This method must be implemented by subclasses");
  }
}

// Concrete class for AuthorResult
class AuthorResult extends SearchResult {
  constructor(data) {
    super(data);
  }

  getInfo() {
    return {
      name: this.data.name,
      key: this.data.key
    };
  }
}

// Concrete class for Author Search
class AuthorSearch extends Search {
  constructor(searchTerm) {
    super(searchTerm);
    this.collectedAuthors = [];
  }

  async performSearch() {
    const response = await fetch(`https://openlibrary.org/search/authors.json?q=${this.searchTerm}`);
    const data = await response.json();
    return data.docs.map(doc => new AuthorResult(doc));
  }

  storeResult(authorResult) {
    this.collectedAuthors.push(authorResult);
  }

  viewCollectedAuthors() {
    if (this.collectedAuthors.length === 0) {
      console.log("No authors have been collected yet.");
      return;
    }

    console.log("Collected Authors:");
    for (let i = 0; i < this.collectedAuthors.length; i++) {
      const authorInfo = this.collectedAuthors[i].getInfo();
      console.log(`  ${i + 1}. ${authorInfo.name}`);
    }
  }
}

async function main() {
  const authorSearch = new AuthorSearch("");

  while (true) {
    let searchTerm;
    do {
      searchTerm = prompt("Enter author name to search (at least 3 letters) (or 'view' to see collected, 'exit' to quit):");
      if (searchTerm.length < 3 && searchTerm !== "view" && searchTerm !== "exit") {
        console.log("Please enter a search term with at least 3 characters.");
      }
    } while (searchTerm.length < 3 && searchTerm !== "view" && searchTerm !== "exit");

    if (searchTerm === "exit") {
      break;
    } else if (searchTerm === "view") {
      authorSearch.viewCollectedAuthors();
      return; // Exit the program after viewing collected authors
    }

    authorSearch.searchTerm = searchTerm;
    const results = await authorSearch.performSearch();

    if (results.length > 0) {
      const authorResult = results[0]; // Assuming first result is the desired one
      const authorInfo = authorResult.getInfo();
      console.log(`Author Information:`);
      console.log(`  Name: ${authorInfo.name}`);
      console.log(`  Key: ${authorInfo.key}`);

      const store = confirm("Would you like to store this author's information?");
      if (store) {
        authorSearch.storeResult(authorResult);
        console.log("Author information stored successfully!");
      }
    } else {
      console.log("No author found with that name.");
    }
  }

  console.log("Search completed.");
}

main();

Above, we’re utilizing inheritance and polymorphism to create a flexible search system for authors using the Open Library API. It begins with defining abstract base classes Search and SearchResult. These set the structure for the search operations and search results.

The concrete classes AuthorSearch and AuthorResult inherit from these base classes, enabling specific author searches and handling of author search results. The AuthorSearch class overrides the performSearch() method to query the Open Library API for author data, while AuthorResult overrides the getInfo() method to extract relevant information.

Above, Polymorphism is breaking the inheritance in a class by overriding certain methods. Polymorphism is seen in the dynamic invocation of performSearch() and getInfo() depending on the context of author search and the result retrieval.

The main function main() orchestrates the search process, prompting the user for author names, displaying results, and storing selected author information using the implemented polymorphic methods.

PHP

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Find Your Favorite Authors</title>
</head>
<body>
  <h1>Find Authors</h1>
  <form action="" method="get">
    <input type="text" name="search" id="searchInput" placeholder="Search Authors (min 3 characters)">
    <button type="submit">Search</button>
  </form>

  <div id="searchResults"></div>

  <?php
  // Define an interface for polymorphism
  interface Displayable {
    public function display();
  }

  // Class representing an Author
  class Author implements Displayable {
    public $name;

    function __construct($name) {
      $this->name = $name;
    }

    public function display() {
      echo "<li>" . $this->name . "</li>";
    }
  }

  // Class representing Search functionality
  class Search implements Displayable {
    public $results;

    function __construct($searchResults) {
      $this->results = $searchResults->docs;
    }

    public function display() {
      if (count($this->results) > 0) {
        echo "<h2>Search Results</h2><ul>";
        foreach ($this->results as $result) {
          $author = new Author($result->name);
          $author->display();
        }
        echo "</ul>";
      } else {
        echo "No authors found for your search.";
      }
    }
  }

  // Check if search parameter exists and is at least 3 characters long
  if (isset($_GET['search']) && strlen($_GET['search']) >= 3) {
    $searchQuery = urlencode(str_replace(" ", "%20", $_GET['search'])); // Encode search and spaces
    $searchResults = json_decode(file_get_contents("https://openlibrary.org/search/authors.json?q=" . $searchQuery));

    // Create a Search object and display results
    $search = new Search($searchResults);
    $search->display();
  }
  ?>
</html>

Above, similar to the previous examples, we’re implementing inheritance and polymorphism to display the search results of authors from the Open Library database. The example is a bit brief because the page would reload whenever an HTTP request is sent or an action is triggered; both actions reload the browser page, and I didn’t want to utilize JavaScript to prevent the page from reloading. I wanted the example to be standalone PHP and HTML.

Anyway, there’s an interface named Displayable with a method display(), ensuring that any class implementing this interface provides a display functionality. The Author class implements this interface to display individual author names. Another class, Search, also implements Displayable and is responsible for displaying search results.

Then, in the Search class, it checks if search results exist and then iterates through each result, creating an Author object for each and invoking its display() method.

Python

import requests

class Person:
  def __init__(self, name, key):
    self.name = name
    self.key = key

  def __str__(self):
    return f"Name: {self.name} (Key: {self.key})"

class Author(Person):
  def __init__(self, name, key):
    super().__init__(name, key)
    self.published_works = []  # List to store published works (future implementation)

class AuthorSearch:
  def __init__(self):
    self.collected_people = []  # Changed to collect People (base class)
    self.base_url = "https://openlibrary.org/search/authors.json?q="

  def search_people(self):  # Changed to search_people for broader use
    while True:
      search_term = input("Enter name (at least 3 characters): ")
      if len(search_term) < 3:
        print("Search term must be at least 3 characters long.")
        continue

      encoded_term = search_term.replace(" ", "%20")
      url = self.base_url + encoded_term

      response = requests.get(url)
      data = response.json()

      if data["numFound"] == 0:
        print("No people found for that search term.")
      else:
        self.display_people(data["docs"])
        break

  def display_people(self, people):  # Changed to display_people for broader use
    print("\nSearch Results:")
    for i, person in enumerate(people):
      print(f"{i+1}. {person['name']}")

    choice = input("\nSave person (enter number or 'n'): ")
    if choice.isdigit():
      index = int(choice) - 1
      if 0 <= index < len(people):
        # Create Author object only if person type is author (polymorphism)
        if people[index]["type"] == "author":
          collected_person = Author(people[index]["name"], people[index]["key"])
          self.collected_people.append(collected_person)
          print(f"Person '{collected_person.name}' added to collection.")
        else:
          print(f"Selected person is not an author (type: {people[index]['type']})")

  def view_collection(self):
    if not self.collected_people:
      print("No people saved in collection yet.")
      return

    print("\nCollected People:")
    for person in self.collected_people:
      print(person)

if __name__ == "__main__":
  search = AuthorSearch()
  search.search_people()

  while True:
    action = input("\nActions (s: search, v: view collection, q: quit): ")
    if action.lower() == "s":
      search.search_people()
    elif action.lower() == "v":
      search.view_collection()
    elif action.lower() == "q":
      break
    else:
      print("Invalid action. Please enter 's', 'v', or 'q'.")

Above, we’re defining three classes; Person, Author, and AuthorSearch. The Person class serves as the base class with attributes name and key, while Author inherits from Person, adding a published_works list attribute for future implementation. The AuthorSearch class utilizes polymorphism by accepting both Person and Author instances in its collected_people list.

In the search_people method, it searches for people based on user input, displaying the results and allowing the user to save selected authors to the collection. If the selected person is identified as an author, a corresponding Author object is created and added to the collection.

Just like our previous example, this demonstrates polymorphism as the Author class is used in place of the Person class when appropriate. Moreover, the view_collection method iterates over the collected people, displaying their details, showcasing inheritance with Person and Author instances treated uniformly.

As always, I hope you have a great day!

Resources