SOLID 원칙은 다음과 같습니다:
- Single Responsibility Principle (SRP, 단일 책임 원칙)
- Open-Closed Principle (OCP, 개방-폐쇄 원칙)
- Liskov Substitution Principle (LSP, 리스코프 치환 원칙)
- Interface Segregation Principle (ISP, 인터페이스 분리 원칙)
- Dependency Inversion Principle (DIP, 의존성 역전 원칙)
1. Single Responsibility Principle (SRP, 단일 책임 원칙)
단일 책임 원칙은 클래스는 단 한 개의 책임을 가져야 한다는 원칙입니다. 이를 통해 클래스의 변경이 한가지 이유로 제한될 수 있습니다.
예를 들어, Book 클래스를 만든다고 가정해봅시다.
class Book {
private String title;
private String author;
// Getters and setters
void save() {
// Save book to database
}
}
위 Book 클래스는 책 정보를 저장하고, 데이터베이스에 책을 저장하는 두 가지 책임을 가지고 있습니다. 그러나 SRP에 따르면, 각 클래스는 하나의 책임만 가지는 것이 좋습니다. 따라서 아래와 같이 클래스를 나눌 수 있습니다.
class Book {
private String title;
private String author;
// Getters and setters
}
class BookDatabase {
void save(Book book) {
// Save book to database
}
}
2. Open-Closed Principle (OCP, 개방-폐쇄 원칙)
개방-폐쇄 원칙은 기존 코드를 변경하지 않고 확장할 수 있어야 한다는 원칙입니다. 이를 위해 추상화와 다형성이 사용됩니다.
interface Shape {
double area();
}
class Rectangle implements Shape {
private double width;
private double height;
// Getters, setters and constructor
@Override
public double area() {
return width * height;
}
}
class Circle implements Shape {
private double radius;
// Getters, setters and constructor
@Override
public double area() {
return Math.PI * Math.pow(radius, 2);
}
}
class AreaCalculator {
double totalArea(Shape[] shapes) {
double total = 0;
for (Shape shape : shapes) {
total += shape.area();
}
return total;
}
}
위 코드에서 새로운 도형을 추가하더라도 AreaCalculator는 수정할 필요가 없습니다. 각 도형은 Shape 인터페이스를 구현하므로, AreaCalculator는 단순히 area() 메소드를 호출합니다. 이러한 방식으로 OCP를 준수하고 있습니다.
3. Liskov Substitution Principle (LSP, 리스코프 치환 원칙)
리스코프 치환 원칙은 부모 클래스를 자식 클래스로 치환할 수 있어야 한다는 원칙입니다.
예를 들어, 아래와 같은 클래스가 있을 때,
class Bird {
void fly() {
// Implementation
}
}
class Duck extends Bird {
}
class Ostrich extends Bird {
}
Ostrich 클래스(타조)는 Bird 클래스의 fly 메소드를 사용할 수 없습니다(타조는 날 수 없습니다). 따라서, 이런 경우에는 Bird 클래스를 두 개의 서브 클래스로 분리하는 것이 좋습니다.
class Bird {
}
class FlyingBird extends Bird {
void fly() {
// Implementation
}
}
class Duck extends FlyingBird {
}
class Ostrich extends Bird {
}
4. Interface Segregation Principle (ISP, 인터페이스 분리 원칙)
인터페이스 분리 원칙은 클라이언트가 자신이 사용하지 않는 메서드에 의존하지 않아야 한다는 원칙입니다. 즉, 매우 큰 인터페이스 보다는 여러 개의 작은 인터페이스가 낫다는 원칙입니다.
예를 들어, 아래와 같은 Worker 인터페이스가 있다고 가정해봅시다.
interface Worker {
void work();
void eat();
}
하지만 일부 일꾼들은 식사 기능을 필요로 하지 않을 수 있습니다. 이런 경우에는 인터페이스를 분리하는 것이 좋습니다.
interface Worker {
void work();
}
interface Eater {
void eat();
}
class HumanWorker implements Worker, Eater {
public void work() {
// Implementation
}
public void eat() {
// Implementation
}
}
class RobotWorker implements Worker {
public void work() {
// Implementation
}
}
5. Dependency Inversion Principle (DIP, 의존성 역전 원칙)
의존성 역전 원칙은 상위 모듈이 하위 모듈에 의존하면 안 된다는 원칙입니다. 둘 다 추상화에 의존해야 합니다. 이 원칙을 준수함으로써 시스템의 결합도를 줄일 수 있습니다.
예를 들어, 아래 코드는 MySQLDatabase 클래스에 의존하는 User 클래스를 보여줍니다.
class MySQLDatabase {
void save(User user) {
// Implementation
}
}
class User {
private MySQLDatabase db;
User(MySQLDatabase db) {
this.db = db;
}
void save() {
db.save(this);
}
}
하지만 이렇게 구현하면 User 클래스는 MySQLDatabase 클래스에 강하게 의존하게 되고, 데이터베이스를 변경하려면 User 클래스도 수정해야 합니다. 따라서 이런 경우에는 인터페이스를 사용하여 의존성을 역전시킬 수 있습니다.
interface Database {
void save(User user);
}
class MySQLDatabase implements Database {
@Override
public void save(User user) {
// Implementation
}
}
class User {
private Database db;
User(Database db) {
this.db = db;
}
void save() {
db.save(this);
}
}
이렇게 하면, User 클래스는 Database 인터페이스에만 의존하게 되어 특정 데이터베이스에 강하게 결합되지 않습니다.
이처럼 SOLID 원칙은 소프트웨어의 유지 보수성과 확장성을 향상시키는 데 큰 도움을 줍니다. 이 원칙들을 지키면서 코딩하려면 초기에는 노력이 필요하지만, 결국에는 장기적인 이점을 얻게 될 것입니다.
'Java' 카테고리의 다른 글
자바 컬렉션 프레임워크 기초: List, Set, Map 이해하기 (0) | 2023.05.25 |
---|---|
자바 멀티스레딩 탐구: 동시성을 통한 효율적인 프로그래밍 (0) | 2023.05.25 |
자바에서 @Autowire 대신 생성자 또는 @requiredargsconstructor 어노테이션 사용하기 : 그 이유와 특징 (0) | 2023.05.21 |
자바의 객체 지향 특징 4가지 (0) | 2023.05.15 |
가비지 컬렉션(Garbage Collection) 종류 (0) | 2023.05.04 |