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.

Listen to this post

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: