[Spring] SOLID 원칙

Na young·2024년 1월 31일

Spring

목록 보기
2/4

객체지향 설계원칙 SOLID

  1. SRP (Single Responsibility Principle): 단일 책임 원칙
  • 한 클래스는 단 하나의 책임을 가져야 하며, 클래스가 변경되어야 하는 이유는 단 하나의 이유여야 한다
  1. OCP (Open-Closed Principle): 개방-폐쇄 원칙
  • 기존 코드를 변경하지 않으면서 기능을 확장할 수 있도록 설계해야 한다
  1. LSP (Liskov Substitution Principle): 리스코프 치환 원칙
  • 서브 타입은 언제나 기반 타입으로 교체할 수 있어야 한다. 자식타입이 할 수 있는 일은 항상 부모타입이 할 수 있어야한다.
  1. ISP (Interface Segregation Principle): 인터페이스 분리 원칙
  • 인터페이스는 클라이언트에 특화되어야 하며, 클라이언트가 사용하지 않는 메서드는 포함하지 않아야 한다
  1. DIP (Dependency Inversion Principle): 의존 역전 원칙
  • 상위 수준 모듈은 하위 수준 모듈에 의존하지 않아야 하며, 추상화는 구체적인 사항에 의존하지 않아야 한다

SRP : 단일 책임 원칙

SRP
하나의 클래스는 하나의 책임만 가져야한다

  • 즉, 클래스가 변경되어야하는 이유는 오직 하나여야한다
  • 클래스를 작은 단위로 나누어 관리하면 코드를 더 읽기 쉽고 유지보수하기 쉽게 만들어준다


< SRP를 위반한 User.class >

public class User {
    private String username;
    private String password;
    
    public User(String username, String password) {
        this.username = username;
        this.password = password;
    }
    
    public boolean isValid() {
        // 유효성 검사
        return true;
    }
    
    public void save() {
        // 데이터 저장
    }
}
  • 사용자 정보를 나타내는 클래스라, 유효성 검사와 데이터 저장은 이 클래스 내에서 할 필요가 없다.

< SRP를 지켜 수정한 User.class >

public class User {
    private String username;
    private String password;
    
    public User(String username, String password) {
        this.username = username;
        this.password = password;
    }
   
}

public class UserValidator {
    public boolean isValid(User user) {
        // 유효성 검사
        return true;
    }
}

public class UserDAO {
    public void save(User user) {
        // 데이터 저장
    }
}

OCP : 개방-폐쇄 원칙

OCP
확장에는 열려있고 변경에는 닫혀있어야한다

  • 즉, 시스템을 변경하기 위해서 기존 코드를 수정하지 않고 새로운 코드를 추가할 수 있도록 설계하는 것을 목표로 한다

< OCP 원칙을 지킨 코드 >

public interface Shape {
    double calculateArea();
}

public class Circle implements Shape {
    private double radius;

    public Circle(double radius) {
        this.radius = radius;
    }

    public double calculateArea() {
        return Math.PI * radius * radius;
    }
}

public class Rectangle implements Shape {
    private double width;
    private double height;

    public Rectangle(double width, double height) {
        this.width = width;
        this.height = height;
    }

    public double calculateArea() {
        return width * height;
    }
}

public class AreaCalculator {
    public double calculateArea(Shape[] shapes) {
        double totalArea = 0;
        for (Shape shape : shapes) {
            totalArea += shape.calculateArea();
        }
        return totalArea;
    }
}
  • Shape : 다양한 도형의 면적을 계산하기 위해 공통 기능 정의

  • Circle , Rectangle : 각각 원과 사각형의 면적을 계산하는 구체적인 기능 구현

  • AreaCalculator : 입력받은 여러 도형의 면적을 모두 더해 총 면적 계산 -> Shape 인터페이스를 구현한 어떤 도형 클래스도 입력으로 받을 수 있다.

  • 따라, 어떤 새로운 도형 클래스가 추가되더라도 AreaCalculator수정할 필요가 X

  • OCP를 준수한 설계


LSP : 리스코프 치환 원칙

LSP
자식 클래스가 부모 클래스의 인스턴스 대신 사용될 때 언제나 정상적으로 작동해야한다

  • 자식 클래스가 할 수 있는 일은 전부 부모 클래스가 할 수 있어야한다

< LSP 위반 코드 >

public class PrintPositiveNum {
		private int num;
		
		public PrintPositiveNum(int num){
				this.num = num;
		}
		public getNum(){
				if(this.num <= 0){
						throw new RuntimeException("0 이하는 출력 불가능!!!");
				}
				return this.num;
		}
}

public class PrintNum extends PrintPositiveNum {
		
		@Override
		public getNum(){
			return this.num;
		}

}

public class Client{
		public static void main(String[] args){
				PrintPositiveNum obj = new PrintPositiveNum(1);
	
				obj.getNum();

		}
}
  • PrintPositiveNum (부모클래스) : 양수만 화면에 출력 가능

  • PrintNum (자식클래스) : 모든 범위의 숫자 출력 가능

  • Client가 양수를 주는 경우에는 문제가 없으나, 음수나 0을 주는 경우에는 Client 측 코드를 수정해야하만 실행을 보장한다.

  • 이는 자식은 수행가능한데 부모는 수행 불가능하기 때문에 발생한다.

  • 부모가 수행 가능한 범위 내에서만 오버라이딩을 해야 클라이언트 측 코드를 고칠 필요가 없어진다는 것이 핵심적인 내용이다.


ISP : 인터페이스 분리 원칙

ISP
클라이언트가 자신이 사용하지 않는 메서드에 의존하지 않도록 인터페이스를 작게 분리하는 것

< ISP를 위반한 코드 >

public interface Shape {
    double calculateArea();
    double calculateVolume();
}

public class Rectangle implements Shape {
    private double width;
    private double height;

    public double calculateArea() {
        return width * height;
    }

    public double calculateVolume() {
        throw new UnsupportedOperationException();
    }
}

public class Cube implements Shape {
    private double width;
    private double height;
    private double depth;

    public double calculateArea() {
        return 2 * (width * height + width * depth + height * depth);
    }

    public double calculateVolume() {
        return width * height * depth;
    }
}
  • Shape : 도형의 면적과 부피 계산하는 두 가지 메서드 정의

  • Rectangle : 면적만 계산하고 부피 계산 불가

  • 클라이언트는 Shape 인터페이스를 구현한 모든 클래스에서 부피 계산 메서드를 사용하기 때문에 불필요한 의존성이 생긴다

< ISP 원칙을 지킨 코드 >

public interface Area {
    double calculateArea();
}

public interface Volume {
    double calculateVolume();
}

public class Rectangle implements Area {
    private double width;
    private double height;

    public double calculateArea() {
        return width * height;
    }
}

public class Cube implements Area, Volume {
    private double width;
    private double height;
    private double depth;

    public double calculateArea() {
        return 2 * (width * height + width * depth + height * depth);
    }

    public double calculateVolume() {
        return width * height * depth;
    }
}

DIP : 의존성 역전의 원칙

DIP
상위 객체는 하위 객체의 구체성에 의존해서는 안되며 추상화에 의존해야 한다

  1. 고차원 모듈은 저차원 모듈에 의존해서는 안된다
  2. 추상화는 구현 클래스에 의존해서는 안된다. 구현 클래스는 추상화에 의존해야한다.

< DIP를 적용하지 않은 코드>

public class RedLight {
    public void turnOn() {
        System.out.println("Red Light turned on");
    }
}

public class Switch {
    private RedLight light;
    
    public Switch() {
        this.light = new RedLight();
    }
    
    public void flip() {
        if (light != null) {
            light.turnOn();
        }
    }
}
  • Switch : RedLight 클래스를 직접 생성하고 사용한다.
  • Switch 클래스가 RedLight 클래스에 의존하게 되고 만약 RED->BLUE 하려면 Switch 클래스에도 변경이 필요하다

< DIP를 적용한 코드 >

public interface Light {
    void turnOn();
}

public class RedLight implements Light {
    @Override
    public void turnOn() {
        System.out.println("Red Light turned on");
    }
}

public class Switch {
    private Light light;
    
    public Switch(Light light) {
        this.light= light;
    }
    
    public void flip() {
        if (light!= null) {
            light.turnOn();
        }
    }
}
profile
개발어린이

0개의 댓글