728x90

🏆 목차.

  1. 서론
  2. 본론
  3. 결론

 

🛒 서론

 

이번 글에서는 객체지향 프로그래밍(Object-Oriented Programming, OOP)에 가장 중요한  SOLID 원칙에 대해 이야기해보려고 합니다. 이 SOLID 원칙들이 무엇인지, 왜 중요한지, 그리고 실제 코드에서 어떻게 적용할 수 있는지에 대해 알아보겠습니다.

 

🎨 본론

 

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

정의
클래스는 단 하나의 책임만 가져야 한다.
즉, 클래스는 하나의 기능 또는 역할만을 가져야 한다.


 

잘못된 코드 예시)

public class ReportGenerator
{
    public string GenerateReport()
    {
        // 리포트 생성 로직
    }

    public void SaveToFile(string report)
    {
        // 리포트 파일 저장 로직
    }
}

 

위 예시 코드에서는 단일 책임 원칙을 위반하고 있습니다.

"ReportGenerator" 클래스는 보고서 생성과 파일 저장 두 가지 책임을 가지고 있습니다.

이를 분리하여 각각의 책임을 가진 클래스로 나누는 것이 좋습니다.

 

올바른 코드 예시)

public class ReportGenerator
{
    public string GenerateReport()
    {
        // 리포트 생성 로직
    }
}

public class ReportSaver
{
    public void SaveToFile(string report)
    {
        // 리포트 파일 저장 로직
    }
}

 

 

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

정의
소프트웨어 구성 요소(클래스, 모듈, 함수 등)는 확장에는 열려 있어야 하지만, 변경에는 닫혀 있어야 한다.
즉, 기존 코드를 수정하지 않고 기능을 확장할 수 있어야 한다.

 

잘못된 코드 예시)

public class Rectangle{
    public float width;
    public float height;
}
public class Circle{
    public float radius;
}
public class AreaCalculator{
    public float GetRectangleArea(Rectangle rectangle)
    {
    	return rectangle.width * rectangle.height;
    }
    public float GetCircleArea(Circle circle)
    {
    	return circle.radius * circle.radius * Mathf.PI;
    }
}

 

위 예시 코드에서는 개방-폐쇄 원칙을 위반하고 있습니다.

별 문제없어 보이지만 만약 삼각형 등등 다양한 도형을 추가해야 한다면 " AreaCalculator  "클래스를 수정해야 합니다.

 

이 경우에는 인터페이스 또는 추상클래스를 사용하여 개방-폐쇄 원칙을 준수할 수 있습니다.

 

올바른 코드 예시)

public abstract class Shape{
	public abstract float CalculateArea();
}
public class Rectangle : Shape{
    public float width;
    public float height;
    public override float CalculateArea(){
    	return width * height;
    }
}
public class Circle : Shape{
    public float radius;
    public override float CalculateArea(){
    	return radius * radius * Mathf.PI;
    }
}

public class AreaCalculator{
    public float GetArea(Shape shape){
    	return shape.CalculateArea();
    }
}

 

 

올바른 코드에서는 새로운 도형이 추가되면 해당 클래스만 추가하면 됩니다.

Shape 클래스 및 AreaCalulator 클래스는 수정이 필요하지 않기 때문에 개방-폐쇄 원칙을 준수한 코드라고 볼 수 있습니다.

 

플랫포머 등 다양한 게임에서 오브젝트와 상호작용하는 경우에도 이와 동일한 방식으로 인터페이스, 추상클래스를 활용하여 간단하게 구현이 가능합니다.

 

 

 

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

정의
파생 클래스는 기본 클래스를 대체할 수 있어야 한다.
즉, 자식 클래스는 부모 클래스의 기능을 대체할 수 있어야 하며, 부모 클래스에서 기대하는 모든 동작을 자식 클래스도 수행할 수 있어야 한다.
하위 클래스를 강력하고 유연하게 만드는 원칙.


 

잘못된 코드 예시)

public class Vehicle
{
    public virtual void DriveStraight(){..}
    public virtual void TurnLeft(){..}
    public virtual void TurnRight(){..}
}

public class Car : Vehicle
{
    public override void DriveStraight(){..}
    public override void TurnLeft(){..}
    public override void TurnRight(){..}
}
public class Train : Vehicle
{
    public override void DriveStraight(){..}
    public override void TurnLeft()
    {
        throw new NotImplementedException("기차는 좌회전을 할 수 없습니다");
    }
    public override void TurnRight()
    {
        throw new NotImplementedException("기차는 우회전을 할 수 없습니다.");
    }
}

 

위 예시 코드에서는 리스코프 치환 원칙을 위배하고있습니다.

 " Vehicle "이라는 부모 클래스가 DriveStraight, TurnLeft, TurnRight라는 세 가지 기능을 가지고 있습니다.

자동차(Car) 클래스와 기차(Train) 클래스가 이 부모 클래스를 상속받습니다. 그러나 기차클래스는 좌회전, 우회전이 불가능해 해당 기능을 사용하지 않으므로 LSP를 위반하고 있습니다.

 

올바른 코드 예시)

public interface IMovable
{
    void DriveStraight();
}

public interface ITurnable
{
    void TurnLeft();
    void TurnRight();
}

public class Car : IMovable, ITurnable
{
    public virtual void DriveStraight(){..}
    public virtual void TurnLeft(){..}
    public virtual void TurnRight(){..}
}
public class Train : IDriveStraight
{
    public virtual void DriveStraight(){..}
}

 

LSP를 준수하기 위해, Vehicle 클래스를 수정하여 TurnLeft와 TurnRight 메서드를 자식 클래스에서 선택적으로 구현할 수 있도록 인터페이스를 사용하여 분리했습니다.

이를 통해 Train 클래스는 DriveStraight 메서드만 구현하고, Car 클래스는 모든 기능을 구현할 수 있습니다.

 

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

정의
인터페이스는 클라이언트가 자신이 사용하지 않는 메서드에 의존하지 않도록 분리되어야 한다.
즉, 인터페이스는 특정 클라이언트의 필요에 맞춰 작게 분리되어야 한다.

 

잘못된 코드 예시)

public interface IWorker
{
    void Work();
    void Eat();
}

 

위 예시 코드에서는 인터페이스 분리 원칙을 위배하고 있습니다.

IWorker 인터페이스를 구현하는 클래스가 필요하지 않은 메서드에 의존하게 만듭니다. 이를 분리하여 인터페이스를 작게 유지해야 합니다.

 

올바른 코드 예시)

public interface IWorker
{
    void Work();
}

public interface IEater
{
    void Eat();
}

 

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

정의
고수준 모듈은 저수준 모듈에 의존해서는 안 되며, 둘 다 추상화에 의존해야 한다.
즉, 구체적인 구현이 아닌 추상화된 인터페이스에 의존해야 한다.

 

잘못된 코드 예시)

public class Light
{
    public void TurnOn() {...}
    public void TurnOff() {...}
}

public class Switch
{
    private Light _light;

    public Switch(Light light)
    {
        _light = light;
    }

    public void Operate(bool on)
    {
        if (on) _light.TurnOn();
        else _light.TurnOff();
    }
}

 

위 예시 코드는 의존성 역전 원칙을 위배하고 있습니다.

Switch 클래스가 Light 클래스에 강하게 결합되어 있습니다. 이를 추상화된 인터페이스에 의존하도록 변경해야 합니다.

 

올바른 코드 예시)

public interface ISwitchable
{
    void TurnOn();
    void TurnOff();
}

public class Light : ISwitchable
{
    public void TurnOn() {..}
    public void TurnOff() {..}
}

public class Switch
{
    private ISwitchable _device;

    public Switch(ISwitchable device)
    {
        _device = device;
    }

    public void Operate(bool on)
    {
        if (on) _device.TurnOn();
        else _device.TurnOff();
    }
}

 

 

🎯 결론

 

SOLID 원칙은 객체지향 설계의 가장 중요한 원칙입니다.

해당 원칙을 준수하며 코드를 작성했을 때,

 

- 코드의 안정성 : 기존 코드를 변경하지 않고 새로운 기능을 추가 가능
- 코드의 재사용 : 확장 가능한 설계를 통해 재사용 가능한 컴포넌트를 작성 가능
- 유지보수 : 코드를 쉽게 확장할 수 있어 유지보수가 용이
디버깅 : 코드가 분리되고 확장 가능하게 설계되어 단위 테스트 작성이 용이
단일 책임 원칙의 장점
- 가독성 : 클래스가 짧아져 읽기 쉬워짐
- 확장성 : 작은 클래스로부터 상속이 쉬움
재사용성 : 부분에서 재사용할 수 있도록 작고 모듈식으로 설계 가능

 

이러한 이점이 있습니다.

728x90

+ Recent posts