Inheritance vs Composition
Inheritance
Inheritance allows you to create a class and allow one or more classes to inherit that class. This allows you to reuse all variables, methods, constructors, etc. in each class that inherits it. A class that is inherited by other classes is commonly referred to as a superclass, base class, or parent class. A class that inherits a class is commonly referred to as a subclass or a child class. Base classes are commonly abstract, although they do not have to be. Subclasses can also be abstract. The following shows an example of inheritance in Java.
// Java Code
// Base Class
public abstract class MyBaseClass {
// ...
}
// Subclass of MyBaseClass
public class MySubclass extends MyBaseClass {
// ...
}
Inheritance is an "is a" relationship. The above example, MySubClass, "is a" MyBaseClass.
Code Reuse
Code reuse is probably the most common reason developers use inheritance. It allows you to encapsulate and abstract data and behavior. It also allows you to utilize polymorphism and is used in many design patterns.
// Java code
public class GenericDao {
protected void insert(final String sql) {
// ...
}
protected int update(final String sql) {
// ...
}
protected void delete(final String sql) {
// ...
}
}
public class UserDao extends GenericDao {
public void updateLastLogin(final Timestamp lastLogin) {
final String sql = "{sql query}";
update(sql);
}
public void delete(final String id) {
final String sql = "{sql query}";
delete("{sql query}");
}
}
This shows an example of how code is reused with inheritance. All code in the GenericDao class is also available in the UserDao class.
Class Hierarchy
Inheritance is very powerful when creating a class hierarchy. You can model your domain objects using inheritance and then create an Application Programming Interface (API) using polymorphism. The following shows an example of class hierarchy in Java using inheritance.
// Java Code
public abstract class Animal {
}
public class Dog extends Animal {
}
public class Cat extends Animal {
}
public class Tiger extends Cat {
}
public class Lion extends Cat {
}
Composition
Composition has similar benefits to inheritance. Composition is where you have an instance of a class inside another class. It allows you to simplify complex objects by using one or more classes inside of a class. The following shows an example of composition in Java:
// Java Code
public class UserDao {
private final Connection connection;
}
Composition represents the "has a" relationship. In the previous example, UserDao "has a" relationship to Connection.
Code Reuse
Just like inheritance, composition is used for code reuse. It is used to encapsulate information and implement different design patterns. The following code example is the same code in the inheritance section but implemented using composition instead.
// Java code
public class GenericDao {
public void insert(final String sql) {
// ...
}
public int update(final String sql) {
// ...
}
public void delete(final String sql) {
// ...
}
}
public class UserDao extends GenericDao {
private final GenericDao genericDao;
public UserDao(final GenericDao genericDao) {
this.genericDao = genericDao;
}
public void updateLastLogin(final Timestamp lastLogin) {
final String sql = "{sql query}";
genericDao.update(sql);
}
public void delete(final String id) {
final String sql = "{sql query}";
genericDao.delete("{sql query}");
}
}
Dependency Injection
Dependency injection is a common use case for composition. You can use polymorphism to inject dependencies using constructor or method injection. This allows you to inject different implementations depending on the environment. The following example shows constructor injection.
// Java code
public class UserService {
private final Logger logger;
public UserService(final Logger logger) {
this.logger = logger;
}
}
A production environment can inject one implementation.
// Java code
final var userService = new UserService(fileLogger);
A test can inject a different implementation, such as a mock.
// Java code
final var userService = new UserService(loggerMock);
Conclusion
Both inheritance and composition are great for code reuse. Inheritance is useful for modeling data, and composition is useful for dependency injection.