SOLID

강상훈·2023년 8월 21일
0

Absctact

  • Don’t reinvent the wheel.
  • 디자인 패턴 → 이미 많은 개발자들이 마주한 이슈들에 대한 결정들
  • 만능 도라에몽은 아니다. 하나의 수단이며 트레이드 오프

KISS: “Keep it simple, stupid”

SOLID: 디자인 패턴의 5가지 핵심 원칙

  • Single responsibility
  • Open-closed
  • Liskov substitution
  • Interface segregation
  • Dependency inversion

Single Responsibility(SRP)

: A class should have one reason to change, just its single responsibility.

  • 각 모듈, 클래스, 함수는 단 하나의 책임만 가진다.
  • 거대 클래스에 비해 작은 다수의 클래스는 설명/이해/실행하기 쉽다.
  • 유니티로 치면 컴포넌트

Player 스크립트를 Player, PlayerSound, PlayerInput, PlayerMovement로 분리, Player에서 하위 컴포넌트를 컨트롤

Open-closed(OCP)

: A classes must be open for extension but closed for modification.

  • 기존의 코드를 수정하지 않고 새로운 기능을 추가할 수 있게 디자인
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;
    }
}
public class Rectangle
{
     public float width;
     public float height;
}
public class Circle
{
    public float radius;
}
  • 새로운 쉐입이 추가되면, 새로운 메소드가 추가되어야 한다.
  • Shape라는 추상 클래스를 만든다.
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();
    }
}
  • 새로운 쉐입이 추가됐을 때 AreaCalculator 클래스를 수정하지 않아도 된다.

Liskov substitution(LSP)

: A derived classes must be substitutable for their base class.

  • 상속의 방향은 자식 클래스가 부모 클래스를 온전히 대체할 수 있어야 한다.
  • Vehicle 클래스 → Car, Train을 RoadVehicle : IMovable, ITurnable, RailVehicle: ITurnable, Car : RoadVehicle, Train : RailVehicle로 의존성 변경
  • 기차가 Move 관련 메소드를 가지고 있지 않아도 되기 때문에 LSP 만족

This way of thinking can be counterintuitive because you have certain assumptions about the real world . In software development, this is called the circle–ellipse problem . Not every actual “is a” relationship translates into inheritance . Remember, you want your software design to drive your class hierarchy, not your prior knowledge of reality .

Interface Segregation(ISP)

: No Clientr should be forced to depend on methos it does not use.

  • 인터페이스는 필요한 기능만 넣어서 작게 분리한다.
  • 불필요한 구현을 방지

Denpdency inversion principle(DIP)

: High-level modules should not import anything directly from low-level modules

  • 한 클래스가 다른 클래스와 의존성이 높을 경우, 다른 클래스에서 수정이 발생했을 때 의존성이 있는 클래스에도 수정이 필요해진다.
  • 인터페이스를 통해 통신
public class Switch : MonoBehavior{
	public Door door;
	public bool isActived;

	public void Toggle(){
		if(isActived){
			isActived = false;
			door.Close();
		}
		else{
			isActived = true;
			door.Open();
		}
	}
}

public class Door : MonoBehavior{
	public void Open(){}
	public void Close(){}
}
  • 고수준의 클래스(Switch)가 저수준의 클래스(Door)에 직접적으로 의존한다.
  • 스위치에 다른 기능들이 필요하다면? Switch에 새로운 메소드를 추가해야한다.(OCP)
  • Door와 Switch 사이에 ISwitchable 인터페이스를 추가, 스위치가 인터페이스에 의존하게 한다.
public interface ISwitchable{
	public bool IsActive { get; }
	public void Active();
	public void Deactive();
}

// 스위치 클래스
public class Switch : MonoBehavior{
	public ISwitchable client;
	
	public void Toggle(){
		if(client.IsActive){
			client.Deactivate();		
		}
		else{
			client.Activate();
		}
	}
}

// 문 클래스
public class Door : MonoBehavior, ISwitchable{
	private bool isActive;
	private bool IsActive => isActive;

	public void Activate(){
		isActive = true;
	}
	public void Deactivate(){
		isActive = false;
	}
}

추상 클래스 vs 인터페이스

  • 추상 클래스(상속)는 1개만, 인터페이스는 복수 가능
  • 추상 클래스를 쓰는 이유는 재정의를 하지 않아도(할 수 도 있다) 되는 경우가 있기 때문이다.
  • 공통된 기능은 추상 클래스에 특정한 기능은 인터페이스로
profile
https://totohoon01.tistory.com/

0개의 댓글