SOLID는 객체 지향 프로그래밍에서 다섯 가지 중요한 설계 원칙을 나타내는 약어입니다. 이러한 원칙은 소프트웨어를 더 견고하고 유지보수 가능하며 확장 가능하도록 만드는 데 도움을 줍니다. SOLID 원칙은 다음과 같습니다:
단일 책임 원칙 (Single Responsibility Principle - SRP): 클래스는 하나의 책임만 가져야 하며, 클래스가 변경되어야 하는 이유는 오직 하나여야 합니다. 이것은 클래스가 변경되어야 하는 이유를 최소화하여 클래스를 유지보수하기 쉽게 만듭니다.
개방-폐쇄 원칙 (Open-Closed Principle - OCP): 소프트웨어 엔티티(클래스, 모듈, 함수 등)는 확장에 대해 열려 있어야 하고, 수정에 대해서는 닫혀 있어야 합니다. 이것은 새로운 기능을 추가할 때 기존 코드를 변경하지 않고 확장할 수 있게 해줍니다.
리스코프 치환 원칙 (Liskov Substitution Principle - LSP): 하위 클래스는 상위 클래스를 대체할 수 있어야 합니다. 이것은 상속 관계에서 서브클래스가 슈퍼클래스를 대체하면서 프로그램이 정상적으로 동작해야 함을 의미합니다.
인터페이스 분리 원칙 (Interface Segregation Principle - ISP): 클라이언트는 사용하지 않는 인터페이스에 의존하지 않아야 합니다. 이것은 인터페이스를 작고 구체적으로 만들어서 클라이언트가 필요한 메서드만 구현하도록 하는 원칙입니다.
의존성 역전 원칙 (Dependency Inversion Principle - DIP): 고수준 모듈은 저수준 모듈에 의존해서는 안 되며, 둘 모두 추상화에 의존해야 합니다. 이것은 추상화를 통해 시스템의 유연성을 높이고 결합도를 낮춥니다.
// SRP 위반 예제
class Employee {
private String name;
private double salary;
public Employee(String name, double salary) {
this.name = name;
this.salary = salary;
}
public void calculateSalary() {
// 급여 계산 로직
}
public void generateReport() {
// 보고서 생성 로직
}
}
위의 코드에서 Employee 클래스는 두 가지 다른 책임을 가지고 있습니다. 하나는 급여 계산(calculateSalary)이고 다른 하나는 보고서 생성(generateReport)입니다. 이는 SRP를 위반하는 예제입니다.
SRP를 준수하는 코드로 수정하면 다음과 같습니다.
// SRP 준수 예제
class Employee {
private String name;
private double salary;
public Employee(String name, double salary) {
this.name = name;
this.salary = salary;
}
public void calculateSalary() {
// 급여 계산 로직
}
}
class ReportGenerator {
public void generateReport(Employee employee) {
// 보고서 생성 로직
}
}
여기서 Employee 클래스는 급여 계산과 관련된 책임만을 가지며, 보고서 생성은 별도의 ReportGenerator 클래스로 분리되었습니다. 이렇게 하면 각 클래스는 하나의 책임만 갖게 되어 SRP를 준수하게 됩니다.
// 도형을 그리는 인터페이스
interface Shape {
void draw();
}
// 원 클래스
class Circle implements Shape {
public void draw() {
System.out.println("원을 그립니다.");
}
}
// 사각형 클래스
class Rectangle implements Shape {
public void draw() {
System.out.println("사각형을 그립니다.");
}
}
// 그림 그리는 클래스
class Drawing {
public void drawShape(Shape shape) {
shape.draw();
}
}
위의 코드에서 Shape 인터페이스를 구현한 클래스들(Circle과 Rectangle)은 확장에 열려 있습니다. 이것은 새로운 도형 클래스를 추가할 때 기존 코드를 수정하지 않고도 가능합니다. Drawing 클래스는 Shape 인터페이스를 사용하여 도형을 그리는데, 이것은 기존 코드를 수정하지 않고 새로운 도형을 추가할 수 있음을 보여줍니다.
예를 들어, 삼각형 클래스를 추가하려면 다음과 같이 할 수 있습니다.
// 삼각형 클래스
class Triangle implements Shape {
public void draw() {
System.out.println("삼각형을 그립니다.");
}
}
이렇게 하면 Triangle 클래스를 추가하더라도 Drawing 클래스나 기존 도형 클래스를 수정할 필요가 없으므로 OCP를 준수하고 있습니다.
class Bird {
public void fly() {
System.out.println("날아갈 수 있습니다.");
}
}
class Sparrow extends Bird {
// Sparrow는 Bird를 확장하면서 추가적인 행위를 정의하지 않음
}
class Ostrich extends Bird {
public void fly() {
// 타조는 날지 못하므로 오버라이딩하여 구현을 변경
System.out.println("날지 못합니다.");
}
}
public class Main {
public static void main(String[] args) {
Bird sparrow = new Sparrow();
Bird ostrich = new Ostrich();
sparrow.fly(); // "날아갈 수 있습니다." 출력
ostrich.fly(); // "날지 못합니다." 출력
}
}
LSP를 준수하면 프로그램의 유연성이 향상되며, 다형성을 활용하여 코드를 더 쉽게 확장하고 유지보수할 수 있게 됩니다.
// ISP 위반 예제
interface Worker {
void work();
void eat();
}
class Human implements Worker {
public void work() {
// 일하는 로직
}
public void eat() {
// 식사하는 로직
}
}
class Robot implements Worker {
public void work() {
// 일하는 로직
}
public void eat() {
// 로봇은 먹지 않는데도 먹는 메서드를 구현해야 함
}
}
위의 코드에서 Worker 인터페이스는 두 가지 메서드인 work와 eat을 가지고 있습니다. 그런데 Robot 클래스는 eat 메서드를 구현해야 하는데, 로봇은 먹지 않으므로 이는 ISP를 위반하는 예제입니다.
ISP를 준수하는 코드로 수정하면 다음과 같습니다:
// ISP 준수 예제
interface Workable {
void work();
}
interface Eatable {
void eat();
}
class Human implements Workable, Eatable {
public void work() {
// 일하는 로직
}
public void eat() {
// 식사하는 로직
}
}
class Robot implements Workable {
public void work() {
// 일하는 로직
}
}
아래의 Java 코드 예제는 DIP를 준수하는 예제를 보여줍니다. 이 예제에서는 LightBulb 클래스가 Switch 인터페이스에 의존하고 있습니다.
// 저수준 모듈
class LightBulb {
public void turnOn() {
System.out.println("전구가 켜집니다.");
}
public void turnOff() {
System.out.println("전구가 꺼집니다.");
}
}
// 고수준 모듈
interface Switch {
void operate();
}
class RemoteControl implements Switch {
private LightBulb bulb;
public RemoteControl(LightBulb bulb) {
this.bulb = bulb;
}
public void operate() {
bulb.turnOn();
}
}
// 저수준 모듈
interface Switchable {
void turnOn();
void turnOff();
}
class LightBulb implements Switchable {
public void turnOn() {
System.out.println("전구가 켜집니다.");
}
public void turnOff() {
System.out.println("전구가 꺼집니다.");
}
}
// 고수준 모듈
interface Switch {
void operate();
}
class RemoteControl implements Switch {
private Switchable device;
public RemoteControl(Switchable device) {
this.device = device;
}
public void operate() {
device.turnOn();
}
}