Liskov Substitution Principle via Content Management System

Liskov Substitution Principle via Content Management System

Learn how the Liskov Substitution Principle enables flexible content management systems with clean, extensible code.

When I was asked to explain the Liskov Substitution Principle (LSP) to someone building a website for articles, news, and poems, I saw a perfect opportunity to ground this abstract OOP concept in a practical, real-world example. LSP, part of the SOLID principles, ensures that subclasses can stand in for their base classes without breaking the system. Let’s walk through how this applies to a Java-based content management system (CMS) I designed a website, complete with full code examples.

The Setup

Imagine a website where you manage different types of content: articles with detailed bodies, news with timely headlines, and poems with lyrical flair. In object-oriented programming, a natural starting point is an abstract Content class that defines the common behavior all content types must share. Here’s the base class:

import java.util.Map;

public abstract class Content {
    protected String title;
    protected String author;

    public Content(String title, String author) {
        this.title = title;
        this.author = author;
    }

    // Abstract method to display content
    public abstract void display();

    // Returns estimated reading time in minutes
    public abstract int getReadingTime();

    // Returns metadata as a key-value map (e.g., tags, category)
    public abstract Map<String, String> getMetadata();

    // Returns a URL-friendly slug (e.g., "my-first-article")
    public abstract String getURLSlug();

    // Common getters
    public String getTitle() {
        return title;
    }

    public String getAuthor() {
        return author;
    }
}

This class sets up a contract: every piece of content must have a title, author, and methods to display itself, estimate reading time, provide metadata, and generate a URL slug. Now, let’s implement this for Article, News, and Poem.

Subclass: Article

An article is a longer piece with a body and word count. Here’s the full implementation:

import java.util.HashMap;
import java.util.Map;

public class Article extends Content {
    private String body;
    private int wordCount;

    public Article(String title, String author, String body, int wordCount) {
        super(title, author);
        this.body = body;
        this.wordCount = wordCount;
    }

    @Override
    public void display() {
        System.out.println("Article: " + title + " by " + author);
        System.out.println(body);
    }

    @Override
    public int getReadingTime() {
        // Assume 200 words per minute
        return (int) Math.ceil(wordCount / 200.0);
    }

    @Override
    public Map<String, String> getMetadata() {
        Map<String, String> metadata = new HashMap<>();
        metadata.put("type", "article");
        metadata.put("category", "blog");
        return metadata;
    }

    @Override
    public String getURLSlug() {
        return title.toLowerCase().replaceAll("[^a-z0-9]+", "-");
    }
}

Subclass: News

News items are time-sensitive, with a headline, body, and publication date:

import java.util.HashMap;
import java.util.Map;

public class News extends Content {
    private String headline;
    private String body;
    private String publicationDate;

    public News(String title, String author, String headline, String body, String publicationDate) {
        super(title, author);
        this.headline = headline;
        this.body = body;
        this.publicationDate = publicationDate;
    }

    @Override
    public void display() {
        System.out.println("News: " + headline);
        System.out.println("Published on: " + publicationDate + " by " + author);
        System.out.println(body);
    }

    @Override
    public int getReadingTime() {
        // News is shorter, estimate based on body length
        return (int) Math.ceil(body.split("\\s+").length / 200.0);
    }

    @Override
    public Map<String, String> getMetadata() {
        Map<String, String> metadata = new HashMap<>();
        metadata.put("type", "news");
        metadata.put("date", publicationDate);
        return metadata;
    }

    @Override
    public String getURLSlug() {
        return (title + "-" + publicationDate).toLowerCase().replaceAll("[^a-z0-9]+", "-");
    }
}

Subclass: Poem

Poems have lines and a stylistic flair:

import java.util.HashMap;
import java.util.Map;

public class Poem extends Content {
    private String[] lines;
    private String style; // e.g., "sonnet", "free verse"
    // Check the third song on the album of the day at the bottom of the page

    public Poem(String title, String author, String[] lines, String style) {
        super(title, author);
        this.lines = lines;
        this.style = style;
    }

    @Override
    public void display() {
        System.out.println("Poem: " + title + " by " + author + " (" + style + ")");
        for (String line : lines) {
            System.out.println(line);
        }
    }

    @Override
    public int getReadingTime() {
        // Poems are read slower, (with emotions!) estimate 10 lines per minute
        return (int) Math.ceil(lines.length / 10.0);
    }

    @Override
    public Map<String, String> getMetadata() {
        Map<String, String> metadata = new HashMap<>();
        metadata.put("type", "poem");
        metadata.put("style", style);
        return metadata;
    }

    @Override
    public String getURLSlug() {
        return ("poem-" + title).toLowerCase().replaceAll("[^a-z0-9]+", "-");
    }
}

LSP in Play

Here’s how we’d use this in a website’s main logic:

import java.util.ArrayList;
import java.util.List;

public class Website {
    public static void main(String[] args) {
        List<Content> contents = new ArrayList<>();
        contents.add(new Article("My First Article", "Jane Doe", "This is the body of my article...", 500));
        contents.add(new News("Breaking News", "John Smith", "Major Event!", "Details here...", "2025-03-09"));
        contents.add(new Poem("The Road", "Emily Poet", new String[]{"Not all who wander", "Are lost", "But I might be"}, "free verse"));

        for (Content content : contents) {
            System.out.println("Title: " + content.getTitle());
            System.out.println("Reading Time: " + content.getReadingTime() + " mins");
            System.out.println("URL: /" + content.getURLSlug());
            System.out.println("Metadata: " + content.getMetadata());
            content.display();
            System.out.println("---");
        }
    }
}

Run this, and you’ll see output like:

Title: My First Article
Reading Time: 3 mins
URL: /my-first-article
Metadata: {type=article, category=blog}
Article: My First Article by Jane Doe
This is the body of my article...
---
Title: Breaking News
Reading Time: 1 mins
URL: /breaking-news-2025-03-09
Metadata: {type=news, date=2025-03-09}
News: Major Event!
Published on: 2025-03-09 by John Smith
Details here...
---
Title: The Road
Reading Time: 1 mins
URL: /poem-the-road
Metadata: {type=poem, style=free verse}
Poem: The Road by Emily Poet (free verse)
Not all who wander
Are lost
But I might be
---

This works seamlessly because each subclass adheres to the Content contract. LSP guarantees that swapping an Article for a Poem or News doesn’t crash the site—flexibility at its finest.

Real-World Methods

To make this CMS practical:

  • getReadingTime: Tailored to each type (word count for articles, line count for poems), enhancing user experience.
  • getMetadata: Provides SEO-friendly data or filtering options.
  • getURLSlug: Generates clean, unique URLs for routing.

A Potential Pitfall

What if News added a method like setBreakingNewsStatus(boolean isBreaking)? If our loop tried:

for (Content content : contents) {
    content.setBreakingNewsStatus(true); // Error!
    content.display();
}

It’d fail for Article and Poem—they don’t have that method. That’s an LSP violation. The base class contract must be universal. A fix? Use an interface like UrgentContent for news-specific features, keeping Content lean.

Why It Matters

For my website, LSP means they can later add a Review or Essay class without rewriting the core logic. The system is robust and extensible—ideal for a growing site. Whether it’s calculating reading times for UX, generating slugs for URLs, or tagging metadata for search engines, LSP keeps the design clean and future-proof.


Album of the day: