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:
javaimport 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:
javaimport 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:
javaimport 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:
javaimport 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:
javaimport 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:
javafor (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: