첫 번째, 단일 책임 원칙(Single Responsibility Principle, SRP)은 클래스가 변경될 단 하나의 이유만을 가져야 함을 강조합니다. 예를 들어, 데이터베이스 상호작용, 이메일 전송, 인증 관리를 모두 처리하는 ‘User’ 모델은 단일 책임 원칙을 위반합니다. 이를 해결하기 위해 ‘UserMailer’와 ‘UserAuthenticator’와 같이 각각의 책임을 분리된 클래스로 추출하여 ‘User’ 모델은 데이터 영속성만 담당하게 함으로써 코드를 깔끔하게 유지합니다.
두 번째, 개방/폐쇄 원칙(Open/Closed Principle, OCP)은 클래스가 확장에 대해서는 개방적이지만 수정에 대해서는 폐쇄적이어야 함을 의미합니다. 즉, 새로운 기능을 추가할 때 기존 코드를 수정하지 않고 확장할 수 있어야 합니다. ‘PaymentProcessor’ 클래스에 새로운 결제 방식을 추가할 때마다 해당 클래스를 수정하는 대신, ‘PaymentMethod’라는 추상 기본 클래스를 정의하고 ‘CreditCardPayment’, ‘PayPalPayment’, ‘BankTransferPayment’와 같은 구체적인 결제 방식들을 이 클래스를 상속받아 구현함으로써 다형성을 활용합니다. ‘PaymentProcessor’는 ‘PaymentMethod’의 인스턴스를 받아 처리하므로, 새로운 결제 방식이 추가되어도 기존 코드를 변경할 필요가 없습니다.
세 번째, 리스코프 치환 원칙(Liskov Substitution Principle, LSP)은 서브타입 객체가 프로그램의 정확성 손실 없이 슈퍼타입 객체로 대체될 수 있어야 함을 명시합니다. 예를 들어, ‘fly’ 메서드를 가진 ‘Bird’ 슈퍼클래스가 있지만 모든 새가 날 수 있는 것은 아닙니다. 이 문제를 해결하기 위해 ‘Bird’ 클래스에 ‘make_sound’와 같은 공통 동작을 정의하고, ‘Flying’ 모듈을 만들어 날 수 있는 새(예: ‘FlyingBird’)만 해당 모듈을 포함하도록 합니다. 날 수 없는 새(예: ‘Penguin’)는 ‘swim’과 같은 자신만의 동작을 구현함으로써, 상속 계층의 일관성을 유지하고 불필요한 메서드 구현을 강제하지 않습니다.
네 번째, 인터페이스 분리 원칙(Interface Segregation Principle, ISP)은 클라이언트가 사용하지 않는 메서드에 의존해서는 안 된다는 원칙입니다. 즉, 클래스는 자신이 사용하지 않는 메서드를 구현하도록 강요받아서는 안 됩니다. ‘ReportGenerator’ 클래스가 PDF 및 CSV 생성 메서드를 모두 요구하지만, 모든 보고서가 둘 다 필요하지 않은 경우 문제가 발생합니다. 이를 해결하기 위해 ‘PdfExportable’ 및 ‘CsvExportable’과 같은 별도의 모듈로 책임을 분리하고, ‘PdfReport’와 ‘CsvReport’는 필요한 기능만 포함하도록 합니다. 이로써 각 클래스는 필요한 기능만 포함하고 불필요한 메서드를 구현할 필요가 없습니다.
다섯 번째, 의존성 역전 원칙(Dependency Inversion Principle, DIP)은 고수준 모듈이 저수준 모듈에 의존해서는 안 되며, 둘 다 추상화에 의존해야 함을 강조합니다. 또한 추상화는 세부 사항에 의존해서는 안 되며, 세부 사항이 추상화에 의존해야 합니다. ‘NotificationService’가 ‘EmailNotifier’를 직접 인스턴스화하면 SMS 알림으로 전환하기 어렵습니다. 해결책은 의존성 주입(Dependency Injection)을 사용하는 것입니다. ‘NotificationService’는 초기화 시 ‘notifier’라는 추상화를 통해 ‘EmailNotifier’나 ‘SmsNotifier’와 같은 구체적인 알리미 객체를 주입받습니다. 이로써 ‘NotificationService’는 어떤 알리미와도 수정 없이 작동할 수 있으며, 새로운 알리미 유형을 쉽게 추가할 수 있는 유연성을 확보합니다.