SOLID 란 무엇입니까? ?
밥 삼촌이 작성한 좋은 소프트웨어 디자인 관행을 갖는 것은 일련의 원칙입니다.
왜 사용해야 합니까?
SOLID를 사용하지 않는다면 아마 모르게 STUPID¹를 코딩 할 것입니다.
¹ : STUPID는 Singleton, Tight Coupling, Untestability, Premature Optimization, Indescriptive Naming, Duplication을 의미합니다.
SOLID는 무엇을 의미합니까?
SOLID는 다음을 의미하는 약어입니다.
우리는 무엇을 해야 합니까
S - Single Responsibility Principle(SRP)
? 클래스는 변경해야 할 이유가 하나만 있어야 합니다. 즉, 하나의 책임만 있어야 합니다.
예
텍스트 문서를 나타내는 클래스가 있다고 가정 해 봅시다. 문서에는 제목과 내용이 있습니다. 이 문서는 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)
? 개체 또는 엔티티는 확장을 위해 열려 있어야 하지만 수정을 위해 닫혀 있어야 합니다.
예
로그인 시스템을 구현해야 한다고 상상해 봅시다. 처음에 사용자를 인증하려면 사용자 이름과 비밀번호 (주 사용 사례)가 필요합니다. 지금까지는 좋지만 사용자가 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...
}
}
이 코드에는 두 가지 큰 문제가 있습니다.
스위치로 구현 된 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)
이 원칙은 미국에서 박사 학위를 취득한 최초의 여성이자 전문가에 의해 도입되었습니다. Barbara Liskov 컴퓨터 과학 박사. 그리고 그것은 매우 흥미로운 원리입니다.
Wikipedia에 따르면. Liskov의 Substitution Principle은 다른 클래스에서 상속 된 각 클래스는 차이점을 알 필요 없이 부모로 사용할 수 있다고 말합니다.
등록된 댓글이 없습니다.