sOlid…ne programowanie!

Ostatnio było “S”, a dziś będzie o “O”, czyli Open/Closed Principle z pakiety zasad SOLID. Zasada ta mówi, że program powinien być otwarty na rozwijanie, lecz zamknięty na modyfikacje. Na pierwszy rzut oka wygląda jak “masło maślane”. Jak można rozwijać element programu, nie wprowadzając modyfikacji?

O co chodzi….?

Mianowicie twórca tej zasady chciał, aby zmiany wprowadzone w istniejącym kodzie nie powodowały potrzeby modyfikowania kodu źródłowego istniejących bytów (klas, metod itd.). Nowy kod ma rozszerzać możliwości elementu programu bez zmiany starego kodu. Zadanie wydaje się dość karkołomne… jednak takie nie jest.
Do czasu jak nie byłem programistą, moje myślenie ograniczało się do “tu i teraz” podlanego znaną wiedzą. W programowaniu często zdarza się, że musimy na wszystko zerknąć z „góry”. Wyjść na inny poziom abstrakcji. I tak dokładnie jest w tym miejscu. Wyższy poziom abstrakcji można uzyskać poprzez wprowadzenie interfejsów (tutaj wpis o interfejsach).

Jak nie pisać – przykład

Koniec opowiadania, czas na przykład.

Załóżmy, że piszemy system bankowy, w którym właśnie kodujemy serwis obsługujący możliwość realizacji płatności.

Stworzyliśmy klasę:

class PaymentService {

	private final RegularTransferPaymentProvider regularTransferPaymentProvider;

	public PaymentService(RegularTransferPaymentProvider regularTransferPaymentProvider) {
	return this regularTransferPaymentProvder = regularTransferPaymentProvider; }

	public void initializePayment() {
		regularTransferPaymentProvider.pay();
       }
}

oraz klasę wstrzykiwaną powyżej:

class RegularTransferPaymentProvider {

	public void pay() {
		….do payment processing here….
	}
}

Wtem przychodzi do nas klient i oznajmia, że będziemy też potrzebowali płatności kartą. Musimy więc zmodyfikować istniejący kod (zwróć uwagę na czasownik w tym zdaniu :)).

class CardPaymentProvider {
	
	public void pay() {
		….do card payment processing…...
	}
}

Dodajemy metodę do płatności do PaymentService. Wstrzykujemy nowy provider i zmieniamy nazwę metody.

class PaymentService {

	private final RegularTransferPaymentProvider regularTransferPaymentProvider;
        private final CardPaymentProvider cardPaymentProvider ;

	public PaymentService(RegularTransferPaymentProvider regularTransferPaymentProvider, CardPaymentProvider cardPaymentProvider) {
	this regularTransferPaymentProvder = regularTransferPaymentProvider;
        this.cardPaymentProvider = cardPaymentProvider; }

	public void initializeRegularTranferPayment() {
		regularTransferPaymentProvider.pay();
}

         public void initializeCardPayment() {
		cardPaymentProvider.pay();
     }
}

Tworząc na początku taki schemat działania, programista idzie na skróty. Do czasu dodania nowego wymagania, wszystko jest w porządku. Nowe wymaganie wymusiło jednak modyfikację kodu źródłowego klasy PaymentService. Nowe metody, zmiana nazewnictwa itd. (pewnie i testy trzeba byłoby dostosować do nowej logiki tej klasy).

Jak wprowadzić zasadę Open/Closed?

Należy stworzyć abstrakcję, będącą nad elementami, które mogą być dodawane, aby rozszerzyć działanie serwisu. W powyższym przypadku są to klasy dostarczające metody płatności (RegularTransferPaymentProvider oraz CardPaymentProvider). Obydwie wykonują tę samą czynność, procesują płatność (dla swojego obszaru działania tj. transfer, czy płatność kartą).

Tworzymy interfejs PaymentProvider:

interface PaymentProvider {
	
	void pay();
}

Następnie tozszerzamy klasy:

class RegularTransferPaymentProvider implements PaymentProvider {
	
	@Override
	public void pay() {
		….do payment processing here….
	}
}	
class CardPaymentProviderimplements implements PaymentProvider {
	
	@Override
	public void pay() {
		….do card payment processing…....
	}
}	

Po takim zabiegu klasa PaymentService może przyjąć następujący kształt:

class PaymentService {

	private final PaymentProvider paymentProvider;

	// constructor here

	public void initializePayment() {
		paymentProvider.pay();
       }
}

Podsumowanie

Niezależnie od tego ile nowych metod płatności wymyśli klient do swojej aplikacji, klasa PaymentService pozostanie niezmienna.

Do zobaczenia za tydzień. Postaram się wtedy przybliżyć zasadę “L” z pakietu zasad SOLID.

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *