57일차 (3) - 스프링 (SOLID)

Yohan·2024년 5월 13일
0

코딩기록

목록 보기
83/156
post-custom-banner

스프링 프레임워크

스프링 프레임워크(Spring Framework)는 자바 언어를 위한 오픈소스 경량급 애플리케이션 프레임워크

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 X - 한 클래스에 여러가지 기능이 있다.
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 O - 클래스마다 기능을 쪼갰다.
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 : 개방 - 폐쇄의 원칙

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

LSP : 리스코프 치환 원칙

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

ISP : 인터페이스 분리 원칙

  • ISP(Interface Segregation Principle)란, 자신이 사용하지 않는 메서드에 의존하지 않도록 인터페이스를 작게 분리하는 것
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(Dependency Inversion Principle)는 의존성 역전 원칙으로, 상위 객체는 하위 객체의 구체성에 의존해서는 안되며 추상화에 의존해야 한다는 원칙
  • DIP 적용안한 예시
    • Switch클래스는 RedLight클래스를 사용하고 있다. 즉, Switch 클래스는 RedLight 클래스를 의존하고있다. RedLight클래스가 다른 클래스로 바뀌면 Switch 클래스에서도 바꿔줘야 하는 문제가 발생
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();
        }
    }
}
  • DIP 적용 예시
    • Switch 클래스는 Light 인터페이스를 통해 RedLight 클래스와 의존 관계를 맺고 있다. 이렇게하면 RedLight 클래스에 변경이 생겨도 Switch 클래스는 영향을 받지 않는다.
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();
        }
    }
}

예제를 통해 OCP, DIP 확인

1. OCP, DIP 적용 X

package com.study.springstudy.chap01;

/*
 * @problem - 호텔 클래스에서 직접 객체를 생성하면
 *            나중에 의존객체를 변경해야 될 때
 *             직접 호텔 클래스를 수정해야 되므로
 *            OCP를 위반하게 됨.
 *            그리고 headChef가 변경되면 레스토랑 안에
 *              쉐프도 같이 바뀌어야 할 때 2군데를 수정해야 함.
 */

public class Hotel {

    // 레스토랑
    private AsianRestaurant restaurant = new AsianRestaurant();

    // 헤드쉐프
    private KimuraChef headChef = new KimuraChef();

    // 호텔을 소개하는 기능
    public void inform() {
        System.out.printf("우리 호텔의 레스토랑은 %s입니다. " +
                "그리고 헤드쉐프는 %s입니다.\n"
                , restaurant.getClass().getSimpleName()
                , headChef.getClass().getSimpleName());

        restaurant.orderMenu();
    }
}

2. OCP X DIP O

package com.study.springstudy.chap02;

/*
 * @Solution
 * - 먼저 DIP를 해결하기 위해 구체적인 객체 대신
 * 추상적인 역할에 의존하게 코드를 개선
 *
 * @problem - 추상화를 했지만 여전히 의존객체를 바꾸려면
 *            코드를 직접 변경해야 한다.
 */

public class Hotel {

    // 레스토랑
    private Restaurant restaurant = new WesternRestaurant();

    // 헤드쉐프
    private Chef headChef = new KimuraChef();

    // 호텔을 소개하는 기능
    public void inform() {
        System.out.printf("우리 호텔의 레스토랑은 %s입니다. " +
                "그리고 헤드쉐프는 %s입니다.\n"
                , restaurant.getClass().getSimpleName()
                , headChef.getClass().getSimpleName());

        restaurant.order();
    }
}

3. OCP O, DIP O

package com.study.springstudy.chap03;

/*
 * @Solution
 * - 객체 생성의 제어권을 이 클래스에서
 *   다른 클래스로 이전한다.
 *   ex) new 생성자(); - 이 문법을 담당클래스를 정해서 몰아서 수행시킴
 * - 호텔 객체 생성시 반드시 객체를 전달하도록 강요 (생성자를 통해서)
 *
 * // 제어의 역전(IoC) : 객체 생성의 제어권을 외부로 넘긴다.
    // 의존성 주입(DI) : 외부에서 생성된 객체를 주입받는 개념
 */

public class Hotel {

    // 레스토랑
    private Restaurant restaurant;

    // 헤드쉐프
    private Chef headChef;

    // restaurant, headChef 있어야 Hotel 지을 수 있어~
    public Hotel(Restaurant restaurant, Chef headChef) {
        this.restaurant = restaurant;
        this.headChef = headChef;
    }

    // 호텔을 소개하는 기능
    public void inform() {
        System.out.printf("우리 호텔의 레스토랑은 %s입니다. " +
                "그리고 헤드쉐프는 %s입니다.\n"
                , restaurant.getClass().getSimpleName()
                , headChef.getClass().getSimpleName());

        restaurant.order();
    }
}
  • 객체를 직접 생성하지 않고 제어권을 다른 클래스로 이전한다. (주로 config)
    -> 제어권을 다른 클래스로 이전하는 것을 제어의 역전(IoC) 라고 한다.
  • 또한, 외부에서 생성된 객체를 주입해야한다.
    -> 외부에서 생성된 객체를 주입받는 개념은 의존성 주입(DI)라고 한다.
  • config 클래스
package com.study.springstudy.chap03.config;

import com.study.springstudy.chap03.*;

// 객체 생성의 제어권을 모두 가지고 온 객체
public class HotelManager {

    // 쉐프 객체 생성
    public Chef chef1() {
        return new JannChef();
    }
    public Chef chef2() {
        return new KimuraChef();
    }
    // 요리 코스객체 생성
    public Course course1() {
        return new FrenchCourse();
    }
    public Course course2() {
        return new SushiCourse();
    }
    // 레스토랑 객체 생성
    public Restaurant restaurant1() {
        return new WesternRestaurant(chef1(), course1());
    }
    public Restaurant restaurant2() {
        return new AsianRestaurant(chef2(), course2());
    }
    // 호텔 객체 생성
    public Hotel hotel() {
        return new Hotel(restaurant2(), chef2());
    }

}
  • ctrl + shif + t로 단위테스트를 할 수 있다.
  • 코드가 잘 출력되는 것을 알 수 있다.

profile
백엔드 개발자
post-custom-banner

0개의 댓글