💡 하나의 클래스(객체)는 단 하나의 책임만 가져야 한다
하나의 클래스는 하나의 기능을 담당해 기능 수행에 있어 하나에 집중하도록 설계하는 원칙
✅ 한 책임의 변경으로부터 다른 책임의 변경으로의 연쇄작용 극복
✅ 프로그램의 유지보수성 증가
다만 책임의 범위는 명확히 정해져있는 것이 아니라 개발자에 따라 기준이 달라질 수 있다
💡 확장에 열려있고 수정에는 닫혀있어야 한다
기능을 추가할 때 클래스 확장을 통해 손쉽게 구현하며 확장에 따른 클래스 수정은 최소화
✅ 새로운 변경 사항이 발생했을 때 유연하게 코드를 추가함으로써 큰 힘을 들이지 않고 애플리케이션의 기능을 확장할 수 있음
✅ 새로운 변경 사항이 발생했을 때 기능을 사용하는 부분에 대한 직접적인 수정이 필요 없음
즉, 다형성과 확장을 가능하게 하는 객체지향의 장점을 극대화하는 설계 원칙
interface PaymentMethodInterface {
public function acceptPayment($receipt);
}
class CashPaymentMethod implements PaymentMethodInterface{
public function acceptPayment($receipt) {
//
}
}
class Checkout {
public function begin(Receipt $receipt, PaymentMethodInterface $payment) {
$payment->acceptPayment($receipt);
}
}
💡 객체의 서브 타입은 언제나 기반(부모) 타입으로 교체할 수 있어야 한다
다형성의 원리를 이용하기 위한 원칙으로 상위 클래스 타입으로 객체를 선언해 하위 클래스의 인스턴스를 받으면 업캐스팅된 상태에서 부모의 메서드를 사용해도 동작이 의도대로 흘러가야 함
✅ 다형성의 원리를 이용해 객체 지향적 개발이 가능하다
✅ OCP 원칙을 준수할 수 있다
부모 객체와 자식 객체의 리턴 타입이 다른 것도 리스코프 치환 원칙에 위배된다.
상속관계에서는 꼭 일반화 관계 (IS-A)가 성립해야 한다
💡 인터페이스를 각 용도에 맞게 잘 분리하자는 설계 원칙
구현체가 필요하지 않은 메서드를 구현하지 않도록 하는 원칙
✅ 인터페이스를 사용하는 클라이언트를 기준으로 분리함으로써, 클라이언트의 목적과 용도에 적합한 인터페이스만을 제공 가능하다
한 번 인터페이스를 분리해 구성하면 이후 수정하거나 분리하는 행위를 하지 말아야 한다
💡 상위 모듈은 하위 모듈에 의존해서는 안 된다. 단순히 하위 모듈을 추상화시킨 개념에 의존하는 것이 아니라 두 모듈 모두 인터페이스 혹은 추상클래스에 의존해야 한다.
의존 관계를 맺을 때 변화하기 쉬운 것보다는 변화하지 않는 것에 의존하라
✅ 확장하기 용이한 구조를 가지고 있다
✅ 객체 간의 의존도가 느슨해진다
package solid.dip;
class SamsungPay implements Pay {
@Override
public String payment() {
return "samsung";
}
}
------------------------------------------------
package solid.dip;
public class KakaoPay implements Pay {
@Override
public String payment() {
return "kakao";
}
}
------------------------------------------------
package solid.dip;
public class PayService {
private Pay pay;
public void setPay(final Pay pay) {
this.pay = pay;
}
public String payment() {
return pay.payment();
}
}
------------------------------------------------
class PayServiceTest {
private PayService payService = new PayService();
@Test
void 삼성페이가_올바르게_결제되는지_테스트() {
/* Given */
Pay samsungPay = new SamsungPay();
/* When */
payService.setPay(samsungPay);
/* Then */
assertThat(payService.payment()).isEqualTo("samsung");
}
@Test
void 카카오페이가_올바르게_결제되는지_테스트() {
/* Given */
Pay kakaoPay = new KakaoPay();
/* When */
payService.setPay(kakaoPay);
/* Then */
assertThat(payService.payment()).isEqualTo("kakao");
}
}