소개
2년간의 사이드 프로젝트 경험을 통해 많은 것을 배울 수 있었습니다. 특히, 소프트웨어 설계 원칙의 중요성을 깨달았습니다. 좋은 설계 원칙을 따라 개발된 소프트웨어는 유지보수성이 높고, 확장성이 뛰어나며, 버그가 적습니다. 본 블로그 글의 목적은 내가 2년간의 사이드 프로젝트에서 얻은 7가지 소프트웨어 설계 원칙을 공유하는 것입니다.
1. 단일 책임 원칙 (Single Responsibility Principle, SRP)
SRP는 한 클래스가 한 가지 책임만 가져야 한다는 원칙입니다. 예를 들어, 사용자 관리 클래스는 사용자 등록, 사용자 조회, 사용자 수정, 사용자 삭제 등의 책임을 모두 가지고 있으면 안됩니다. 각 책임은 별도의 클래스로 분리되어야 합니다.
public class UserManager {
public void registerUser(User user) {
// 사용자 등록 로직
}
public User getUser(int userId) {
// 사용자 조회 로직
}
public void updateUser(User user) {
// 사용자 수정 로직
}
public void deleteUser(int userId) {
// 사용자 삭제 로직
}
}
SRP의 장점은 코드의 가독성과 유지보수성이됩니다. 또한, 버그를 찾고 수정하기 쉽습니다.
2. 개방-폐쇄 원칙 (Open-Closed Principle, OCP)
OCP는 클래스는 확장에는 열려 있어야 하지만, 수정에는 닫혀 있어야 한다는 원칙입니다. 예를 들어, 사용자 관리 클래스가 새로운 사용자 유형을 추가해야 하는 경우, 기존 코드를 수정하지 말고 확장하여 추가해야 합니다.
public abstract class User {
public abstract void register();
}
public class NormalUser extends User {
@Override
public void register() {
// 일반 사용자 등록 로직
}
}
public class PremiumUser extends User {
@Override
public void register() {
// 프리미엄 사용자 등록 로직
}
}
OCP의 장점은 코드의 확장성이되고, 기존 코드를 수정하지 않아도 됩니다.
3. 리스코프 치환 원칙 (Liskov Substitution Principle, LSP)
LSP는 상위 타입의 객체를 하위 타입의 객체로 치환해도 프로그램의 정상적인 동작에 영향을 주지 않도록 해야 한다는 원칙입니다. 예를 들어, 사용자 관리 클래스가 사용자 유형을 상속받은 클래스로 치환해도, 프로그램의 동작에 영향을 주지 않도록 해야 합니다.
public class User {
public void register() {
// 사용자 등록 로직
}
}
public class NormalUser extends User {
@Override
public void register() {
// 일반 사용자 등록 로직
}
}
public class PremiumUser extends User {
@Override
public void register() {
// 프리미엄 사용자 등록 로직
}
}
LSP의 장점은 코드의 안정성이되고, 버그가 적습니다.
4. 인터페이스 분리 원칙 (Interface Segregation Principle, ISP)
ISP는 클라이언트가 사용하지 않는 인터페이스는 분리되어야 한다는 원칙입니다. 예를 들어, 사용자 관리 클래스가 사용자 등록, 사용자 조회, 사용자 수정, 사용자 삭제 등의 인터페이스를 가지고 있다면, 각 인터페이스를 별도의 클래스로 분리되어야 합니다.
public interface RegisterUser {
void registerUser(User user);
}
public interface GetUser {
User getUser(int userId);
}
public interface UpdateUser {
void updateUser(User user);
}
public interface DeleteUser {
void deleteUser(int userId);
}
ISP의 장점은 코드의 가독성과 유지보수성이됩니다.
5. 의존성 역전 원칙 (Dependency Inversion Principle, DIP)
DIP는 고수준 모듈이 저수준 모듈에 의존하지 말고, 둘 다 추상화에 의존하도록 해야 한다는 원칙입니다. 예를 들어, 사용자 관리 클래스가 데이터베이스 클래스에 의존해서는 안되고, 둘 다 사용자 관리 인터페이스에 의존해야 합니다.
public interface UserManager {
void registerUser(User user);
}
public class DatabaseUserManager implements UserManager {
@Override
public void registerUser(User user) {
// 데이터베이스 사용자 등록 로직
}
}
public class FileUserManager implements UserManager {
@Override
public void registerUser(User user) {
// 파일 사용자 등록 로직
}
}
DIP의 장점은 코드의 확장성이되고, 유지보수성이 높습니다.
6. 컴포지트 패턴 (Composite Pattern)
컴포지트 패턴은 객체를 트리 구조로 구성하여, 클라이언트 코드에서 개별 객체와 객체의 컨테이너를 동일하게 다룰 수 있도록 하는 패턴입니다. 예를 들어, 사용자 관리 클래스가 사용자들의 목록을 관리해야 하는 경우, 컴포지트 패턴을 사용하여 사용자들을 트리 구조로 구성할 수 있습니다.
public interface User {
void register();
}
public class NormalUser implements User {
@Override
public void register() {
// 일반 사용자 등록 로직
}
}
public class CompositeUser implements User {
private List<User> users = new ArrayList<>();
public void add(User user) {
users.add(user);
}
@Override
public void register() {
for (User user : users) {
user.register();
}
}
}
컴포지트 패턴의 장점은 코드의 확장성이되고, 유지보수성이 높습니다.
7. 관찰자 패턴 (Observer Pattern)
관찰자 패턴은 객체의 상태 변경을 관찰하는 객체가 상태 변경을 감지하여 자동으로 반응하도록 하는 패턴입니다. 예를 들어, 사용자 관리 클래스가 사용자들의 상태 변경을 감지하여 자동으로 사용자 목록을 갱신해야 하는 경우, 관찰자 패턴을 사용할 수 있습니다.
public interface Observer {
void update(User user);
}
public class User {
private List<Observer> observers = new ArrayList<>();
public void addObserver(Observer observer) {
observers.add(observer);
}
public void notifyObservers() {
for (Observer observer : observers) {
observer.update(this);
}
}
}
관찰자 패턴의 장점은 코드의 확장성이되고, 유지보수성이 높습니다.
결론
이상으로 7가지 소프트웨어 설계 원칙에 대해 알아봤습니다. 이 원칙들을 따라 개발된 소프트웨어는 유지보수성이 높고, 확장성이 뛰어나며, 버그가 적습니다. 앞으로의 계획은 더 많은 소프트웨어 설계 원칙을 학습하여, 더욱 좋은 소프트웨어를 개발하는 것입니다.
'개발일기' 카테고리의 다른 글
| 코딩 입문자 실수 5가지 (0) | 2026.04.03 |
|---|---|
| 코딩 테스트 90% 해결 전략 (0) | 2026.04.02 |
| 2026년 백엔드 개발자 성공을 위한 5가지 필수 스킬 (0) | 2026.04.01 |
| Nuxt.js 3로 빠른 웹앱 개발하기 (0) | 2026.03.31 |
| FastAPI로 빠른 REST API 개발 (0) | 2026.03.30 |