
📚 객체 지향 설계의 5가지 기본 원칙으로, 소프트웨어 설계에서 유지보수성, 확장성, 유연성을 높이기 위한 지침을 제공한다.
SOLID 원칙의 종류
단일 책임 원칙 SRP (Single Responsibility Principle)
하나의 클래스는 하나의 책임만 가져야 한다.
클래스는 한 가지 기능에 집중하며, 다른 기능을 담당하지 않아야 한다.
public class User {
private String name; // 사용자 정보
public void login() { /* 로그인 기능 */ }
public void saveUser() { /* 데이터베이스 저장 기능 */ }
}
단일 책임 원칙 적용 예시
public class User { /* 사용자 정보 관리 */ }
public class AuthService {
public void login(User user) { /* 로그인 기능 */ }
}
public class UserRepository {
public void saveUser(User user) { /* 데이터베이스 저장 */ }
}
개방 폐쇄 원칙 OCP (Open Closed Principle)
소프트웨어 요소는 확장에 열려 있어야 하고, 수정에 닫혀 있어야 한다.
새로운 기능을 추가할 때 기존 코드를 수정하지 않고 확장 가능해야 한다.
public interface Shape {
double calculateArea();
}
public class Circle implements Shape {
public double calculateArea() { return /* 원의 넓이 계산 */; }
}
public class Square implements Shape {
public double calculateArea() { return /* 사각형의 넓이 계산 */; }
}
public class AreaCalculator {
public double calculate(Shape shape) {
return shape.calculateArea();
}
}
리스코프 치환 원칙 LSP (Liskov Substitution Principle)
자식 클래스는 언제나 부모 클래스를 대체할 수 있어야 한다.
interface Acceleratable {
void accelerate();
}
class Car implements Acceleratable {
@Override
public void accelerate() {
System.out.println("내연기관 자동차가 가속합니다.");
}
}
class ElectricCar implements Acceleratable {
@Override
public void accelerate() {
System.out.println("전기차가 배터리로 가속합니다.");
}
}
인터페이스 분리 원칙 ISP (Interface Segregation Principle)
특정 클라이언트를 위한 인터페이스 여러 개가 범용 인터페이스 하나보다 낫다.
public interface Runnable {
void run();
}
public interface Swimmable {
void swim();
}
public class Dog implements Runnable, Swimmable {
public void run() { /* 달리기 */ }
public void swim() { /* 수영 */ }
}
의존관계 역전 원칙 DIP (Dependency Inversion Principle)
구체적인 클래스에 의존하지 말고, 인터페이스나 추상 클래스에 의존하도록 설계해야 한다.
interface Notifier {
void send(String message);
}
class EmailNotifier implements Notifier {
@Override
public void send(String message) {
System.out.println("Email 알림: " + message);
}
}
class NotificationService {
private Notifier notifier;
public NotificationService(Notifier notifier) {
this.notifier = notifier;
}
public void sendNotification(String message) {
notifier.send(message);
}
}
💡 객체 지향의 핵심은 다형성에 있으며, 다형성 만으로는 OCP, DIP를 지킬 수 없다.
📚 Spring은 다형성 만으로 해결하지 못했던 OCP, DIP 원칙을 IOC, DI를 통해 지킬 수 있게 도와준다.
📚 Spring 애플리케이션에서 객체(Bean)를 생성, 관리, 소멸하는 역할을 담당한다. 설정 파일이나 Annotation을 읽어 Bean을 생성하고 주입하는 모든 과정을 컨트롤한다.
Java의 객체 생성
사용하는 클래스에서 직접 생성한다.
Spring Container를 사용하면 인터페이스에만 의존하는 설계가 가능하다.
Spring Container의 종류
BeanFactory: Spring Container의 최상위 인터페이스ApplicationContext: BeanFactory 확장형, Application 개발에 필요한 기능 추가 제공📚 Spring 컨테이너가 관리하는 객체이다. 자바 객체 자체는 특별하지 않지만, Spring이 관리하는 순간부터 Bean이 된다. Spring은 Bean의 생성, 초기화, 의존성 주입을 관리한다.
Spring Bean이란?
Spring Bean의 특징
Bean 등록 방법
객체의 생성과 관리 권한을 개발자가 아닌 Spring 컨테이너가 담당하는 것.
IoC 개념
IoC 예시
요리사(개발자)가 재료를 직접 준비하지 않고, Chef가 필요한 재료(Bean)를 알아서 관리하고 제공한다.
Spring이 객체 간의 의존성을 자동으로 주입해주는 기법. IoC를 구현하는 방식 중 하나.
DI 개념
DI 예시
Chef가 요리사에게 필요한 재료를 자동으로 가져다주듯, Spring이 필요한 객체를 자동으로 주입.
코드 예시 (Spring 없이 직접 관리)
MyRepository repo = new MyRepositoryImpl();
MyService myService = new MyServiceImpl(repo);
myService.doSomething();
코드 예시 (Spring을 통한 IoC, DI)
@Service
public class MyIocService implements MyService {
private final MyRepository myRepository;
@Autowired
public MyIocService(MyRepository myRepository) {
this.myRepository = myRepository;
}
// ...
}
ApplicationContext context = new AnnotationConfigApplicationContext(MyIocApp.class);
MyService service = context.getBean(MyService.class);
클래스의 인스턴스가 오직 하나만 생성되도록 보장하는 디자인 패턴.
💡 Spring은 요청할 때마다 새로운 객체를 생성하는 옵션도 제공.
상태를 유지하는(Stateful) 객체는 주의 필요
상태를 유지하는 싱글톤 객체는 데이터 불일치 및 동시성 문제를 유발할 수 있음.
상태 유지 예시
public class StatefulSingleton {
private int value;
public void setValue(int value) {
this.value = value;
}
public int getValue() {
return this.value;
}
}
value 값을 변경하면, 다른 클라이언트에게도 변경된 값이 영향을 미침.⚠️ Spring Bean은 항상 무상태(stateless)로 설계해야 함.