SOLID to zestaw pięciu zasad projektowania obiektowego, mających na celu tworzenie łatwo utrzymywalnego i rozszerzalnego kodu. Stosowanie tych zasad ułatwia organizację kodu, zwiększa jego czytelność i ułatwia współpracę w zespole. Poniżej znajdziesz omówienie każdej z nich oraz przykłady "błędne" i "poprawne" w JavaScripcie.
Zasada pojedynczej odpowiedzialności: Klasa lub moduł powinny mieć tylko jedną odpowiedzialność, czyli jeden powód do zmiany.
class User {
constructor(name, email) {
this.name = name;
this.email = email;
}
saveToDatabase() {
// Logika zapisu do bazy danych
console.log(`Saving ${this.name} to database`);
}
sendEmail() {
// Logika wysyłania emaila
console.log(`Sending email to ${this.email}`);
}
validateEmail() {
// Logika walidacji emaila
return this.email.includes('@');
}
}
class User {
constructor(name, email) {
this.name = name;
this.email = email;
}
}
class UserRepository {
saveUser(user) {
// Logika zapisu do bazy danych
console.log(`Saving ${user.name} to database`);
}
}
class EmailService {
sendEmail(user) {
// Logika wysyłania emaila
console.log(`Sending email to ${user.email}`);
}
}
class EmailValidator {
static validate(email) {
return email.includes('@');
}
}
Zasada otwarte-zamknięte: Klasy powinny być otwarte na rozszerzanie, ale zamknięte na modyfikacje. Nową funkcjonalność należy dodawać poprzez rozszerzanie istniejącego kodu, nie modyfikując go bezpośrednio.
class PaymentProcessor {
processPayment(payment) {
if (payment.type === 'credit') {
// Logika dla karty kredytowej
} else if (payment.type === 'debit') {
// Logika dla karty debetowej
} else if (payment.type === 'paypal') {
// Logika dla PayPal
}
}
}
class PaymentProcessor {
processPayment(paymentMethod) {
return paymentMethod.process();
}
}
class CreditCardPayment {
process() {
// Logika dla karty kredytowej
}
}
class DebitCardPayment {
process() {
// Logika dla karty debetowej
}
}
class PayPalPayment {
process() {
// Logika dla PayPal
}
}
Zasada podstawienia Liskov: Obiekty klas bazowych muszą być zastępowalne przez obiekty klas pochodnych bez wpływu na poprawność działania programu. Innymi słowy, jeśli S jest podtypem T, to obiekty typu T mogą być zastąpione obiektami typu S bez zmiany poprawności programu.
class Bird {
fly() {
return "I can fly";
}
}
class Penguin extends Bird {
fly() {
throw new Error("I can't fly"); // Łamie LSP
}
}
class Bird {
move() {
return "I can move";
}
}
class FlyingBird extends Bird {
fly() {
return "I can fly";
}
}
class SwimmingBird extends Bird {
swim() {
return "I can swim";
}
}
Zasada segregacji interfejsów: Lepiej jest mieć wiele małych, wyspecjalizowanych interfejsów niż jeden ogólny. Klient nie powinien być zmuszony do implementacji metod, których nie używa.
class Worker {
work() {
// Implementacja pracy
}
eat() {
// Implementacja jedzenia
}
sleep() {
// Implementacja spania
}
}
class Robot extends Worker {
eat() {
throw new Error("Robots don't eat");
}
sleep() {
throw new Error("Robots don't sleep");
}
}
class Workable {
work() {
// Implementacja pracy
}
}
class NeedsRest {
sleep() {
// Implementacja spania
}
}
class NeedsFood {
eat() {
// Implementacja jedzenia
}
}
class Human extends Workable {
constructor(foodNeeds, restNeeds) {
super();
this.foodNeeds = foodNeeds;
this.restNeeds = restNeeds;
}
}
class Robot extends Workable {
// Tylko praca, bez jedzenia i spania
}
Zasada odwrócenia zależności: Moduły wysokopoziomowe nie powinny zależeć od modułów niskopoziomowych. Oba powinny zależeć od abstrakcji. Abstrakcje nie powinny zależeć od szczegółów implementacji.
class EmailNotifier {
constructor() {
this.emailService = new EmailService();
}
notify(user, message) {
this.emailService.sendEmail(user.email, message);
}
}
class Notifier {
constructor(notificationService) {
this.notificationService = notificationService;
}
notify(user, message) {
this.notificationService.send(user, message);
}
}
class EmailService {
send(user, message) {
// Wysyłanie emaila
}
}
class SMSService {
send(user, message) {
// Wysyłanie SMS
}
}
// Użycie:
const emailNotifier = new Notifier(new EmailService());
const smsNotifier = new Notifier(new SMSService());
Stosowanie zasad SOLID pomaga tworzyć kod, który jest:
Zasady SOLID są szczególnie istotne w JavaScript, gdzie:
Każda z zasad SOLID koncentruje się na innym aspekcie projektowania obiektowego, ale wszystkie razem tworzą spójny zestaw wytycznych prowadzących do lepszej jakości kodu.