🌞 SOLID Principles

GunhoΒ·2024λ…„ 11μ›” 1일

Object Oriented Programming (OOP) & Java

λͺ©λ‘ 보기
12/29

πŸŒ› What are the SOLID Principles?

SOLID principles is an acronym of the five software developing principles where complying with the SOLID principles becomes a good technical guidance and practice for good OOP (Object Oriented Programming) designs.

πŸ’‘ Cohesion refers to degree of modularity across interrelated components.

πŸ’‘ Coupling refers to the degree of interdependence across logically varying software components.

SOLID principles can be viewed as the clarifications and perhaps redefinition of the best common practices across the developers where the concepts like high-cohesion and low-coupling still well persist in the SOLID principles.

Five principles in the SOLID are namely:

  • S ingle Responsibility Principle (SRP)
  • O pen and Close Principle (OCP)
  • L iskov Substitution Principle (LSP)
  • I nterface Segregation Principle (ISP)
  • D ependency Inversion Principle (DIP)

where they will be discussed in the following sections.

🌳 Single Responsibility Principle

Single Responsibility Principle (SRP) suggests that:

🌳 A class has only one reason to change

which essentially translates to

🌳 A class should have a single responsibility.


SRP can be better explained by the below codes.

Dog Class

class Dog {
	private String sex;
    
    void bark() {
      	if (sex.equals("male")) {
        	System.out.println("Baaaark brk!!!")
        } else if (sex.equals("female")) {
        	System.out.println("Bark Bark!!!")
        } 
    }
}

Dog Class as can be seen has two logical branches, whether an instance's sex property is male or female. This violates the SRP as the Dog class has two reasons to change either for the male or female-related logic. Hence, a better class design could be the one below, where the abstract Dog class is extended to Male & Female Dog classes. Hence, there are two separate classes responsible for a single logical branch.

Dog Classes (SRP)

abstract class Dog { 
    abstract void bark() {}
}

class MaleDog extends Dog {
	void bark() {
    	System.out.println("Baaaark brk!!!")
    }
}

class FemaleDog extends Dog {
	void bark() {
    	System.out.println("Bark Bark!!!")
    }
}

SRP if appropriately applied can initially result in:

  • πŸ”Ί maintainability
  • πŸ”Ί testability

excessive implementation of SRP can, however, result in:

  • πŸ”Ί complexity (followed by too many defined classes)

πŸšͺ Open and Close Principle

Open and Close Principle (OCP) suggests that

πŸšͺ A class should be open for extensions but closed for modification.

This implies that new features should be introduced in the form of class extensions and interface implementation rather than directly changing the existing code as the latter enhances the risk of creating potential bugs.


A good example could be below:

Driver & SpecificCarClass

class Driver {
	private SpecificCarClass specificCarClass = new SpecificCarClass();
}

class SpecificCarClass {
	private int wheels;
    private int windows; 
    private int seats; 
}

Driver class from the above code has a field with a concrete class, and the violation of OCP is highly likely under the circumstances of concrete car class field changing.

Driver & Car & SpecificCarClass

class Driver {
	private Car car;
}

class abstract Car {
	private int wheels;
    private int windows; 
    private int seats; 
}

class SpecificCarClass extends car {
}

Hence, to maintain OCP, the principles from the DIP (polymorephism) could be directly implemented, where specifically the parent class or a parent interface could be implemented instead of the concrete class type.

The above code well illustrates how OCP via DIP could be maintained. Another good example in the real world could be JDBC API where in the JDBC, the interfaces are only stated, and the actual implementation or the future modification can be altered by simply implementing the interfaces from the JDBC APIs.

Geeks for Geeks Available at here

Compliance with OCP increases the codes'

  • πŸ”Ί maintainability
  • πŸ”Ί reusability

the above merits, however, come with the cost of:

  • πŸ”Ί complexity (followed by too many defined classes)

🎭 Liskov Substitution Principle

Liskov Substitution Principle (LSP) simply suggests that

🎭 A child class 'A' should be able to substitute its parent class 'B' without causing any breakdowns.

Abstraction from the OOP is a concept that is most closely related to the LSP as LSP redefines how abstraction via class extensions and interface implementation should be managed.

Specifically, LSP focuses on child classes to explicitly follow the behaviour conduct from their parent classes, where failing to comply with the behavioural conduct essentially implies that the abstraction has been poorly implemented leading to code that becomes tightly coupled, unpredictable, and harder to maintain or extend.

LSP could be further simplied to complying with the below sentences.

  • child class is a kind of parent class
  • implmenting class is able to interface

LSP can be well represented underneath, in which the below child class, Penguin violates the behavioural conduct of its parent class, Bird assuming that the fly() method is implicitly not possible in the scope of the Penguin class.

Bird & Penguin & Sparow classes

abstract class Bird {
	abstract void fly();
}

class extends Penguin extends Bird {
	public void fly() {
    	System.out.println("FLY????");
    }
}

class extends Sparow extends Bird {
	public void fly() {
    	System.out.println("Yeh I can fly");
    }
}

To maintain LSP, the overall class design could change, where specifically, the Sparrow class could implement the Flyable interface that requires the implementation of the fly() method.

Bird & Penguin & Sparow classes & Flyable interface

abstract class Bird {
	abstract void eat();
}

class extends Penguin extends Bird {
	@Override
	public void eat() {
    	System.out.println("yum yum");
    }
}

interface Flyable {
	void fly();
}

class extends Sparow extends Bird implements Flyable {
	@Override
	public void eat() {
    	System.out.println("yum yum");
    }
    
    @Override
    public void fly() {
    	System.out.println("Yeh I can fly");
    }
}

Compliance with LSP increases codes':

  • πŸ”Ί stability
  • πŸ”Ί flexibility

the above merits, however, come with the cost of:

  • πŸ”Ί complexity (followed by too many defined classes)

πŸ–¨οΈ Interface Segregation Principle

Interface Segregation Principle (ISP) suggests that

πŸ–¨οΈ A client should not be dependent on methods that are not used by the client.

methods that are hardly used strictly refers to the methods with varying interests strictly relevant to SRP.

ISP could be an alternative approach to maintaining SRP than by class extensions, where given with the methods with varying interests, these methods can be individually required to be implemented via interfaces.


Underneath Man class could be a good example, where the class excessive responsibilities harming its code maintainance.

Man Class

class Man {
	public void doBetterToParents() {
    }
    
    public void helpParents() {
    }

	public void kiss() {
    }
    
    public void date() {
    }
    
    public void work() {
    }
    
    public void goWork() {
    }
}

Under ISP, the above Man class can be further splitted into multiple interfaces with varying interests:

Interfaces

public interface Son {
	public void doBetterToParents() {
    }
   	
    public void helpParents() {
    }
}

public interface Boyfriend {
	public void kiss() {
    }
   	
    public void date() {
    }
}

public interface Worker {
    public void work() {
    }
    
    public void goWork() {
    }
}

and depending on the interests, vayring interfaces can be implemented.

Man, Boy & GrownUpMap class

abstract class Man {
}

class Boy extends Man implements Son {
	@Override
    public void doBetterToParents() {
    }
   	
    @Override
    public void helpParents() {
    }	
}

class GrownupMan extends Man implements Son, Worker {
	@Override
    public void doBetterToParents() {
    }
   	
    @Override
    public void helpParents() {
    }	
    
    @Override
    public void work() {
    }
    
    @Override
    public void goWork() {
    }
}

Compliance with ISP maintains SRP and hence increases:

  • πŸ”Ί maintainability
  • πŸ”Ί testability

the above merits, however, come with the expanse of:

  • πŸ”Ί complexity (followed by too many defined interfaces)

πŸ’‰ Dependency Inversion Principle

Dependency Inversion Principle (DI) suggests that

πŸ’‰ High-level modules should not depend on low-level modules, both should depend on abstractions.

this can be further elaborated in the following statement

A class should implement abstractions represented as interfaces and abstract classes over concrete classes

Exactly the same example codes from the OCP could be used to explain DI at the code level.

DI becomes the essence of Spring where the internal mechanisms of Spring Beans in the Spring Container extensively apply and implement DI.

DIP allows:

  • πŸ”Ί class decoupling
  • πŸ”Ί flexibility
  • πŸ”Ί maintainability

the above merits, however, come with the expanse of:

  • πŸ”» understandability (followed by abstraction)

πŸ“š References

μŠ€ν”„λ§ μž…λ¬Έμ„ μœ„ν•œ μžλ°” 객체지ν–₯의 원리와 이해
Geeks for Geeks
Free Code Camp

profile
Hello

0개의 λŒ“κΈ€