Amblem
Furkan Baytekin

Liskov Substitution Principle via Content Management System

How LSP makes content management systems more flexible and maintainable

Liskov Substitution Principle via Content Management System
134
6 minutes

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:

java
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:

java
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:

java
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:

java
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:

java
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:

A Potential Pitfall

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

java
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:

Suggested Blog Posts