클린 아키텍처 스터디 3부 정리

Adam·2023년 1월 25일
0

클린 아키텍쳐

목록 보기
4/7
post-thumbnail

설계 원칙

좋은 설계를 위해선 깔끔한 코드가 필요.
이러한 깔끔한 코드는 SOLID 원칙을 따르는 것이 좋다.
SOLID 원칙을 따르게 된다면 변경에 유연하고, 코드가 이해해지기 쉬우며, 많은 소프트웨어 시스템에 사용 될 수 있는 컴포넌트의 기반이 된다.

Single Responsibility Priciple

단일 모듈의 변경 이유는 오직 하나뿐이어야 한다.

모듈이 SRP를 어기는 징조는 다음과 같다.

  1. 우발적 중복
    서로 다른 액터가 의존하는 코드를 가까이 배치하는 클래스에 배치할때 발생하는 문제.
    하나의 클래스는 하나의 액터를 위한 코드만 존재해야 한다.
  • SRP는 서로 다른 액터가 의좋나는 코드를 서로 분리해야한다.
  1. 병합
    소스코드를 수정하는 과정에서 우발적 중복이 있는 경우, 수정한 사항을 병합하는데 다른 액터에게 문제를 야기 할 수 있다.

해결책

데이터와 메서드를 분리하는 방법.
이렇게 분리를하면 병합의 문제는 피할 수 있으나 세 가지 클래스를 인스턴스화하고 추적해야하는 번거로움이 있다.
이런 번거로움을 해결하기 위해 퍼사드 패턴을 활용하는게 좋음.

  • Facade Pattern
    어떤 소프트웨어의 다른 커다란 코드 부분에 대하여 간략화된 인터페이스를 제공해주는 디자인 패턴을 의미.
    퍼사드 객체는 복잡한 소프트웨어 바깥쪽의 코드가 라이브러리의 안쪽 코드에 의존하는 일을 감소시켜 주고, 복잡한 소프트웨어를 사용 할 수 있게 간단한 인터페이스를 제공.
    Facade class를 생성하여 이 안에서 SRP에 맞는 분리된 클래스들을 생성하고 facade class안에서 생성된 객체들을 사용한 매써드를 실행시키는 방식
public void view()
{
     Beverage beverage = new Beverage("콜라");
     Remote_Control remote= new Remote_Control();
     Movie movie = new Movie("어벤져스");
       
     beverage.Prepare();  //음료 준비
     remote.Turn_On();   //tv를 켜다
     movie.Search_Movie();  //영화를 찾다
     movie.Charge_Movie();  // 영화를 결제하다
     movie.play_Movie();   //영화를 재생하다
}

해당 코드를 아래와 같이 변경하는 예시가 facade pattern을 도입하는 것이다

public class Remote_Control {
    
    public void Turn_On()
    {
        System.out.println("TV를 켜다");
    }
    public void Turn_Off()
    {
        System.out.println("TV를 끄다");
    } 
}

public class Movie {
    
    private String name="";
    
    public Movie(String name)
    {
        this.name = name;
    }
    
    public void Search_Movie()
    {
        System.out.println(name+" 영화를 찾다");
    }
    
    public void Charge_Movie()
    {
        System.out.println("영화를 결제하다");
    }
    public void play_Movie()
    {
        System.out.println("영화 재생");
    } 
}

public class Beverage {
    
    private String name="";
    
    public Beverage(String name)
    {
        this.name = name;
    }
    
    public void Prepare()
    {
        System.out.println(name+" 음료 준비 완료 ");
    }
 
}


public class Facade {
    
    private String beverage_Name ="";
    private String Movie_Name="";
    
    public Facade(String beverage,String Movie_Name)
    {
        this.beverage_Name=beverage_Name;
        this.Movie_Name=Movie_Name;
    }
    
    public void view_Movie()
    {
        Beverage beverage = new Beverage(beverage_Name);
        Remote_Control remote= new Remote_Control();
        Movie movie = new Movie(Movie_Name);
        
        beverage.Prepare();
        remote.Turn_On();
        movie.Search_Movie();
        movie.Charge_Movie();
        movie.play_Movie();
    }
}
  • 결론
    SRP는 매서드와 클래스 수준의 원칙.
    하지만 컴포넌트 수준에서 공통 폐쇄 원칙이 되고, 아키텍처 수준에서는 아키텍처 경계의 생성을 책임지는 변경의 축이 된다.

OCP

소프트웨어 개체는 확장에는 열려 있어야 하고, 변경에는 닫혀 있어야 한다는 원칙.
아키텍트는 기능이 어떻게, 왜, 언제 발생하는지에 따라 기능을 분리하고, 분리한 기능을 컴포넌트의 계층구조로 조직화한다.
조직화를 통해 저수준 컴포넌트에서 발생한 변경으로부터 고수준 컴포넌트를 보호할 수 있다.
방향성을 제어하여 의존성을 조정해야하고, 정보 은닉을 통해 고수준의 컴포넌트가 저수준의 컴포넌트의 변경으로 부터 자유로운 것을 보장해야 한다.

  • 결론
    OCP는 시스템을 확장하기 쉬운 동시에 변경으로 인해 시스템이 너무 많은 영향을 받지 않도록 하는 데 있다.
    시스템을 컴포넌트 단위로 분리하고, 저수준 컴포넌트에서 발생한 변경으로부터 고수준 컴포넌트를 보호할 수 있는 형태의 의존성 계층구조가 있어야 한다.
public interface CalculatorOperation {
    void perform();
}


public class Addition implements CalculatorOperation {
    private double left;
    private double right;
    private double result;

    // constructor, getters and setters

    @Override
    public void perform() {
        result = left + right;
    }
}

public class Division implements CalculatorOperation {
    private double left;
    private double right;
    private double result;

    // constructor, getters and setters
    @Override
    public void perform() {
        if (right != 0) {
            result = left / right;
        }
    }
}

public class Calculator {

    public void calculate(CalculatorOperation operation) {
        if (operation == null) {
            throw new InvalidParameterException("Cannot perform operation");
        }
        operation.perform();
    }
}

추후에 CalculatorOperation을 구현한 다른 객체를 추가하는 것이 용이하며 이때 상위 클래스읜 Calculator은 이런 변경으로 부터 보호를 받는다.

리스코프 치환 원칙

부모 객체와 이를 상속한 자식 객체가 있을 때 부모 객체를 호출하는 동작에서 자식 객체가 부모 객체를 완전히 대체할 수 있다는 원칙.

  • 정사각형 직사각형 문제
    모든 정사각형은 직사각형이기 때문에 정사각형이 직사각형을 상속 받는 형태로 설계를 했다고 할 경우,
    직사각형은 가로와 세로가 독립적인 반면 정사각형은 가로와 세로가 같아야하기 때문에 setWidth같은 매써드로 가로를 설정한 후 넓이를 구하면 직사각형을 상속받은 정사각형에서 오류가 발생한다.

객체 지향 언어가 발전함에 따라 LSP는 단순 상속을 넘어 인터페이스와 구현체에도 적용되는 더 광범위한 설계 원칙으로 변모.
LSP를 위배하면 시스템 아키텍처가 오염되어 별도의 매커니즘을 추가해야되는 상황이 발생 가능하다.

ISP

객체는 자신이 호출하지 않는 메소드에 의존하지 않아야 한다는 원칙.
구현할 객체에게 무의미한 메소드의 구현을 방지하기 위해 필요한 메서드만 상속/구현 하도록 해야되고, 상속할 객체의 규모가 너무 크다면, 해당 객체의 메소드를 작은 인터페이스로 나누는 것이 좋다.

DIP

소스 코드 의존성이 추상에 의존하며 구체에는 의존하지 않는 시스템.
그러나 자바에서 String class역시 구현체이지만 변경이 없는 안정적인 구체이며, 이러한 안정적인 구체에는 의존을 하는 것은 괜찮으나, 변경이 잦은 구체에는 의존해서는 안된다.

안정된 추상화

추상 인터페이스에 변경이 생기면 이를 구체화한 구현체들도 따라서 수정해야 한다.
이러한 인터페이스의 변동성을 낮추기 위해 애를 써야한다.
안정화된 추상화를 위해선 아래의 사항들을 지켜야 한다.

  • 변동성이 큰 구체 클래스를 참조하지 말라.
  • 변동성이 큰 구체 클래스로부터 파생하지 말라.
  • 구체 함수를 오버라이드 하지 말라.

팩토리

변동성이 큰 객체는 주의해서 생성을 해야한다.
어떠한 서비스에서 변경이 잦은 객체를 생성할때 팩터리 인테페이스를 만들고 이 팩터리 인터페이스를 구현한 객체에서 변동이 잦은 객체를 생성하는 방식으로 설계를 하는 것이 이상적이다.
이때 소스 코드 의존성은 제어흐름과는 반대 방향으로 역전되는데 이를 의존성 역전이라한다.

  • 의존성 규칙 : 추상적 엔티티와 구체 사이에 아키텍처 경계가 있는데, 의존성은 이 경계를 기준으로 더 추상적인 엔티티가 있는 쪽으로만 향해야 한다.
profile
Keep going하는 개발자

0개의 댓글