본문 바로가기
C#

C#] SOLID 원칙 4. Interface Segregation Principle(인터페이스 분리 원칙)

by Fastlane 2024. 2. 7.
728x90
반응형

어떠한 고객에게도 사용하지 않는 함수에 의존하기를 강요할 수 없다. 

 

기본적으로  ISP는 최소한의 필요한 구현만 하도록 코드작성을 줄여준다. 따라서 interfaces는 오직 필요한 declarations만 갖도록 만들어야 한다. 다른 declarations을 가지고 있는 interface는 더 작은 interfaces로 분리되어야 한다. 

Example

우리가 운전하기도 하고, 날수도 있는 탈것이 있다.

public interface IVehicle
{
    void Drive();
    void Fly();
}

여러 기능이 있는 차를 개발하려고 한다. IVehicle 인터페이스를 사용하기 좋다. 

public class MultiFunctionalCar : IVehicle
{
    public void Drive()
    {
        //actions to start driving car
        Console.WriteLine("Drive a multifunctional car");
    }

    public void Fly()
    {
        //actions to start flying
        Console.WriteLine("Fly a multifunctional car");
    }
}

잘 작동하며, interface는 필요한 모든 action을 커버한다. 

이제 차와 비행기 class 또한 구현하려고 한다. 

public class Car : IVehicle
{
    public void Drive()
    {
        //actions to drive a car
        Console.WriteLine("Driving a car");
    }

    public void Fly()
    {
        throw new NotImplementedException();
    }
}

 

public class Airplane : IVehicle
{
    public void Drive()
    {
        throw new NotImplementedException();
    }

    public void Fly()
    {
        //actions to fly a plane
        Console.WriteLine("Flying a plane");
    }
}

이제 IVehicle interface를 사용하기에 문제가 있어 보인다. 필요없는 함수는 exception을 던지도록 구현되었다. 이것은 좋은 아이디어가 아니다. 왜냐하면, 우리는 단지 exception을 던지는 것이 아닌 뭔가를 하도록 code를 작성해야 하기 때문이다. 게다가, 왜 해당 함수를 사용하면 안되는지 다른 사람이 알 수 있도록 코멘트를 남겨야한다. 

 

해당 문제를 고치기 위해, ISP를 따르도록 리팩토링하자. 

 

ISP 구현

첫번째로 할 일은 IVehicle interface를 나누는 것이다. 

public interface ICar
{
    void Drive();
}
public interface IAirplane
{
    void Fly();
}

이제 class들은 필요한 method만 구현하면 된다. 

public class Car : ICar
{
    public void Drive()
    {
        //actions to drive a car
        Console.WriteLine("Driving a car");
    }
}
public class Airplane : IAirplane
{
    public void Fly()
    {
        //actions to fly a plane
        Console.WriteLine("Flying a plane");
    }
}
public class MultiFunctionalCar : ICar, IAirplane
{
    public void Drive()
    {
        //actions to start driving car
        Console.WriteLine("Drive a multifunctional car");
    }

    public void Fly()
    {
        //actions to start flying
        Console.WriteLine("Fly a multifunctional car");
    }
}

 

higher level interface를 사용할 수도 있다. 

public interface IMultiFunctionalVehicle : ICar, IAirplane
{
}

higher level interface는 다른 방식들로 구현할 수 있다. 

첫번째는 함수들을 구현하는 것이다. 

public class MultiFunctionalCar : IMultiFunctionalVehicle
{
    public void Drive()
    {
        //actions to start driving car
        Console.WriteLine("Drive a multifunctional car");
    }

    public void Fly()
    {
        //actions to start flying
        Console.WriteLine("Fly a multifunctional car");
    }
}

이미 구현된 Car class, Airplace class가 있다면 decorator pattern을 사용해서 class 내부에서 사용할 수 있다. 

public class MultiFunctionalCar : IMultiFunctionalVehicle
{
    private readonly ICar _car;
    private readonly IAirplane _airplane;

    public MultiFunctionalCar(ICar car, IAirplane airplane)
    {
        _car = car;
        _airplane = airplane;
    }

    public void Drive()
    {
        _car.Drive();
    }

    public void Fly()
    {
        _airplane.Fly();
    }
}

 

ISP의 장점

앞선 예처럼, 작은 interface는 필요없는 함수를 구현할 필요가 없기 때문에, 구현이 훨씬 편하다. 

 

물론, 예에서는 하나의 함수만 갖는 interface로 살펴보았다. 하지만 실제 프로젝트에서는 여러 함수를 갖는 interface를 사용한다. 이러한 함수들이 서로 연관성이 높다면 문제가 없다. 따라서, 클래스가 해당 함수를 모두 구현해야 함을 보장할 수 있다. 

 

또 다른 장점은 ISP가 가독성과 유지보수를 쉽게 한다는 것이다. 필요없는 코드 없이 필요한 action만 구현하게 되므로 코드가 줄어든다. 

 

 

728x90
반응형

댓글