SlideShare a Scribd company logo
Favor composition over
inheritance
1
Why favor composition over inheritance?
• Inheritance is often overused; composition is a better alternative in
many cases
• Inheritance can make your code fragile
• Inheritance is inflexible
2
Inheritance can make your code
fragile
3
Example: piece of code used by a cafe
interface Beverage {
BigDecimal price();
String description();
}
class Order {
private static final BigDecimal TAX_RATE = new BigDecimal("0.1");
private BigDecimal subTotal = BigDecimal.ZERO;
void add(Beverage beverage) {
subTotal = subTotal.add(beverage.price());
}
void addAll(Collection<? extends Beverage> beverages) {
for (Beverage beverage : beverages)
subTotal = subTotal.add(beverage.price());
}
BigDecimal grandTotal() {
BigDecimal tax = subTotal.multiply(TAX_RATE);
return subTotal.add(tax);
}
}
4
New requirement: discount campaign
class CampaignOrder extends Order {
private static final BigDecimal DISCOUNT_RATE = new BigDecimal("0.2");
private int numberOfBeverages;
@Override void add(Beverage beverage) {
super.add(beverage);
numberOfBeverages++;
}
@Override void addAll(Collection<? extends Beverage> beverages) {
super.addAll(beverages);
numberOfBeverages += beverages.size();
}
@Override BigDecimal grandTotal() {
BigDecimal grandTotal = super.grandTotal();
if (numberOfBeverages > 2) {
BigDecimal discount = grandTotal.multiply(DISCOUNT_RATE);
grandTotal = grandTotal.subtract(discount);
}
return grandTotal;
}
}
5
Refactoring of Order can break CampaignOrder
class Order {
...
void add(Beverage beverage) {
subTotal = subTotal.add(beverage.price());
}
void addAll(Collection<? extends Beverage> beverages) {
for (Beverage beverage : beverages)
// Someone has done refactoring here:
// subTotal = subTotal.add(beverage.price());
add(beverage);
}
...
}
• This refactoring has broken CampaignOrder;
now CampaignOrder#numberOfBeverages gets incremented twice for every addAll() call
• The problem: CampaignOrder relies on an implementation detail of its super class
• Using inheritance this way should be avoided because that introduces fragility to the codebase
6
Better approach: composition (1/2)
interface Order {
void add(Beverage beverage);
void addAll(Collection<? extends Beverage> beverages);
BigDecimal grandTotal();
}
class RegularOrder implements Order {
private static final BigDecimal TAX_RATE = new BigDecimal("0.1");
private BigDecimal subTotal = BigDecimal.ZERO;
@Override public void add(Beverage beverage) {
subTotal = subTotal.add(beverage.price());
}
@Override public void addAll(Collection<? extends Beverage> beverages) {
for (Beverage beverage : beverages)
subTotal = subTotal.add(beverage.price());
}
@Override public BigDecimal grandTotal() {
BigDecimal tax = subTotal.multiply(TAX_RATE);
return subTotal.add(tax);
}
}
7
Better approach: composition (2/2)
class CampaignOrder implements Order {
private static final BigDecimal DISCOUNT_RATE = new BigDecimal("0.2");
private int numberOfBeverages;
private final Order delegate;
CampaignOrder() { this(new RegularOrder()); }
CampaignOrder(Order delegate) { this.delegate = delegate; }
@Override public void add(Beverage beverage) {
delegate.add(beverage);
numberOfBeverages++;
}
@Override public void addAll(Collection<? extends Beverage> beverages) {
delegate.addAll(beverages);
numberOfBeverages += beverages.size();
}
@Override public BigDecimal grandTotal() {
BigDecimal grandTotal = delegate.grandTotal();
if (numberOfBeverages > 2) {
BigDecimal discount = grandTotal.multiply(DISCOUNT_RATE);
grandTotal = grandTotal.subtract(discount);
}
return grandTotal;
}
}
• No refactoring can break CampaignOrder as long as classses keep the contract of the public methods
• CampaignOrder no longer relies on any implementation details of any class
• Composition reduces chances of unforeseen breakage; it will make a codebase more stable 8
Inheritance is inflexible
9
Example: piece of code used by a cafe
interface Beverage {
BigDecimal price();
String description();
}
class Coffee implements Beverage {
@Override public BigDecimal price() { return new BigDecimal("1.99"); }
@Override public String description() { return "Coffee"; }
}
class CoffeeWithMilk extends Coffee {
@Override public BigDecimal price() { return super.price().add(new BigDecimal("0.10")); }
@Override public String description() { return super.description() + ", Milk"; }
}
class CoffeeWithWhip extends Coffee {
@Override public BigDecimal price() { return super.price().add(new BigDecimal("0.15")); }
@Override public String description() { return super.description() + ", Whip"; }
}
class CoffeeWithSugar extends Coffee {
@Override public BigDecimal price() { super.price().add(new BigDecimal("0.05")); }
@Override public String description() { return super.description() + ", Sugar"; }
}
10
New requirement: multiple condiments
• Too many subclasses; we will need to create many subclasses every time we introduce a new condiment
• What if we want to reuse code which is responsible for a condiment for another beverage (e.g. a Tea class) ?
• Inheritance is not flexible enough for this use case
11
Better approach: composition (1/3)
class MilkWrapper implements Beverage {
private final Beverage delegate;
MilkWrapper(Beverage delegate) { this.delegate = delegate; }
@Override public BigDecimal price() { return delegate.price().add(new BigDecimal("0.10")); }
@Override public String description() { return delegate.description() + ", Milk"; }
}
class WhipWrapper implements Beverage {
private final Beverage delegate;
WhipWrapper(Beverage delegate) { this.delegate = delegate; }
@Override public BigDecimal price() { return delegate.price().add(new BigDecimal("0.15")); }
@Override public String description() { return delegate.description() + ", Whip"; }
}
class SugarWrapper implements Beverage {
private final Beverage delegate;
SugarWrapper(Beverage delegate) { this.delegate = delegate; }
@Override public BigDecimal price() { return delegate.price().add(new BigDecimal("0.05")); }
@Override public String description() { return delegate.description() + ", Sugar"; }
} 12
Better approach: composition (2/3)
@Test
void coffeeWithMilk() {
Beverage coffeeWithMilk = new MilkWrapper(new Coffee());
assertThat(coffeeWithMilk.description()).isEqualTo("Coffee, Milk");
assertThat(coffeeWithMilk.price()).isEqualByComparingTo("2.09");
}
@Test
void coffeeWithWhip() {
Beverage coffeeWithWhip = new WhipWrapper(new Coffee());
assertThat(coffeeWithWhip.description()).isEqualTo("Coffee, Whip");
assertThat(coffeeWithWhip.price()).isEqualByComparingTo("2.14");
}
@Test
void coffeeWithSugar() {
Beverage coffeeWithSugar = new SugarWrapper(new Coffee());
assertThat(coffeeWithSugar.description()).isEqualTo("Coffee, Sugar");
assertThat(coffeeWithSugar.price()).isEqualByComparingTo("2.04");
}
13
Better approach: composition (3/3)
@Test
void coffeeWithMilkAndWhip() {
Beverage coffee = new Coffee();
coffee = new MilkWrapper(coffee);
coffee = new WhipWrapper(coffee);
assertThat(coffee.description()).isEqualTo("Coffee, Milk, Whip");
assertThat(coffee.price()).isEqualByComparingTo("2.24");
}
@Test
void coffeeWithMilkAndSugar() {
Beverage coffee = new Coffee();
coffee = new MilkWrapper(coffee);
coffee = new SugarWrapper(coffee);
assertThat(coffee.description()).isEqualTo("Coffee, Milk, Sugar");
assertThat(coffee.price()).isEqualByComparingTo("2.14");
}
@Test
void coffeeWithMilkAndWhipAndSugar() {
Beverage coffee = new Coffee();
coffee = new MilkWrapper(coffee);
coffee = new WhipWrapper(coffee);
coffee = new SugarWrapper(coffee);
assertThat(coffee.description()).isEqualTo("Coffee, Milk, Whip, Sugar");
assertThat(coffee.price()).isEqualByComparingTo("2.29");
}
• Introducing a new condiment doesn't impact any other class (there will be no class explosion)
• Those wrapper classes are highly reusable; they can be reused for anything which implements Beverage
• Much more flexible than inheritance for this use case 14
Conclusion
• Improper use of inheritance can make your code fragile and inflexible
• Using inheritance just for code reuse can lead to an unforeseen
problem in the future
• When you are tempted to use inheritance, using composition instead
can be a good idea
• Further reading:
• Head First Design Patterns
• Design Patterns: Elements of Reusable Object-Oriented Software
15

More Related Content

PPT
Spring Core
PPTX
Introduction to Apache ZooKeeper
PPTX
Spring data jpa
PDF
Introducing DataFrames in Spark for Large Scale Data Science
PPT
Java database connectivity with MYSQL
PPTX
Remote Method Invocation
PPTX
Soap vs rest
PDF
Spring annotation
Spring Core
Introduction to Apache ZooKeeper
Spring data jpa
Introducing DataFrames in Spark for Large Scale Data Science
Java database connectivity with MYSQL
Remote Method Invocation
Soap vs rest
Spring annotation

What's hot (20)

PDF
MongoDB and Node.js
PDF
Spring Framework - AOP
KEY
Hybrid MongoDB and RDBMS Applications
PDF
Spring Framework - Core
PDF
1_OnBSession_はじめてのOracleCloud_12Jul.pdf
PDF
Javascript Design Patterns
PPTX
04 activities and activity life cycle
PDF
Android volley
PPTX
CAP Theorem - Theory, Implications and Practices
PPTX
Introduction to EJB
PPTX
Design Pattern - Factory Method Pattern
PPTX
PPTX
Basics of MongoDB
PDF
Hibernate 3
PPT
Design patterns ppt
PPTX
A visual introduction to Apache Kafka
PPTX
55 New Features in Java SE 8
PPTX
Introduction to Apache Kafka
PPTX
Introduction to Spring Framework
PDF
MongoDB and Node.js
Spring Framework - AOP
Hybrid MongoDB and RDBMS Applications
Spring Framework - Core
1_OnBSession_はじめてのOracleCloud_12Jul.pdf
Javascript Design Patterns
04 activities and activity life cycle
Android volley
CAP Theorem - Theory, Implications and Practices
Introduction to EJB
Design Pattern - Factory Method Pattern
Basics of MongoDB
Hibernate 3
Design patterns ppt
A visual introduction to Apache Kafka
55 New Features in Java SE 8
Introduction to Apache Kafka
Introduction to Spring Framework
Ad

Similar to Favor composition over inheritance (20)

PPTX
Java 102 intro to object-oriented programming in java - exercises
KEY
SOLID Principles
PDF
Combatendo code smells em Java
PDF
Architecture - Part 2 - Transcript.pdf
KEY
Solid principles
PDF
Dinheiro em Java: Joda-Money, Money API e além
PDF
Refatorando com a API funcional do Java
PPTX
DDDesign Challenges
PPTX
Continously delivering
PDF
Starting Out with Visual Basic 7th Edition Gaddis Test Bank
PPTX
Solid Software Design Principles
PDF
Starting Out with Visual Basic 7th Edition Gaddis Test Bank
PPTX
Joy of scala
PPTX
A GWT Application with MVP Pattern Deploying to CloudFoundry using Spring Roo
PDF
Laravel Design Patterns
PDF
SOLID Principles
PDF
Design patterns in the 21st Century
PDF
Miscellaneous Features - Part 1 - Transcript.pdf
PDF
Project Manifold (Forwarding and Delegation)
Java 102 intro to object-oriented programming in java - exercises
SOLID Principles
Combatendo code smells em Java
Architecture - Part 2 - Transcript.pdf
Solid principles
Dinheiro em Java: Joda-Money, Money API e além
Refatorando com a API funcional do Java
DDDesign Challenges
Continously delivering
Starting Out with Visual Basic 7th Edition Gaddis Test Bank
Solid Software Design Principles
Starting Out with Visual Basic 7th Edition Gaddis Test Bank
Joy of scala
A GWT Application with MVP Pattern Deploying to CloudFoundry using Spring Roo
Laravel Design Patterns
SOLID Principles
Design patterns in the 21st Century
Miscellaneous Features - Part 1 - Transcript.pdf
Project Manifold (Forwarding and Delegation)
Ad

More from Kohei Nozaki (6)

PDF
The State Pattern
PDF
Synchronize access to shared mutable data
PDF
The Singleton Pattern In Java
PDF
Java Generics wildcards
PDF
JUnit and Mockito tips
PDF
Overview of Java EE
The State Pattern
Synchronize access to shared mutable data
The Singleton Pattern In Java
Java Generics wildcards
JUnit and Mockito tips
Overview of Java EE

Recently uploaded (20)

PDF
Why TechBuilder is the Future of Pickup and Delivery App Development (1).pdf
PPTX
CHAPTER 2 - PM Management and IT Context
PDF
top salesforce developer skills in 2025.pdf
PDF
PTS Company Brochure 2025 (1).pdf.......
PDF
System and Network Administration Chapter 2
PPTX
history of c programming in notes for students .pptx
PPTX
Odoo POS Development Services by CandidRoot Solutions
PPTX
Agentic AI Use Case- Contract Lifecycle Management (CLM).pptx
PDF
Adobe Premiere Pro 2025 (v24.5.0.057) Crack free
PDF
Raksha Bandhan Grocery Pricing Trends in India 2025.pdf
PDF
medical staffing services at VALiNTRY
PDF
Internet Downloader Manager (IDM) Crack 6.42 Build 41
PDF
How Creative Agencies Leverage Project Management Software.pdf
PDF
Claude Code: Everyone is a 10x Developer - A Comprehensive AI-Powered CLI Tool
PDF
AI in Product Development-omnex systems
PPTX
VVF-Customer-Presentation2025-Ver1.9.pptx
PPTX
Agentic AI : A Practical Guide. Undersating, Implementing and Scaling Autono...
PDF
Design an Analysis of Algorithms I-SECS-1021-03
PDF
Flood Susceptibility Mapping Using Image-Based 2D-CNN Deep Learnin. Overview ...
PPTX
Introduction to Artificial Intelligence
Why TechBuilder is the Future of Pickup and Delivery App Development (1).pdf
CHAPTER 2 - PM Management and IT Context
top salesforce developer skills in 2025.pdf
PTS Company Brochure 2025 (1).pdf.......
System and Network Administration Chapter 2
history of c programming in notes for students .pptx
Odoo POS Development Services by CandidRoot Solutions
Agentic AI Use Case- Contract Lifecycle Management (CLM).pptx
Adobe Premiere Pro 2025 (v24.5.0.057) Crack free
Raksha Bandhan Grocery Pricing Trends in India 2025.pdf
medical staffing services at VALiNTRY
Internet Downloader Manager (IDM) Crack 6.42 Build 41
How Creative Agencies Leverage Project Management Software.pdf
Claude Code: Everyone is a 10x Developer - A Comprehensive AI-Powered CLI Tool
AI in Product Development-omnex systems
VVF-Customer-Presentation2025-Ver1.9.pptx
Agentic AI : A Practical Guide. Undersating, Implementing and Scaling Autono...
Design an Analysis of Algorithms I-SECS-1021-03
Flood Susceptibility Mapping Using Image-Based 2D-CNN Deep Learnin. Overview ...
Introduction to Artificial Intelligence

Favor composition over inheritance

  • 2. Why favor composition over inheritance? • Inheritance is often overused; composition is a better alternative in many cases • Inheritance can make your code fragile • Inheritance is inflexible 2
  • 3. Inheritance can make your code fragile 3
  • 4. Example: piece of code used by a cafe interface Beverage { BigDecimal price(); String description(); } class Order { private static final BigDecimal TAX_RATE = new BigDecimal("0.1"); private BigDecimal subTotal = BigDecimal.ZERO; void add(Beverage beverage) { subTotal = subTotal.add(beverage.price()); } void addAll(Collection<? extends Beverage> beverages) { for (Beverage beverage : beverages) subTotal = subTotal.add(beverage.price()); } BigDecimal grandTotal() { BigDecimal tax = subTotal.multiply(TAX_RATE); return subTotal.add(tax); } } 4
  • 5. New requirement: discount campaign class CampaignOrder extends Order { private static final BigDecimal DISCOUNT_RATE = new BigDecimal("0.2"); private int numberOfBeverages; @Override void add(Beverage beverage) { super.add(beverage); numberOfBeverages++; } @Override void addAll(Collection<? extends Beverage> beverages) { super.addAll(beverages); numberOfBeverages += beverages.size(); } @Override BigDecimal grandTotal() { BigDecimal grandTotal = super.grandTotal(); if (numberOfBeverages > 2) { BigDecimal discount = grandTotal.multiply(DISCOUNT_RATE); grandTotal = grandTotal.subtract(discount); } return grandTotal; } } 5
  • 6. Refactoring of Order can break CampaignOrder class Order { ... void add(Beverage beverage) { subTotal = subTotal.add(beverage.price()); } void addAll(Collection<? extends Beverage> beverages) { for (Beverage beverage : beverages) // Someone has done refactoring here: // subTotal = subTotal.add(beverage.price()); add(beverage); } ... } • This refactoring has broken CampaignOrder; now CampaignOrder#numberOfBeverages gets incremented twice for every addAll() call • The problem: CampaignOrder relies on an implementation detail of its super class • Using inheritance this way should be avoided because that introduces fragility to the codebase 6
  • 7. Better approach: composition (1/2) interface Order { void add(Beverage beverage); void addAll(Collection<? extends Beverage> beverages); BigDecimal grandTotal(); } class RegularOrder implements Order { private static final BigDecimal TAX_RATE = new BigDecimal("0.1"); private BigDecimal subTotal = BigDecimal.ZERO; @Override public void add(Beverage beverage) { subTotal = subTotal.add(beverage.price()); } @Override public void addAll(Collection<? extends Beverage> beverages) { for (Beverage beverage : beverages) subTotal = subTotal.add(beverage.price()); } @Override public BigDecimal grandTotal() { BigDecimal tax = subTotal.multiply(TAX_RATE); return subTotal.add(tax); } } 7
  • 8. Better approach: composition (2/2) class CampaignOrder implements Order { private static final BigDecimal DISCOUNT_RATE = new BigDecimal("0.2"); private int numberOfBeverages; private final Order delegate; CampaignOrder() { this(new RegularOrder()); } CampaignOrder(Order delegate) { this.delegate = delegate; } @Override public void add(Beverage beverage) { delegate.add(beverage); numberOfBeverages++; } @Override public void addAll(Collection<? extends Beverage> beverages) { delegate.addAll(beverages); numberOfBeverages += beverages.size(); } @Override public BigDecimal grandTotal() { BigDecimal grandTotal = delegate.grandTotal(); if (numberOfBeverages > 2) { BigDecimal discount = grandTotal.multiply(DISCOUNT_RATE); grandTotal = grandTotal.subtract(discount); } return grandTotal; } } • No refactoring can break CampaignOrder as long as classses keep the contract of the public methods • CampaignOrder no longer relies on any implementation details of any class • Composition reduces chances of unforeseen breakage; it will make a codebase more stable 8
  • 10. Example: piece of code used by a cafe interface Beverage { BigDecimal price(); String description(); } class Coffee implements Beverage { @Override public BigDecimal price() { return new BigDecimal("1.99"); } @Override public String description() { return "Coffee"; } } class CoffeeWithMilk extends Coffee { @Override public BigDecimal price() { return super.price().add(new BigDecimal("0.10")); } @Override public String description() { return super.description() + ", Milk"; } } class CoffeeWithWhip extends Coffee { @Override public BigDecimal price() { return super.price().add(new BigDecimal("0.15")); } @Override public String description() { return super.description() + ", Whip"; } } class CoffeeWithSugar extends Coffee { @Override public BigDecimal price() { super.price().add(new BigDecimal("0.05")); } @Override public String description() { return super.description() + ", Sugar"; } } 10
  • 11. New requirement: multiple condiments • Too many subclasses; we will need to create many subclasses every time we introduce a new condiment • What if we want to reuse code which is responsible for a condiment for another beverage (e.g. a Tea class) ? • Inheritance is not flexible enough for this use case 11
  • 12. Better approach: composition (1/3) class MilkWrapper implements Beverage { private final Beverage delegate; MilkWrapper(Beverage delegate) { this.delegate = delegate; } @Override public BigDecimal price() { return delegate.price().add(new BigDecimal("0.10")); } @Override public String description() { return delegate.description() + ", Milk"; } } class WhipWrapper implements Beverage { private final Beverage delegate; WhipWrapper(Beverage delegate) { this.delegate = delegate; } @Override public BigDecimal price() { return delegate.price().add(new BigDecimal("0.15")); } @Override public String description() { return delegate.description() + ", Whip"; } } class SugarWrapper implements Beverage { private final Beverage delegate; SugarWrapper(Beverage delegate) { this.delegate = delegate; } @Override public BigDecimal price() { return delegate.price().add(new BigDecimal("0.05")); } @Override public String description() { return delegate.description() + ", Sugar"; } } 12
  • 13. Better approach: composition (2/3) @Test void coffeeWithMilk() { Beverage coffeeWithMilk = new MilkWrapper(new Coffee()); assertThat(coffeeWithMilk.description()).isEqualTo("Coffee, Milk"); assertThat(coffeeWithMilk.price()).isEqualByComparingTo("2.09"); } @Test void coffeeWithWhip() { Beverage coffeeWithWhip = new WhipWrapper(new Coffee()); assertThat(coffeeWithWhip.description()).isEqualTo("Coffee, Whip"); assertThat(coffeeWithWhip.price()).isEqualByComparingTo("2.14"); } @Test void coffeeWithSugar() { Beverage coffeeWithSugar = new SugarWrapper(new Coffee()); assertThat(coffeeWithSugar.description()).isEqualTo("Coffee, Sugar"); assertThat(coffeeWithSugar.price()).isEqualByComparingTo("2.04"); } 13
  • 14. Better approach: composition (3/3) @Test void coffeeWithMilkAndWhip() { Beverage coffee = new Coffee(); coffee = new MilkWrapper(coffee); coffee = new WhipWrapper(coffee); assertThat(coffee.description()).isEqualTo("Coffee, Milk, Whip"); assertThat(coffee.price()).isEqualByComparingTo("2.24"); } @Test void coffeeWithMilkAndSugar() { Beverage coffee = new Coffee(); coffee = new MilkWrapper(coffee); coffee = new SugarWrapper(coffee); assertThat(coffee.description()).isEqualTo("Coffee, Milk, Sugar"); assertThat(coffee.price()).isEqualByComparingTo("2.14"); } @Test void coffeeWithMilkAndWhipAndSugar() { Beverage coffee = new Coffee(); coffee = new MilkWrapper(coffee); coffee = new WhipWrapper(coffee); coffee = new SugarWrapper(coffee); assertThat(coffee.description()).isEqualTo("Coffee, Milk, Whip, Sugar"); assertThat(coffee.price()).isEqualByComparingTo("2.29"); } • Introducing a new condiment doesn't impact any other class (there will be no class explosion) • Those wrapper classes are highly reusable; they can be reused for anything which implements Beverage • Much more flexible than inheritance for this use case 14
  • 15. Conclusion • Improper use of inheritance can make your code fragile and inflexible • Using inheritance just for code reuse can lead to an unforeseen problem in the future • When you are tempted to use inheritance, using composition instead can be a good idea • Further reading: • Head First Design Patterns • Design Patterns: Elements of Reusable Object-Oriented Software 15