분류 php

PHP에서 SOLID

컨텐츠 정보

  • 조회 768 (작성일 )

본문

SOLID 란 무엇입니까? ? 


밥 삼촌이 작성한 좋은 소프트웨어 디자인 관행을 갖는 것은 일련의 원칙입니다.


왜 사용해야 합니까? 


  • 소프트웨어 설계 원칙 또는 규칙.
  • 업계에서 널리 사용됩니다.
  • 코드를 유지 관리하기 쉽고 변경 사항에 잘 견디도록 도와줍니다.
  • 클래스 디자인 (마이크로 디자인) 및 소프트웨어 아키텍처 수준에 적용 가능합니다.

SOLID를 사용하지 않는다면 아마 모르게 STUPID¹를 코딩 할 것입니다. 


¹ : STUPID는 Singleton, Tight Coupling, Untestability, Premature Optimization, Indescriptive Naming, Duplication을 의미합니다.


SOLID는 무엇을 의미합니까? 


SOLID는 다음을 의미하는 약어입니다.

  • Single responsibility principle
  • Open/closed principle
  • Liskov substitution principle
  • Interface segregation principle
  • Dependency inversion principle

우리는 무엇을 해야 합니까 


S - Single Responsibility Principle(SRP) 


Alt Text? 클래스는 변경해야 할 이유가 하나만 있어야 합니다. 즉, 하나의 책임만 있어야 합니다. 

  • 달성하는 방법
    제한된 목표를 가진 소규모 수업
  • 목적 또는 이득 :
    높은 응집력과 견고함
    수업 구성 허용 (공동 작업자 삽입)
    코드 중복 방지

 


텍스트 문서를 나타내는 클래스가 있다고 가정 해 봅시다. 문서에는 제목과 내용이 있습니다. 이 문서는 HTML 및 PDF로 내보낼 수 있어야 합니다.


SRP 위반 ? 


⚠️ 하나 이상의 책임과 결합 된 코드


class Document
{
    protected $title;
    protected $content;

    public function __construct(string $title, string $content)
    {
        $this->title = $title;
        $this->content= $content;
    }

    public function getTitle(): string
    {
        return $this->title;
    }

    public function getContent(): string
    {
        return $this->content;
    }

        public function exportHtml() {
                echo "DOCUMENT EXPORTED TO HTML".PHP_EOL;
        echo "Title: ".$this->getTitle().PHP_EOL;
        echo "Content: ".$this->getContent().PHP_EOL.PHP_EOL;
        }

        public function exportPdf() {
                echo "DOCUMENT EXPORTED TO PDF".PHP_EOL;
        echo "Title: ".$this->getTitle().PHP_EOL;
        echo "Content: ".$this->getContent().PHP_EOL.PHP_EOL;
        }
}


다른 프로그래머가 사용할 수 있도록 API로 노출하는 메서드 또는 함수를 볼 수 있듯이 getTitle() 및 getContent()를 포함하지만 이러한 메서드는 동일한 클래스의 동작 내에서 사용됩니다.


이것은 Tell-Don't-Ask 원칙을 위반합니다.


? Tell-Don't-Ask는 객체 지향이 해당 데이터에서 작동하는 함수와 데이터를 묶는다는 것을 사람들이 기억하는 데 도움이 되는 원칙입니다. 객체에게 데이터를 요청하고 그 데이터에 대해 조치를 취하는 대신 객체에게 무엇을 해야 하는지 알려 주어야 함을 상기시킵니다. 


마지막으로, 문서를 표현해야 하는 클래스는 문서를 표현할 뿐만 아니라 다른 형식으로 내보내는 책임도 있음을 알 수 있습니다.


SRP 원칙 준수 ? 


Document 클래스에 "문서"의 표현 외에는 아무것도 없어야 한다는 것을 확인한 후 다음으로 설정해야 할 것은 내보내기와 통신하려는 API입니다.


내보내기를 위해서는 문서를 받는 인터페이스를 만들어야 합니다.


interface ExportableDocumentInterface
{
    public function export(Document $document);
}


다음으로 해야 할 일은 클래스에 속하지 않는 로직을 추출하는 것입니다.


class HtmlExportableDocument implements ExportableDocumentInterface
{
    public function export(Document $document)
    {
        echo "DOCUMENT EXPORTED TO HTML".PHP_EOL;
        echo "Title: ".$document->getTitle().PHP_EOL;
        echo "Content: ".$document->getContent().PHP_EOL.PHP_EOL;
    }
}


class PdfExportableDocument implements ExportableDocumentInterface
{
    public function export(Document $document)
    {
        echo "DOCUMENT EXPORTED TO PDF".PHP_EOL;
        echo "Title: ".$document->getTitle().PHP_EOL;
        echo "Content: ".$document->getContent().PHP_EOL.PHP_EOL;
    }
}


다음과 같이 클래스 구현을 떠나


class Document
{
    protected $title;
    protected $content;

    public function __construct(string $title, string $content)
    {
        $this->title = $title;
        $this->content= $content;
    }

    public function getTitle(): string
    {
        return $this->title;
    }

    public function getContent(): string
    {
        return $this->content;
    }
}


이렇게 하면 내보내기 및 문서 클래스 모두를 더 쉽게 테스트 할 수 있습니다.


O - Open-Closed Principle(OCP) 


Alt Text 

? 개체 또는 엔티티는 확장을 위해 열려 있어야 하지만 수정을 위해 닫혀 있어야 합니다.


  • 달성하는 방법
    특정 구현에 의존하지 않고 추상 클래스 또는 인터페이스를 사용합니다.
  • 목적 또는 이득 :
    애플리케이션에 새로운 사용 사례를 쉽게 추가 할 수 있습니다.

 


로그인 시스템을 구현해야 한다고 상상해 봅시다. 처음에 사용자를 인증하려면 사용자 이름과 비밀번호 (주 사용 사례)가 필요합니다. 지금까지는 좋지만 사용자가 Twitter 또는 Gmail을 통해 인증을 요구하거나 업무상 요구하면 어떻게 됩니까?


우선, 이와 같은 상황이 발생하면 우리에게 요구되는 것이 현재의 기능을 수정하는 것이 아니라 새로운 기능이라는 것을 이해하는 것이 중요합니다. 그리고 Twitter의 경우는 하나의 사용 사례이고 Gmail의 경우는 완전히 다른 것입니다.


타사 API 로그인-OCP 위반 ? 


class LoginService
{
    public function login($user)
    {
        if ($user instanceof User) {
            $this->authenticateUser($user);
        } else if ($user instanceOf ThirdPartyUser) {
            $this->authenticateThirdPartyUser($user);
        }
    }
}


타사 API로 로그인-OCP 팔로우 ? 


가장 먼저 해야 할 일은 우리가 하고 싶은 일을 준수하고 특정 사용 사례에 맞는 인터페이스를 만드는 것입니다.


interface LoginInterface
{
    public function authenticateUser($user);
}


이제 사용 사례를 위해 이미 생성 한 로직을 분리하고 인터페이스를 구현하는 클래스에서 구현해야 합니다.


class UserAuthentication implements LoginInterface
{
    public function authenticateUser($user)
    {
        // TODO: Implement authenticateUser() method.
    }
}


Class ThirdPartyUserAuthentication implements LoginInterface
{
    public function authenticateUser($user)
    {
        // TODO: Implement authenticateUser() method.
    }
}


class LoginService
{
    public function login(LoginInterface $user)
    {
        $user->authenticateUser($user);
    }
}


보시다시피 LoginService 클래스는 어떤 인증 방법 (웹, google 또는 twitter 등을 통해)과 무관합니다.


스위치로 구현 된 결제 API-OCP 위반 ? 


매우 일반적인 경우는 switch ()가있는 경우입니다. 여기서 각 케이스는 서로 다른 작업을 수행하고 향후 스위치에 더 많은 케이스를 계속 추가 할 가능성이 있습니다. 다음 예를 살펴 보겠습니다.


여기에는 요청을 통해 지불 유형을 받는 역할을 하는 pay () 메소드가 있는 컨트롤러가 있으며, 해당 유형에 따라 지불 클래스에 있는 하나 또는 다른 방법을 통해 지불이 처리됩니다.


public function pay(Request $request)
{
    $payment = new Payment();

    switch ($request->type) {
        case 'credit':
            $payment->payWithCreditCard();
            break;
        case 'paypal':
            $payment->payWithPaypal();
            break;
        default:
            // Exception
            break;
    }
}


class PaymentRequest
{
    public function payWithCreditCard()
    {
        // Logic to pay with a credit card...
    }

    public function payWithPaypal()
    {
        // Logic to pay with paypal...
    }
}


이 코드에는 두 가지 큰 문제가 있습니다.


  • PayPal을 통해 더 많은 지불을 수락하지 않는 경우 당사가 수락하거나 사례를 삭제하는 각각의 새로운 지불에 대해 하나 이상의 사례를 추가해야 합니다.
  • 다양한 유형의 지불을 처리하는 모든 방법은 지불 클래스라는 단일 클래스에 있습니다. 따라서 새 지불 유형을 추가하거나 제거 할 때 지불 클래스를 편집해야 하며 Open / Closed 원칙에 따르면 이상적이지 않습니다. 그것은 또한 단일 책임의 원칙을 위반하는 것처럼.

스위치로 구현 된 Payments API-OCP 팔로우 ? 


OCP를 준수하기 위해 가장 먼저 할 수있는 일은 pay () 메소드로 인터페이스를 만드는 것입니다.


interface PayableInterface
{
    public function pay();
}


이제 이러한 인터페이스를 구현해야 하는 클래스 생성을 진행합니다.


class CreditCardPayment implements PayableInterface
{
    public function pay()
    {
        // Logic to pay with a credit card...
    }
}


class PaypalPayment implements PayableInterface
{
    public function pay()
    {
        // Logic to pay with paypal...
    }
}


다음 단계는 pay () 메서드를 리팩터링 하는 것입니다.


public function pay(Request $request)
{
    $paymentFactory = new PaymentFactory();
    $payment = $paymentFactory->initialize($request->type);

    return $payment->pay();
}


? 보시다시피 스위치를 공장으로 교체했습니다.


class PaymentFactory
{
    public function initialize(string $type): PayableInterface
    {
        switch ($type) {
            case 'credit':
                return new CreditCardPayment();
            case 'paypal':
                return new PayPalPayment();
            default:
                throw new \Exception("Payment method not supported");
                break;
        }
    }
}


개방 / 폐쇄 원칙의 이점 


  • 시스템의 핵심을 건드리지 않고 시스템의 기능을 확장하십시오.
  • 새로운 기능을 추가하여 시스템의 일부가 깨지는 것을 방지합니다.
  • 테스트 용이성.
  • 다른 논리의 분리.

? OCP에 유용한 디자인 패턴 


L - Liskov Substitution Principle(LSP) 


Alt Text 

이 원칙은 미국에서 박사 학위를 취득한 최초의 여성이자 전문가에 의해 도입되었습니다. Barbara Liskov 컴퓨터 과학 박사. 그리고 그것은 매우 흥미로운 원리입니다.


Wikipedia에 따르면. Liskov의 Substitution Principle은 다른 클래스에서 상속 된 각 클래스는 차이점을 알 필요 없이 부모로 사용할 수 있다고 말합니다.