좋은 객체지향 설계를 위해 사용되는 다섯 가지 원칙을 SOLID라고 합니다. SOLID는 각 원칙의 첫 글자를 따서 만들어진 약어입니다. 이 원칙들은 소프트웨어의 유지보수성, 재사용성, 확장성을 향상시키는 데 도움이 되는 지침을 제공합니다.
1. 단일 책임 원칙 (Single Responsibility Principle - SRP)
하나의 클래스는 하나의 책임만 가져야 합니다. 즉, 클래스는 변경의 이유가 하나여야 합니다. 이는 클래스가 특정한 기능이나 역할에 집중되도록 하여 코드의 응집성을 높이고, 변경 사항이 발생할 때 해당 클래스만 수정하면 되도록 합니다. 즉 변경사항이 발생할 때 파급효과가 적다면 단일책임원칙을 잘 따른것으로 볼 수 있습니다.
아래는 예시코드입니다.
SRP 위반 예시)
class Report {
void generateReport() {
// 보고서 생성 코드
}
void saveToFile() {
// 파일에 보고서 저장 코드
}
}
SRP를 적용한 예시)
class Report {
void generateReport() {
// 보고서 생성 코드
}
}
class ReportSaver {
void saveToFile(Report report) {
// 파일에 보고서 저장 코드
}
}
2. 개방-폐쇄 원칙 (Open-Closed Principle - OCP)
소프트웨어 개체(클래스, 모듈, 함수 등)는 확장에는 열려 있어야 하고, 수정(변)에는 닫혀 있어야 합니다. 이는 새로운 기능을 추가할 때 기존의 코드를 변경하지 않고 확장할 수 있도록 하는 원칙입니다. 주로 추상화와 다형성을 통해 이를 구현합니다.
아래는 예시코드입니다.
OCP 위반 예시)
class Rectangle {
int width;
int height;
}
class AreaCalculator {
double calculateArea(Rectangle rectangle) {
return rectangle.width * rectangle.height;
}
}
OCP를 적용한 예시)
interface Shape {
double calculateArea();
}
class Rectangle implements Shape {
int width;
int height;
public double calculateArea() {
return width * height;
}
}
class Circle implements Shape {
int radius;
public double calculateArea() {
return 3.14 * radius * radius;
}
}
3. 리스코프 치환 원칙 (Liskov Substitution Principle - LSP)
상위 타입의 객체를 하위 타입의 객체로 대체해도 프로그램은 정상적으로 동작해야 합니다. 즉, 상속 관계에서는 하위 클래스가 상위 클래스를 완전히 대체할 수 있어야 합니다. 이를 통해 다형성을 보장하고, 코드의 신뢰성을 높입니다.
아래는 예시코드입니다.
LSP 위반 예시)
class Bird {
void fly() {
// 날다
}
}
class Ostrich extends Bird {
void fly() {
throw new UnsupportedOperationException("타조는 날지 못해요");
}
}
// 클라이언트 코드
void makeBirdFly(Bird bird) {
bird.fly();
}
LSP를 적용한 예시)
abstract class Bird {
abstract void move();
}
class Sparrow extends Bird {
void move() {
System.out.println("참새가 날아갑니다");
}
}
class Ostrich extends Bird {
void move() {
System.out.println("타조가 달립니다");
}
}
// 클라이언트 코드
void makeBirdMove(Bird bird) {
bird.move();
}
4. 인터페이스 분리 원칙 (Interface Segregation Principle - ISP)
클라이언트는 자신이 사용하지 않는 메서드에 의존하지 않아야 합니다. 즉, 하나의 큰 인터페이스보다는 작은 여러 개의 인터페이스가 더 좋습니다. 이는 클래스가 자신이 필요로 하지 않는 기능에 의존하지 않도록 하여 결합도를 낮추고 응집도를 높입니다.
아래는 예시코드입니다.
ISP 위반 예시)
interface Worker {
void work();
void eat();
}
class Manager implements Worker {
public void work() {
// 일하다
}
public void eat() {
// 식사하다
}
}
ISP를 적용한 예시)
interface Workable {
void work();
}
interface Eatable {
void eat();
}
class Worker implements Workable, Eatable {
public void work() {
// 일하다
}
public void eat() {
// 식사하다
}
}
5. 의존 역전 원칙 (Dependency Inversion Principle - DIP)
고수준 모듈은 저수준 모듈에 의존해서는 안 되며, 양쪽 모두 추상화에 의존해야 합니다. 또한, 추상화는 세부 사항에 의존해서는 안 되며, 세부 사항은 추상화에 의존해야 합니다. 이는 추상화를 통해 모듈 간의 결합도를 낮추고, 유연성을 확보합니다.
아래는 예시코드입니다.
DIP 위반 예시)
class LightBulb {
void turnOn() {
// 전구를 켜다
}
void turnOff() {
// 전구를 끄다
}
}
class Switch {
LightBulb bulb;
Switch(LightBulb bulb) {
this.bulb = bulb;
}
void operate() {
// 스위치 동작 코드
bulb.turnOn();
}
}
DIP를 적용한 예시 (스프링을 사용한 의존성 주입)
스프링 프레임워크를 사용하면 런타임 시에 의존성을 주입할 수 있습니다. 이렇게 하면 Switch 클래스가 Switchable 인터페이스에 의존하면서 런타임에 실제 구현체를 주입받을 수 있습니다.
interface Switchable {
void turnOn();
void turnOff();
}
class LightBulb implements Switchable {
public void turnOn() {
// 전구를 켜다
}
public void turnOff() {
// 전구를 끄다
}
}
class Switch {
Switchable device;
Switch(Switchable device) {
this.device = device;
}
void operate() {
// 스위치 동작 코드
device.turnOn();
}
}
이러한 SOLID 원칙들은 객체지향 설계를 통해 유지보수성이 뛰어나고 확장이 용이한 소프트웨어를 개발하는 데 도움을 주는 기본적인 원리들입니다.
'JAVA Programming > Java' 카테고리의 다른 글
[JAVA] 키보드 입력 받기(Scanner) (0) | 2024.07.14 |
---|---|
[JAVA] 메소드 (0) | 2024.07.10 |
[JAVA] 제어문 (0) | 2024.07.09 |
[JAVA] 타입 추론 (0) | 2024.07.09 |
[JAVA] 배열 (0) | 2024.07.09 |