blog.itcode.devblog.itcode.dev

[OOP] Five Principles of Object Orientation (SOLID) - Single Responsibility Principle (SRP)

There are principles established for proper object-oriented design, and these five principles together are called the five principles of object orientation (SOLID). While not mandatory, the more you adhere to these rules, the more correctly designed your object-oriented code can be said to be. These five principles are as follows.

[OOP] Five Principles of Object Orientation (SOLID) - Single Responsibility Principle (SRP)

There are principles established for proper object-oriented design, and these five principles together are called the five principles of object orientation (SOLID). While not mandatory, the more you adhere to these rules, the more correctly designed your object-oriented code can be said to be. These five principles are as follows.
RWB0104
@RWBwritten at 2021-08-12 16:37:48
Object-Oriented Programming

시리즈 모아보기

Object-Oriented Programming

5 / 9

There are principles established for proper object-oriented design, and these five principles together are called the five principles of object orientation (SOLID). While not mandatory, the more you adhere to these rules, the more correctly designed your object-oriented code can be said to be.

These five principles are as follows.

  1. Single Responsibility Principle
  2. Open-Closed Principle
  3. Liskov Substitution Principle
  4. Interface Segregation Principle
  5. Dependency Inversion Principle

Taking the first letter of each principle in English gives us the name SOLID.

The Single Responsibility Principle states that a single object must be responsible for only a single behavior.

The stronger the modularization, the fewer the dependencies/associations with other objects. Conversely, the weaker the modularization, the greater the dependencies/associations with other objects become, and in the worst case, there may be no encapsulation policy at all, allowing indiscriminate access to a module's methods.

As the behaviors an object is responsible for — that is, its responsibilities — increase, the amount and scope of impact from changes to that object grow significantly. The Single Responsibility Principle aims to avoid overloading a particular object with too much responsibility as much as possible.

Cars are divided into front-wheel drive (FWD), rear-wheel drive (RWD), and all-wheel drive (AWD) depending on how power is delivered to the wheels, with the following characteristics:

  • In front-wheel drive, power is delivered only to the two front wheels.
  • In rear-wheel drive, power is delivered only to the two rear wheels.
  • In all-wheel drive, power is delivered to all wheels.

Let's implement this as an object.

JAVA

/**
 * Car object
 *
 * @author RWB
 * @since 2021.08.13 Fri 00:14:14
 */
public class Car
{
	private final String WD;
	
	private final int[] WHEEL = { 0, 0, 0, 0 };
	
	/**
	 * Car constructor function
	 *
	 * @param wd: [String] wheel drive type
	 */
	public Car(String wd)
	{
		WD = wd;
	}
	
	/**
	 * Drive function
	 *
	 * @param power: [int] power
	 */
	public void run(int power)
	{
		switch (WD.toUpperCase())
		{
			case "FWD" -> {
				WHEEL[0] = power;
				WHEEL[1] = power;
			}
			
			case "RWD" -> {
				WHEEL[2] = power;
				WHEEL[3] = power;
			}
			
			case "AWD" -> {
				WHEEL[0] = power;
				WHEEL[1] = power;
				WHEEL[2] = power;
				WHEEL[3] = power;
			}
		}
		
		System.out.println("Wheel power status: " + WHEEL[0] + ", " + WHEEL[1] + ", " + WHEEL[2] + ", " + WHEEL[3]);
	}
}

Here we have a Car object. Car takes a wheel drive type as a parameter upon creation.

Car has a run() method that implements the driving behavior, and this method takes power as a parameter. It then assigns power to the appropriate wheels according to the drive type and prints the wheel status.

If we consider the behavior for each drive type as a single responsibility, this object carries as many as three responsibilities. When too many responsibilities are concentrated in a single object like this, the object's dependency within the project increases. This phenomenon directly contradicts encapsulation, one of the main characteristics of object orientation. Moreover, when pieces of code depend on each other, the impact of code changes becomes greater, and the scope also widens.

The example above is a simple one meant to illustrate the Single Responsibility Principle. If the codebase were larger or more complex, every code modification would trigger all sorts of errors. Furthermore, as the code changes, it may also require refactoring already-established code. This could trigger a disaster where refactoring calls for more refactoring. This is where code tends to get messy.

The Single Responsibility Principle was established precisely to prevent situations like this, requiring that objects be designed as concisely and clearly as possible following the rule of 1 object = 1 responsibility. So how do we reduce the responsibilities of the code above to comply with the Single Responsibility Principle?

JAVA

/**
 * Abstract car object
 *
 * @author RWB
 * @since 2021.08.13 Fri 00:14:14
 */
abstract public class Car
{
	protected final String WD;
	
	protected final int[] WHEEL = { 0, 0, 0, 0 };
	
	/**
	 * Car constructor function
	 *
	 * @param wd: [String] wheel drive type
	 */
	public Car(String wd)
	{
		WD = wd;
	}
	
	/**
	 * Drive function
	 *
	 * @param power: [int] power
	 */
	abstract public void run(int power);
}

First, we need to implement a common interface or a parent object. In this case, since a constructor is needed, a parent object is more appropriate than an interface.

What? Discriminating against interfaces??
Unlike ordinary objects or abstract objects, interfaces cannot enforce a constructor.

We implement the Car object so it can serve as the parent object. Since the run() method behaves differently depending on the wheel drive type, we declare it as an abstract method using the abstract modifier. Either an instance of this object will be created, or a subclass will implement it directly.

JAVA

/**
 * Front-wheel car object
 *
 * @author RWB
 * @since 2021.08.13 Fri 01:03:13
 */
class FrontWheelCar extends Car
{
	/**
	 * FrontWheelCar constructor function
	 *
	 * @param wd: [String] wheel drive type
	 */
	public FrontWheelCar(String wd)
	{
		super(wd);
	}
	
	/**
	 * Drive function
	 *
	 * @param power: [int] power
	 */
	@Override
	public void run(int power)
	{
		WHEEL[0] = power;
		WHEEL[1] = power;
		
		System.out.println("Wheel power status: " + WHEEL[0] + ", " + WHEEL[1] + ", " + WHEEL[2] + ", " + WHEEL[3]);
	}
}

/**
 * Rear-wheel car object
 *
 * @author RWB
 * @since 2021.08.13 Fri 01:05:57
 */
class RearWheelCar extends Car
{
	/**
	 * RearWheelCar constructor function
	 *
	 * @param wd: [String] wheel drive type
	 */
	public RearWheelCar(String wd)
	{
		super(wd);
	}
	
	/**
	 * Drive function
	 *
	 * @param power: [int] power
	 */
	@Override
	public void run(int power)
	{
		WHEEL[2] = power;
		WHEEL[3] = power;
		
		System.out.println("Wheel power status: " + WHEEL[0] + ", " + WHEEL[1] + ", " + WHEEL[2] + ", " + WHEEL[3]);
	}
}

/**
 * All-wheel car object
 *
 * @author RWB
 * @since 2021.08.13 Fri 01:05:57
 */
public class AllWheelCar extends Car
{
	/**
	 * AllWheelCar constructor function
	 *
	 * @param wd: [String] wheel drive type
	 */
	public AllWheelCar(String wd)
	{
		super(wd);
	}
	
	/**
	 * Drive function
	 *
	 * @param power: [int] power
	 */
	@Override
	public void run(int power)
	{
		WHEEL[0] = power;
		WHEEL[1] = power;
		WHEEL[2] = power;
		WHEEL[3] = power;
		
		System.out.println("Wheel power status: " + WHEEL[0] + ", " + WHEEL[1] + ", " + WHEEL[2] + ", " + WHEEL[3]);
	}
}

We create objects for front-wheel, rear-wheel, and all-wheel drive. Since all three of these objects belong under Car, we implement them by extending Car. By implementing the behavior in each object's run() method, each object now carries a single responsibility.

By dividing responsibilities this way, when code changes are needed, only the corresponding object needs to be modified, lowering dependency and achieving proper modularization. Moreover, the code becomes concise, making it easier to maintain, and the impact of changes becomes much smaller.

When designing code, it often happens that a single object ends up handling too many behaviors. The author, too, has repeatedly and unconsciously assigned too many responsibilities to a single object/method. The Single Responsibility Principle helps steer development in the right direction.

Let's try to adhere to the Single Responsibility Principle as much as possible so we can implement proper objects.

# CS# Object-Oriented Programming# Five Principles of OOP# Single Responsibility Principle# SRP
ship
blog.itcode.dev

Notes from the π-th Alpaca

7.0.1
Developed by RWB since 2021.057th upgraded at 2026.08