JavaRush /Java Blog /Random-KO /Java 클래스 디자인의 5가지 기본 원칙(SOLID)
Ve4niY
레벨 14

Java 클래스 디자인의 5가지 기본 원칙(SOLID)

Random-KO 그룹에 게시되었습니다
클래스는 애플리케이션이 구축되는 블록입니다. 건물의 벽돌과 같습니다. 잘못 작성된 수업은 언젠가 문제를 일으킬 수 있습니다. Java의 SOLID(클래스 설계) 5가지 기본 원칙 - 1수업이 올바르게 작성되었는지 이해하려면 "품질 표준"을 확인하세요. Java에서는 이것이 소위 SOLID 원칙입니다. 그들에 대해 이야기합시다.

Java의 SOLID 원칙

SOLID는 OOP와 디자인의 처음 5가지 원칙의 대문자로 구성된 약어입니다. 이 원리는 2000년대 초반 로버트 마틴(Robert Martin)에 의해 고안되었으며, 이후 마이클 페더스(Michael Feathers)가 약어를 만들었습니다. SOLID 원칙에는 다음이 포함됩니다.
  1. 단일 책임 원칙.
  2. 개방형 폐쇄 원칙.
  3. Liskov의 대체 원리.
  4. 인터페이스 분리 원칙.
  5. 종속성 반전 원리.

단일 책임 원칙(SRP)

이 원칙은 클래스를 변경하는 데 하나 이상의 이유가 있어서는 안 된다는 것을 명시합니다. 각 객체에는 하나의 책임이 있으며 클래스에 완전히 캡슐화되어 있습니다. 모든 수업 서비스는 이러한 책임을 보장하는 것을 목표로 합니다. 이러한 클래스는 클래스가 담당하는 것과 그렇지 않은 것이 명확하기 때문에 필요한 경우 항상 쉽게 변경할 수 있습니다. 즉, 변경이 가능하고 결과, 즉 다른 개체에 대한 영향을 두려워하지 않을 수 있습니다. 그리고 이러한 코드는 테스트하기가 훨씬 쉽습니다. 다른 모든 기능과 별도로 테스트를 통해 하나의 기능을 다루기 때문입니다. 주문을 처리하는 모듈을 상상해 보세요. 주문이 올바르게 구성되면 데이터베이스에 저장하고 주문 확인을 위해 이메일을 보냅니다.
public class OrderProcessor {

    public void process(Order order){
        if (order.isValid() && save(order)) {
            sendConfirmationEmail(order);
        }
    }

    private boolean save(Order order) {
        MySqlConnection connection = new MySqlConnection("database.url");
        // save the order to the database

        return true;
    }

    private void sendConfirmationEmail(Order order) {
        String name = order.getCustomerName();
        String email = order.getCustomerEmail();

        // Sending a letter to the client
    }
}
이러한 모듈은 세 가지 이유로 변경될 수 있습니다. 첫째, 주문 처리 로직이 다를 수 있고, 둘째, 저장 방법(데이터베이스 유형), 셋째, 확인 편지를 보내는 방법(예: 이메일 대신 SMS를 보내야 함)이 다를 수 있습니다. 단일 책임 원칙은 이 문제의 세 가지 측면이 실제로 세 가지 다른 책임임을 의미합니다. 이는 서로 다른 클래스나 모듈에 있어야 함을 의미합니다. 서로 다른 시간과 다른 이유로 변경될 수 있는 여러 엔터티를 결합하는 것은 잘못된 설계 결정으로 간주됩니다. 모듈을 세 개의 개별 모듈로 나누는 것이 훨씬 낫습니다. 각 모듈은 하나의 단일 기능을 수행합니다.
public class MySQLOrderRepository {
    public boolean save(Order order) {
        MySqlConnection connection = new MySqlConnection("database.url");
        // save the order to the database

        return true;
    }
}

public class ConfirmationEmailSender {
    public void sendConfirmationEmail(Order order) {
        String name = order.getCustomerName();
        String email = order.getCustomerEmail();

        // Sending a letter to the client
    }
}

public class OrderProcessor {
    public void process(Order order){

        MySQLOrderRepository repository = new MySQLOrderRepository();
        ConfirmationEmailSender mailSender = new ConfirmationEmailSender();

        if (order.isValid() && repository.save(order)) {
            mailSender.sendConfirmationEmail(order);
        }
    }

}

개방형/폐쇄형 원리(OCP)

이 원칙은 다음과 같이 간결하게 설명됩니다. 소프트웨어 엔터티(클래스, 모듈, 기능 등)는 확장을 위해 열려 있어야 하고 변경을 위해 닫혀 있어야 합니다 . 이는 클래스 자체를 물리적으로 변경하지 않고도 클래스의 외부 동작을 변경할 수 있어야 함을 의미합니다. 이 원칙에 따라 클래스를 특정 응용 조건에 맞게 조정하려면 클래스를 확장하고 일부 기능을 재정의하는 것으로 충분하도록 클래스가 개발됩니다. 따라서 시스템은 소스 코드를 변경하지 않고도 다양한 조건에서 작동할 수 있도록 유연해야 합니다. 주문 예시를 계속해서 주문이 처리되기 전과 확인 이메일이 전송된 후에 몇 가지 작업을 수행해야 한다고 가정해 보겠습니다. 클래스 자체를 변경하는 대신 OrderProcessor클래스를 확장하여 OCP 원칙을 위반하지 않고 당면한 문제에 대한 솔루션을 얻을 것입니다.
public class OrderProcessorWithPreAndPostProcessing extends OrderProcessor {

    @Override
    public void process(Order order) {
        beforeProcessing();
        super.process(order);
        afterProcessing();
    }

    private void beforeProcessing() {
        // Perform some actions before processing the order
    }

    private void afterProcessing() {
        // Perform some actions after order processing
    }
}

바바라 리스코프 대체 원칙(LSP)

이는 앞서 논의한 개방/폐쇄 원칙의 변형입니다. 이는 다음과 같이 설명할 수 있습니다. 프로그램의 개체는 프로그램의 속성을 변경하지 않고도 해당 상속인에 의해 대체될 수 있습니다. 이는 기본 클래스를 확장하여 개발된 클래스가 클라이언트의 관점에서 기능을 중단하지 않는 방식으로 해당 메서드를 재정의해야 함을 의미합니다. 즉, 개발자가 클래스를 확장하여 애플리케이션에서 사용하는 경우 재정의된 메서드의 예상 동작을 변경해서는 안 됩니다. 서브클래스는 클라이언트의 관점에서 기능을 중단하지 않는 방식으로 기본 클래스 메서드를 재정의해야 합니다. 이는 다음 예시를 통해 자세히 살펴볼 수 있습니다. 주문 확인을 담당하고 모든 주문 항목의 재고가 있는지 확인하는 클래스가 있다고 가정해 보겠습니다. 이 클래스에는 true 또는 false를isValid 반환하는 메서드가 있습니다 .
public class OrderStockValidator {

    public boolean isValid(Order order) {
        for (Item item : order.getItems()) {
            if (! item.isInStock()) {
                return false;
            }
        }

        return true;
    }
}
또한 일부 주문을 다르게 검증해야 한다고 가정해 보겠습니다. 주문의 모든 상품이 재고가 있는지, 모든 상품이 포장되었는지 확인하세요. 이를 위해 클래스를 OrderStockValidator다음 클래스로 확장했습니다 OrderStockAndPackValidator.
public class OrderStockAndPackValidator extends OrderStockValidator {

    @Override
    public boolean isValid(Order order) {
        for (Item item : order.getItems()) {
            if ( !item.isInStock() || !item.isPacked() ){
                throw new IllegalStateException(
                     String.format("Order %d is not valid!", order.getId())
                );
            }
        }

        return true;
    }
}
그러나 이 클래스에서는 주문이 유효성 검사를 통과하지 못한 경우 false를 반환하는 대신 메서드에서 예외를 발생시키기 때문에 LSP 원칙을 위반했습니다 IllegalStateException. 이 코드의 클라이언트는 이를 기대하지 않습니다. 즉, true 또는 false가 반환될 것으로 예상합니다 . 이로 인해 프로그램에 오류가 발생할 수 있습니다.

인터페이스 분할 원리(ISP)

다음 진술의 특징은 다음과 같습니다. 클라이언트는 사용하지 않을 메소드를 구현하도록 강요해서는 안 됩니다 . 인터페이스 분리의 원칙은 너무 두꺼운 인터페이스를 더 작고 구체적인 인터페이스로 나누어 작은 인터페이스의 클라이언트가 작업에 필요한 방법만 알 수 있도록 해야 한다는 것을 의미합니다. 따라서 인터페이스 방식을 변경할 때 이 방식을 사용하지 않는 클라이언트는 변경하면 안 된다. 예를 살펴보겠습니다. 개발자 Alex는 "보고서" 인터페이스를 만들고 두 가지 메서드를 추가 generateExcel()했습니다 generatedPdf(). 이제 클라이언트 A는 이 인터페이스를 사용하려고 하지만 Excel이 아닌 PDF 보고서만 사용하려고 합니다. 그는 이 기능에 만족할까요? 아니요. 그는 두 가지 방법을 구현해야 하는데 그 중 하나는 대체로 불필요하며 소프트웨어 디자이너인 Alex 덕분에 존재합니다. 클라이언트는 다른 인터페이스를 사용하거나 Excel 필드를 비워 둡니다. 그렇다면 해결책은 무엇입니까? 기존 인터페이스를 두 개의 작은 인터페이스로 나누는 것으로 구성됩니다. 하나는 PDF 형식의 보고서이고, 두 번째는 Excel 형식의 보고서입니다. 이를 통해 사용자는 자신에게 필요한 기능만 사용할 수 있습니다.

종속성 역전 원리(DIP)

Java의 이 SOLID 원칙은 다음과 같이 설명됩니다. 시스템 내의 종속성은 추상화를 기반으로 구축됩니다 . 최상위 모듈은 하위 모듈과 독립적입니다. 추상화는 세부사항에 의존해서는 안 됩니다. 세부 사항은 추상화에 따라 달라져야 합니다. 다양한 모듈이 자율적이고 추상화를 통해 서로 연결되도록 소프트웨어를 설계해야 합니다. 이 원칙의 전형적인 적용은 Spring 프레임워크입니다. Spring 프레임워크 내에서 모든 모듈은 함께 작동할 수 있는 별도의 구성 요소로 구현됩니다. 이는 매우 독립적이므로 Spring 프레임워크 이외의 다른 소프트웨어 모듈에서도 쉽게 사용할 수 있습니다. 이는 폐쇄형 원칙과 개방형 원칙의 의존성을 통해 달성됩니다. 모든 모듈은 다른 모듈에서 사용할 수 있는 추상화에만 액세스를 제공합니다. 예를 들어 이를 보여드리겠습니다. 단독 책임 원칙에 대해 말하면서 우리는 몇 가지를 고려했습니다 OrderProcessor. 이 클래스의 코드를 다시 살펴보겠습니다.
public class OrderProcessor {
    public void process(Order order){

        MySQLOrderRepository repository = new MySQLOrderRepository();
        ConfirmationEmailSender mailSender = new ConfirmationEmailSender();

        if (order.isValid() && repository.save(order)) {
            mailSender.sendConfirmationEmail(order);
        }
    }

}
이 예에서 우리는 OrderProcessor두 개의 특정 클래스 MySQLOrderRepository와 에 의존합니다 ConfirmationEmailSender. 또한 다음 클래스에 대한 코드도 제공합니다.
public class MySQLOrderRepository {
    public boolean save(Order order) {
        MySqlConnection connection = new MySqlConnection("database.url");
        // save the order to the database

        return true;
    }
}

public class ConfirmationEmailSender {
    public void sendConfirmationEmail(Order order) {
        String name = order.getCustomerName();
        String email = order.getCustomerEmail();

        // Sending a letter to the client
    }
}
이러한 클래스는 추상화라고 부르기와는 거리가 멀습니다. 그리고 DIP 원칙의 관점에서 볼 때 특정 구현보다는 미래에 이를 사용할 수 있는 몇 가지 추상화를 만드는 것부터 시작하는 것이 더 정확할 것입니다. 추상화가 될 두 개의 인터페이스 MailSender및 를 만들어 보겠습니다 .OrderRepository
public interface MailSender {
    void sendConfirmationEmail(Order order);
}

public interface OrderRepository {
    boolean save(Order order);
}
이제 이미 준비된 클래스에서 이러한 인터페이스를 구현해 보겠습니다.
public class ConfirmationEmailSender implements MailSender {

    @Override
    public void sendConfirmationEmail(Order order) {
        String name = order.getCustomerName();
        String email = order.getCustomerEmail();

        // Sending a letter to the client
    }

}

public class MySQLOrderRepository implements OrderRepository {

    @Override
    public boolean save(Order order) {
        MySqlConnection connection = new MySqlConnection("database.url");
        // save the order to the database

        return true;
    }
}
OrderProcessor우리는 수업이 구체적인 세부 사항이 아닌 추상화에 의존하도록 준비 작업을 수행했습니다 . 클래스 생성자에 종속성을 도입하여 이를 변경해 보겠습니다.
public class OrderProcessor {

    private MailSender mailSender;
    private OrderRepository repository;

    public OrderProcessor(MailSender mailSender, OrderRepository repository) {
        this.mailSender = mailSender;
        this.repository = repository;
    }

    public void process(Order order){
        if (order.isValid() && repository.save(order)) {
            mailSender.sendConfirmationEmail(order);
        }
    }
}
우리 수업은 이제 구체적인 구현보다는 추상화에 의존합니다. 인스턴스가 생성될 때 원하는 종속성을 주입하여 동작을 쉽게 변경할 수 있습니다 OrderProcessor. 우리는 Java의 디자인 원칙인 SOLID를 살펴보았습니다. 일반적인 OOP에 대한 자세한 내용은 JavaRush 과정에서 지루하지 않고 수백 시간의 연습을 통해 이 프로그래밍 언어의 기본 사항을 다룹니다. 몇 가지 문제를 해결할 시간입니다 :) Java의 SOLID(클래스 디자인) 5가지 기본 원칙 - 2
코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION