2024. 1. 10. 02:49ㆍ백엔드/Spring Boot
이전에 디자인 패턴과 SOLID 원칙에 대하여 게시글을 작성했는데, 이번에는 SOLID 원칙별 예시 코드와 함께 정리해보려고 한다.
# SOLID 원칙이란?
SOLID 원칙은 객체 지향 프로그래밍의 다섯 가지 기본 원리로 SRP, OCP, LSP, ISP, DIP 총 5가지로 구성되어 있으며 이를 준수하여 코드를 작성하면 유연하고 확장 가능한 아키텍처를 구축할 수 있다.
SOLID 원칙은 개발자들에게 일관된 설계 구조를 제공하여 효율적이고 유지보수 가능한 코드를 작성하는 데 도움을 준다.
# SOLID 원칙
1. 단일 책임 원칙 - Single Responsibility Principle / SRP
: 하나의 클래스는 하나의 책임(기능)만을 가져야 하고, 클래스를 변경해야 하는 이유는 단 하나여야 한다.
[SRP 원칙을 위반한 코드]
- 클래스는 선수의 정보에 대해 관리하는데 주로 메디컬 팀이 진행하는 `치료`라는 행위까지 가지고 있다. 이는 하나의 클래스는 하나의 책임을 가져야 한다는 SRP 원칙을 위반한다.
public class BaseballPlayerErr {
private String name;
private int age;
private int uniformNumber;
public BaseballPlayerErr(String name, int age, int uniformNumber) {
this.name = name;
this.age = age;
this.uniformNumber = uniformNumber;
}
public String getName() {
return name;
}
// 공을 치다.
public void hit() {}
// 뛰다.
public void run() {}
// 치료 하다.
public void treat() {}
}
class BaseballPlayerErrMain {
public static void main(String[] args) {
BaseballPlayerErr player = new BaseballPlayerErr("감자도리", 24, 21);
player.hit();
player.run();
player.treat();
}
}
[SRP 원칙을 준수한 코드]
- 메디컬팀 클래스를 생성하여 해당 클래스에 치료라는 메서드를 생성하여 SRP의 원칙을 준수하였다.
public class BaseballPlayer {
private String name;
private int age;
private int uniformNumber;
public BaseballPlayer(String name, int age, int uniformNumber) {
this.name = name;
this.age = age;
this.uniformNumber = uniformNumber;
}
public void hit() {
System.out.println(name + " hit!");
}
public String getName() {
return name;
}
}
class MedicalTeam {
public void treat(BaseballPlayer player) {
System.out.println(player.getName() + " ... treat ...");
}
}
class BaseballPlayerMain {
public static void main(String[] args) {
BaseballPlayer player = new BaseballPlayer("John Doe", 20, 21);
player.hit();
MedicalTeam medicalTeam = new MedicalTeam();
medicalTeam.treat(player);
}
}
2. 개방/폐쇄 원칙 - Open/Closed Principle / OCP
: 새로운 기능을 추가하거나 변경할 때 기존의 코드를 수정하지 않고도 가능해야 한다.
[OCP 원칙을 위반한 코드]
- 야구선수의 포지션이 추가 될 때마다 ProtectiveGear 클래스를 수정해야 하므로 OCP의 확장에는 개방, 수정에는 폐쇄 되어야 하는 OCP 원칙을 위반한다.
public class BaseballPlayerErr {
String position;
public BaseballPlayerErr(String position) {
this.position = position;
}
}
// 보호 장비 관련 클래스
class ProtectiveGear {
void wear(BaseballPlayerErr player) {
switch (player.position) {
case "Hitter" :
System.out.println("야구 베트를 착용합니다.");
break;
case "Pitcher" :
System.out.println("야구공과 글러브를 착용합니다.");
break;
case "Catcher" :
System.out.println("포수 장비 세트를 착용합니다.");
break;
default:
System.out.println("착용할 장비가 없습니다.");
break;
}
}
}
class BaseballPlayerErrMain {
public static void main(String[] args) {
ProtectiveGear protectiveGear = new ProtectiveGear();
BaseballPlayerErr hitter = new BaseballPlayerErr("Hitter");
protectiveGear.wear(hitter);
BaseballPlayerErr pitcher = new BaseballPlayerErr("Pitcher");
protectiveGear.wear(pitcher);
BaseballPlayerErr catcher = new BaseballPlayerErr("Catcher");
protectiveGear.wear(catcher);
}
}
[OCP 원칙을 준수한 코드]
- 포지션별 클래스를 생성 후 ProtectiveGear 인터페이스를 상속하여 포지션이 추가되어도 기존 코드 수정이 필요하지 않도록 수정하여 OCP 원칙을 준수하였다.
interface ProtectiveGear {
void wear();
}
class Hitter implements ProtectiveGear {
@Override
public void wear() {
System.out.println("야구 베트를 착용합니다.");
}
}
class Pitcher implements ProtectiveGear {
@Override
public void wear() {
System.out.println("야구공과 글러브를 착용합니다.");
}
}
class Catcher implements ProtectiveGear {
@Override
public void wear() {
System.out.println("포수 장비 세트를 착용합니다.");
}
}
class BaseballPlayerMain {
public static void main(String[] args) {
Hitter hitter = new Hitter();
hitter.wear();
Pitcher pitcher = new Pitcher();
pitcher.wear();
Catcher catcher = new Catcher();
catcher.wear();
}
}
3. 리스코프 치환 원칙 - Liskov Substitution Principle / LSP
: 부모 객체와 자식 객체가 있을 때 부모 객체를 호출하는 동작에서 자식 객체가 부모 객체를 완전히 대체할 수 있다.
[LSP 원칙을 위반한 코드]
- Swimmer 클래스는 run 메소드를 오버라이드 하지 않았음에도 불구하고 main 메서드를 실행시켰을 때 run 메서드가 실행되고 있다. 이는 올바르지 않은 상속관계로 LSP 원칙을 위반한다.
class Athlete {
void run() {
System.out.println("땅에서 뜁니다!");
}
}
class Swimmer extends Athlete {
// 수영선수는 땅에서 뛸 수 없으므로 run 메서드를 오버라이드 하지 않음
}
public class Main {
public static void main(String[] args) {
Athlete athlete = new Swimmer();
athlete.run();
}
}
4. 인터페이스 분리 원칙 - Interface Segregation Principle / ISP
: 클라이언트는 사용하지도 않는 메서드에 의존하게 만들어서는 안되고 큰 인터페이스들을 더 작은 단위로 분리시켜야 한다.
[ISP 원칙을 위반한 코드]
- DeveloperWhoHatesExcercise 클래스는 crossfit 메서드가 필요하지 않은데에도 상속을 받게 되어 ISP 원칙을 위반한다.
interface Developer {
void study();
void develop();
void crossfit();
}
class DeveloperWhoHatesExcercise implements Developer {
@Override
public void study() {
}
@Override
public void develop() {
}
// crossfit 메서드를 사용하지 않는데도 상속을 받게됨
@Override
public void crossfit() {
}
}
class DeveloperWhoLovesExcercise implements Developer {
@Override
public void study() {
}
@Override
public void develop() {
}
@Override
public void crossfit() {
}
}
[ISP 원칙을 준수한 코드]
- Developer 인터페이스에서 crossfit 메서드를 분리하여 새로운 HealthyPerson 인터페이스로 만들었고, 각각의 클래스에서 필요한 인터페이스만 구현하여 ISP 원칙을 준수하였다.
// 운동과 관련된 인터페이스
interface HealthyPerson {
void crossfit();
}
// 개발자와 관련된 인터페이스
interface Developer {
void study();
void develop();
}
class DeveloperWhoHatesExcercise implements Developer {
@Override
public void study() {
}
@Override
public void develop() {
}
}
class DeveloperWhoLovesExcercise implements Developer, HealthyPerson {
@Override
public void study() {
}
@Override
public void develop() {
}
@Override
public void crossfit() {
}
}
5. 의존성 역전 원칙 - Dependency Inversion Principle / DIP
: 고수준 모듈은 저수준 모듈에 의존해서는 안되며, 모두 추상화에 의존해야 한다.
[DIP 원칙을 위반한 코드]
- Zoo 클래스가 Dog 클래스에 직접 의존을 하고 있기 때문에 DIP 원칙을 위반한다.
// 고수준 모듈
class Zoo {
private Dog dog;
public Zoo() {
this.dog = new Dog(); // 저수준 모듈에 직접 의존
}
public void makeSound() {
dog.bark();
}
}
// 저수준 모듈
class Dog {
public void bark() {
System.out.println("Woof!");
}
}
public class DIPViolationExample {
public static void main(String[] args) {
Zoo zoo = new Zoo();
zoo.makeSound();
}
}
[DIP 원칙을 준수한 코드]
- Zoo 클래스가 Animal 인터페이스에 의존하도록 수정하여 DIP 원칙을 준수하였다.
// 고수준 모듈이 의존하는 추상화(interface)
interface Animal {
void makeSound();
}
// 저수준 모듈이 구현하는 추상화
class Dog implements Animal {
@Override
public void makeSound() {
System.out.println("Woof!");
}
}
// 고수준 모듈이 저수준 모듈에 직접 의존하지 않음
class Zoo {
private Animal animal;
public Zoo(Animal animal) {
this.animal = animal;
}
public void makeSound() {
animal.makeSound();
}
}
public class Main {
public static void main(String[] args) {
Animal dog = new Dog();
Zoo zoo = new Zoo(dog);
zoo.makeSound();
}
}
'백엔드 > Spring Boot' 카테고리의 다른 글
Spring Validation(유효성 검사)에 대하여 알아보기 (0) | 2024.01.15 |
---|---|
롬복 @AllArgsConstructor, @NoArgsConstructor, @RequiredArgsConstructor 어노테이션 알아보기 (1) | 2024.01.15 |
디자인 패턴과 SOLID 원칙 (3) | 2024.01.09 |
[Spring Security] 세션 제어하기 (0) | 2024.01.04 |
[Spring Security] AnonymousAuthenticationFilter (1) | 2024.01.04 |