SOLID는 객체 지향 프로그래밍에서 다섯 가지 설계 원칙의 앞 글자를 따서 만든 용어이다. 이러한 원칙들은 소프트웨어의 유연성, 확장성, 재사용성 등을 향상시키는데 중요한 역할을 한다.
① 하나의 클래스는 하나의 책임만 가져야 한다.
② 한 클래스에 너무 많은 책임이 있으면 코드 변경시 다른 책임도 함께 변경될 가능성이 높아지므로 유지보수가 어려워진다.
public class User {
private String name;
private String email;
public void save() {
// save user to database
}
public void sendEmail(String subject, String message) {
// send email to user
}
}
위 코드에서는 User 클래스가 사용자 정보 저장과 이메일 전송 두 가지 책임을 모두 가지고 있다. 이를 개선하기 위해서는 다음과 같이 두 개의 클래스로 분리할 수 있다.
public class User {
private String name;
private String email;
public void save() {
// save user to database
}
}
public class EmailSender {
public void sendEmail(User user, String subject, String message) {
// send email to user
}
}
위 코드에서는 User 클래스는 사용자 정보 저장에만 집중하고, 이메일 전송 기능은 EmailSender 클래스에서 담당하도록 하였다. 이렇게 하면 클래스의 책임이 분리되어 유지보수와 확장성이 높아진다.
① 확장에는 열려 있고, 수정에는 닫혀 있어야 한다.
② 기존 코드를 수정하지 않고도 새로운 기능을 추가할 수 있도록 설계해야 한다.
public interface PaymentMethod {
void pay(double amount);
}
public class CreditCardPayment implements PaymentMethod {
private String cardNumber;
private String cvv;
private String expDate;
public CreditCardPayment(String cardNumber, String cvv, String expDate) {
this.cardNumber = cardNumber;
this.cvv = cvv;
this.expDate = expDate;
}
@Override
public void pay(double amount) {
// credit card payment implementation
}
}
public class PaypalPayment implements PaymentMethod {
private String email;
private String password;
public PaypalPayment(String email, String password) {
this.email = email;
this.password = password;
}
@Override
public void pay(double amount) {
// Paypal payment implementation
}
}
public class Payment {
private PaymentMethod paymentMethod;
public Payment(PaymentMethod paymentMethod) {
this.paymentMethod = paymentMethod;
}
public void processPayment(double amount) {
paymentMethod.pay(amount);
}
}
public class Main {
public static void main(String[] args) {
PaymentMethod paymentMethod = new CreditCardPayment("1234 5678 9012 3456", "123", "01/23");
Payment payment = new Payment(paymentMethod);
payment.processPayment(100.0);
}
}
위 코드에서는 PaymentMethod 인터페이스를 정의하고, 이를 구현하는 CreditCardPayment와 PaypalPayment 클래스를 작성한다. 이후 Payment 클래스에서 PaymentMethod를 의존성 주입(Dependency Injection)으로 받아 처리하는 방식을 사용한다.
이렇게 하면 새로운 결제 수단을 추가할 때, PaymentMethod 인터페이스를 구현하는 클래스를 작성하면 된다. 기존 코드를 수정하지 않아도 되므로 개방 폐쇄 원칙을 잘 지키고 있는 것이다.
① 자식 클래스는 부모 클래스에서 가능한 행위를 수행할 수 있어야 한다.
② 부모 클래스에서 선언한 인터페이스를 자식 클래스에서 모두 충족해야 한다.
① 클라이언트는 자신이 사용하지 않는 메서드에 의존하지 않아야 한다.
② 인터페이스는 클라이언트가 필요로 하는 메서드만 제공해야 한다.
① 추상화에 의존해야지, 구체화에 의존하면 안된다.
② 추상 인터페이스나 추상 클래스와 같은 상위 수준 모듈에 의존해야 한다.
public class LightBulb {
public void turnOn() {
// Light bulb turns on
}
public void turnOff() {
// Light bulb turns off
}
}
public class LightSwitch {
private LightBulb lightBulb;
public LightSwitch() {
this.lightBulb = new LightBulb();
}
public void press() {
if (lightBulb != null) {
lightBulb.turnOn();
}
}
}
위 코드에서 LightSwitch 클래스가 LightBulb 클래스에 의존하고 있다. 즉, LightSwitch 클래스는 LightBulb 클래스가 제공하는 기능을 직접 사용하고 있다. 이는 의존 역전 원칙을 위배하는 코드이다.
public interface Switchable {
void turnOn();
void turnOff();
}
public class LightBulb implements Switchable {
@Override
public void turnOn() {
// Light bulb turns on
}
@Override
public void turnOff() {
// Light bulb turns off
}
}
public class LightSwitch {
private Switchable device;
public LightSwitch(Switchable device) {
this.device = device;
}
public void press() {
if (device != null) {
device.turnOn();
}
}
}
위 코드에서 LightSwitch 클래스는 Switchable 인터페이스에만 의존하도록 변경되었다. 이제 LightSwitch 클래스는 LightBulb 클래스가 아닌 Switchable 인터페이스를 사용하고 있다. 이렇게 함으로써, LightSwitch 클래스는 Switchable 인터페이스를 구현한 다른 클래스들과도 호환되어 사용될 수 있게 되었다.
이러한 원칙들을 잘 지키면 코드의 유지보수성과 가독성, 확장성, 재사용성 등이 개선될 것이다.