📙 목차
1. SOLID 원칙
SOLID 원칙
- 객체 지향 설계에서 소프트웨어의 유지보수성, 확장성, 유연성을 높이기 위한 5가지 기본 설계 원칙
SOLID 원칙의 종류
- 단일 책임 원칙 SRP(Single Responsibility Principle)
- 개방 폐쇄 원칙 OCP(Open Closed Principle)
- 리스코프 치환 원칙 LSP(Liskov Substitution Principle)
- 인터페이스 분리 원칙 ISP(Interface Segregation Principle)
- 의존관계 역전 원칙 DIP(Dependency Inversion Principle)
더보기
단일 책임 원칙 SRP(Single Responsibility Principle)
- 하나의 클래스는 하나의 책임만 가져야 한다.
// 위반 예
public class User {
private String name;
private String email;
public User(String name, String email) {
this.name = name;
this.email = email;
}
// 사용자 정보를 저장하는 책임
public void save() {
System.out.println("저장 중: " + name);
}
// 이메일을 전송하는 책임
public void sendEmail() {
System.out.println("이메일을 보내는 중: " + email);
}
}
// 적용 예
public class User {
private String name;
private String email;
public User(String name, String email) {
this.name = name;
this.email = email;
}
public String getName() {
return name;
}
public String getEmail() {
return email;
}
}
// 사용자 정보를 DB에 저장하는 클래스
public class UserRepository {
public void save(User user) {
System.out.println("저장 중: " + user.getName());
}
}
// 이메일 전송을 담당하는 클래스
public class EmailService {
public void sendEmail(User user) {
System.out.println("이메일을 보내는 중: " + user.getEmail());
}
}
개방 폐쇄 원칙 OCP(Open Closed Principle)
- 확장에는 열려있어야 하며, 변경(수정)에는 닫혀 있어야 한다.
// 위반 예
public class Shape {
public String type;
}
public class AreaCalculator {
public double calculate(Shape shape) {
if (shape.type.equals("circle")) {
return Math.PI * Math.pow(5, 2);
} else if (shape.type.equals("square")) {
return 5 * 5;
}
return 0;
}
}
// 적용 예
public interface Shape {
double calculateArea();
}
public class Circle implements Shape {
private double radius;
public Circle(double radius) {
this.radius = radius;
}
@Override
public double calculateArea() {
return Math.PI * Math.pow(radius, 2);
}
}
public class Square implements Shape {
private double side;
public Square(double side) {
this.side = side;
}
@Override
public double calculateArea() {
return side * side;
}
}
public class AreaCalculator {
public double calculate(Shape shape) {
return shape.calculateArea();
}
}
리스코프 치환 원칙 LSP(Liskov Substitution Principle)
- 하위타입을 상위타입으로 교체해도 기능에 문제가 없어야 한다
// 위반 예
public class Bird {
public void fly() {
System.out.println("The bird is flying");
}
}
public class Penguin extends Bird {
@Override
public void fly() {
throw new UnsupportedOperationException("Penguins cannot fly");
}
}
// 적용 예
public abstract class Bird {
public abstract void move();
}
public class Sparrow extends Bird {
@Override
public void move() {
System.out.println("The sparrow is flying");
}
}
public class Penguin extends Bird {
@Override
public void move() {
System.out.println("The penguin is swimming");
}
}
인터페이스 분리 원칙 ISP(Interface Segregation Principle)
- 사용하지 않는 기능에 의존해서는 안된다
// 위반 예
public interface Employee {
void work();
void eat();
}
public class Manager implements Employee {
@Override
public void work() {
System.out.println("Managing the team");
}
@Override
public void eat() {
System.out.println("Eating lunch");
}
}
public class Intern implements Employee {
@Override
public void work() {
System.out.println("Assisting with tasks");
}
@Override
public void eat() {
// 불필요한 메소드 구현
}
}
// 적용 예
public interface Workable {
void work();
}
public interface Eatable {
void eat();
}
public class Manager implements Workable, Eatable {
@Override
public void work() {
System.out.println("Managing the team");
}
@Override
public void eat() {
System.out.println("Eating lunch");
}
}
public class Intern implements Workable {
@Override
public void work() {
System.out.println("Assisting with tasks");
}
}
의존관계 역전 원칙 DIP(Dependency Inversion Principle)
- 세부사항에 의존하지 말고 추상화에 의존하라
// 위반 예
public class UserRepository {
public void save(User user) {
System.out.println("Saving user: " + user.getName());
}
}
public class UserService {
private UserRepository userRepository;
public UserService() {
this.userRepository = new UserRepository(); // 구체적인 클래스에 의존
}
public void registerUser(User user) {
userRepository.save(user);
}
}
public class User {
private String name;
public User(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
// 적용 예
public interface UserRepository {
void save(User user);
}
public class DatabaseUserRepository implements UserRepository {
@Override
public void save(User user) {
System.out.println("Saving user to the database: " + user.getName());
}
}
public class FileUserRepository implements UserRepository {
@Override
public void save(User user) {
System.out.println("Saving user to a file: " + user.getName());
}
}
public class UserService {
private UserRepository userRepository;
// 생성자 주입을 통해 의존성 주입
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public void registerUser(User user) {
userRepository.save(user);
}
}
public class User {
private String name;
public User(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
2. Spring과 객체 지향
Spring은 OCP(개방-폐쇄 원칙)와 DIP(의존 관계 역전 원칙)처럼 다형성만으로는 구현하기 어려운 객체 지향 설계 원칙들을 IOC(제어의 역전)와 DI(의존성 주입)를 통해 보다 효율적으로 구현할 수 있도록 지원한다.
IoC(제어의 역전, Inversion of Control)
- 제어의 흐름을 반전시킨다는 뜻
- 객체를 생성하고 관리하는 책임을 스프링 컨테이너에 맡기는 방식
// 전통적인 방식
Car car = new Car();
// Spring에서는 이 객체 생성과 초기화 과정을 스프링 컨테이너가 관리한다.
Car car = context.getBean(Car.class);
DI (Dependency Injection, 의존성 주입)
- 객체 간의 의존 관계를 주입하는 방식
- 객체가 필요한 다른 객체를 스스로 생성하거나 관리하지 않고, 외부에서 주입하는 방식으로 의존성을 해결하는 방식
// 전통적인 방식
public class Car {
private Engine engine = new Engine(); // 직접 Engine 객체를 생성
}
// 외부에서 Engine 객체를 주입
public class Car {
private final Engine engine;
// DI를 통해 외부에서 Engine을 주입
@Autowired
public Car(Engine engine) {
this.engine = engine;
}
}
IoC와 DI 의 장점
- 유연성 향상: 객체의 생성과 의존 관계를 스프링이 관리하므로, 직접 객체를 관리하지 않고도 필요한 객체를 쉽게 교체할 수 있다.
- 테스트 용이성: 의존성을 외부에서 주입받기 때문에, 테스트할 때도 실제 구현체가 아닌 가짜 객체(mock object)를 주입하여 테스트할 수 있다.
- 결합도 감소: 의존성 주입을 통해 클래스들 간의 결합도가 낮아진다. 즉 클래스들이 서로 직접적으로 의존하지 않고, 외부에서 주입받으므로 코드 변경이 쉬워진다.
3. Spring의 핵심 개념
Spring Container
- Spring 애플리케이션에서 객체들을 관리하는 역할을 한다.
- 객체를 생성, 관리, 소멸하는 작업을 담당하며, 애플리케이션이 시작될 때 설정 파일이나 어노테이션을 통해 필요한 객체(Bean)를 생성하고 주입한다.
- 역할: 객체 생성, 의존성 주입, 객체 생명주기 관리
- 종류
- BeanFactory: Spring Container의 기본 인터페이스로, 객체를 생성하고 관리한다.
- ApplicationContext: BeanFactory의 확장판으로, 국제화, 환경변수 관리 등 추가적인 기능을 제공한다. ApplicationContext를 주로 사용한다.
Spring Bean
- Spring Bean은 Spring Container가 관리하는 객체
- 특징
- 기본적으로 싱글톤(Singleton)으로 설정되어 동일한 객체 인스턴스를 재사용한다.
- DI를 통해 다른 객체에 의존성을 주입받는다.
4. 싱글톤(Singleton)
싱글톤(Singleton)
- 애플리케이션 실행 중 하나의 인스턴스만 생성되어 재사용되는 객체를 의미한다.
- 같은 객체를 여러 번 만들지 않고 한 번만 만들어서 재사용하는 디자인 패턴
싱글톤 특징
- 객체 1개만 존재: 클래스가 최초 요청될 때 단 1개의 인스턴스를 생성하고, 그 이후에는 같은 인스턴스를 계속 반환한다.
- 공유 인스턴스: 여러 곳에서 동일한 객체를 참조하게 되므로, 공통된 설정이나 자원을 관리할 때 유용하다.
- 메모리 효율성 증가: 객체를 반복해서 생성하지 않으므로 메모리 낭비가 줄어든다.
- 스프링의 기본 Bean 설정: 스프링 컨테이너에 등록된 Bean은 기본적으로 모두 싱글톤이다.
싱글톤 패턴의 주의점
- 상태 유지(stateful)의 문제점: 데이터의 불일치나 동시성 문제가 발생할 수 있다.
'Spring > 강의' 카테고리의 다른 글
[📙 숙련 Spring] 1-3. Validation과 Bean Validation (4) | 2025.05.15 |
---|---|
[📙 숙련 Spring] 1-2. Spring Bean 등록 (0) | 2025.05.15 |
[📗 스프링 입문] 2. 스프링 웹 개발 기초 (1) | 2025.05.08 |
[📗 스프링 입문] 1. 프로젝트 환경 설정 (1) | 2025.05.07 |
[📗 스프링 입문] 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술 (0) | 2025.05.07 |