IT 개발 라이프/Back_End

객체지향 설계의 5원칙 (SOLID)

10Biliion 2024. 12. 11. 16:45

객체지향 프로그래밍에서 설계의 품질을 높이기 위해 사용하는 5가지 원칙인 SOLID 원칙에 대해 알아보겠습니다. SOLID 원칙은 유지보수성과 확장성을 높이고, 코드의 품질을 개선하는 데 도움을 줍니다. 하나씩 자세히 살펴보겠습니다! ✨

1. 단일 책임 원칙 (Single Responsibility Principle, SRP) 🛠️

"클래스는 단 하나의 책임만 가져야 한다."

  • 하나의 클래스는 하나의 기능이나 역할만 담당해야 합니다.
  • 클래스가 여러 책임을 가지게 되면, 하나의 변경이 다른 기능에 영향을 미칠 수 있습니다.
  • 예시:위 코드에서 calculateTotalprintInvoice는 다른 책임을 가지므로, 별도의 클래스로 분리하는 것이 좋습니다.
  •  
public class Invoice {
	public void calculateTotal() {
    	/* 총액 계산 */
    }
    public void printInvoice() {
    	/* 청구서 출력 */
    }
}

2. 개방-폐쇄 원칙 (Open-Closed Principle, OCP) 🚪🔒

"소프트웨어 엔티티는 확장에는 열려 있어야 하고, 변경에는 닫혀 있어야 한다."

  • 기존 코드를 수정하지 않고도 기능을 추가할 수 있어야 합니다.
  • 확장성을 높이기 위해 인터페이스와 상속을 적극 활용합니다.
  • 예시:새로운 도형을 추가할 때 기존 코드를 변경하지 않고 새로운 클래스를 추가하면 됩니다.
  •  
public interface Shape { 
	double calculateArea(); 
} 

public class Circle implements Shape {
	private double radius;
    public double calculateArea() { 
    	return Math.PI * radius * radius;
    }
} 

public class Rectangle implements Shape {
	private double width, height;
    public double calculateArea() {
    	return width * height;
    }
}

3. 리스코프 치환 원칙 (Liskov Substitution Principle, LSP) 🔄

"서브타입은 언제나 자신의 기반 타입으로 대체할 수 있어야 한다."

  • 부모 클래스의 기능을 자식 클래스가 완벽히 수행해야 합니다.
  • 자식 클래스가 부모 클래스의 규칙을 위반하면 안 됩니다.
  • 예시:펭귄은 날 수 없는 새이므로 Bird를 상속받아 fly 메서드를 오버라이딩하지 않는 방식으로 설계를 바꿔야 합니다.
  •  
public class Bird {
	public void fly() {
    	System.out.println("Flying");
    }
}

public class Penguin extends Bird {
	@Override public void fly() {
    	throw new UnsupportedOperationException("Penguins can't fly");
    }
}

4. 인터페이스 분리 원칙 (Interface Segregation Principle, ISP) 🧩

"인터페이스는 구체적인 클라이언트를 기준으로 분리되어야 한다."

  • 클라이언트는 자신이 사용하지 않는 메서드에 의존하지 않아야 합니다.
  • 하나의 커다란 인터페이스보다는 여러 개의 작은 인터페이스로 나누는 것이 좋습니다.
  • 예시:위 코드는 fly 메서드가 필요 없는 Dog 클래스에 비효율적입니다. 이를 인터페이스로 분리해야 합니다.
public interface Eater {
	void eat();
}

public interface Flyer {
	void fly();
}

public class Dog implements Eater {
	public void eat() {
    	System.out.println("Dog is eating");
    }
}
public interface Animal {
	void eat();
    void fly();
}

public class Dog implements Animal {
	public void eat() {
    	System.out.println("Dog is eating");
    }
    public void fly() {
    	throw new UnsupportedOperationException("Dogs can't fly");
    }
}

5. 의존 역전 원칙 (Dependency Inversion Principle, DIP) 🔄🔌

"상위 모듈은 하위 모듈에 의존해서는 안 된다. 둘 다 추상화에 의존해야 한다."

  • 구현이 아닌 추상화(인터페이스)에 의존해야 합니다.
  • 의존성을 주입(DI, Dependency Injection)하여 유연성을 높입니다.
  • 예시:위 코드는 SwitchLight 구현체에 의존하므로, 인터페이스를 활용하는 방식으로 개선해야 합니다.
public interface Device {
	void turnOn();
}

public class Light implements Device {
	public void turnOn() {
    	System.out.println("Light on");
    }
}

public class Switch {
	private Device device;
    
    public Switch(Device device) {
    	this.device = device;
    }
    public void toggle() {
    	device.turnOn();
    }
}
public class Light {
	public void turnOn() {
    	System.out.println("Light on");
    }
}

public class Switch {
	private Light light;
    
    public Switch(Light light) {
    	this.light = light;
    }
    public void toggle() {
    	light.turnOn();
    }
}