Czy przeciążanie jest przykładem zasady otwartej / zamkniętej?

12

Wikipedia mówi

„jednostki oprogramowania (klasy, moduły, funkcje itp.) powinny być otwarte na rozszerzenie, ale zamknięte na modyfikację”

Słowo funkcje złapany oczy, a ja teraz zastanawiam się, czy możemy założyć, że tworzenie przeciążenie dla metody można uznać za przykład otwarty / zamknięty zasady czy nie?

Pozwól mi wyjaśnić przykład. Weź pod uwagę, że masz metodę w warstwie usług, która jest używana w prawie 1000 miejscach. Metoda pobiera userId i określa, czy użytkownik jest administratorem, czy nie:

bool IsAdmin(userId)

Teraz rozważ, że gdzieś trzeba ustalić, czy użytkownik jest administratorem, czy nie, na podstawie nazwy użytkownika, a nie identyfikatora użytkownika. Jeśli zmienimy podpis wyżej wspomnianej metody, złamaliśmy kod w 1000 miejscach (funkcje powinny być zamknięte na modyfikacje). W ten sposób możemy utworzyć przeciążenie, aby uzyskać nazwę użytkownika, znaleźć identyfikator użytkownika na podstawie nazwy użytkownika i oryginalną metodę:

public bool IsAdmin(string username)
{
    int userId = UserManager.GetUser(username).Id;
    return IsAdmin(userId);
}

W ten sposób rozszerzyliśmy naszą funkcję, tworząc dla niej przeciążenie (funkcje powinny być otwarte na rozszerzenie).

Czy to przykład zasady otwartej / zamkniętej?

Saeed Neamati
źródło

Odpowiedzi:

5

Osobiście interpretowałbym w ten sposób instrukcję wiki:

  • dla klasy: możesz odziedziczyć klasę i zastąpić ją lub rozszerzyć jej funkcjonalność, ale nie rób hakowania oryginalnej klasie, zmieniając w ten sposób jej działanie.
  • w przypadku modułu (na przykład biblioteki): napisz nowy moduł / bibliotekę, która otacza oryginał, łączy funkcje w łatwiejsze w użyciu wersje lub rozszerza oryginał o dodatkowe funkcje, ale nie zmieniaj oryginalnego modułu.
  • dla funkcji (tj. funkcji statycznej, a nie metody klasowej): twój przykład jest dla mnie uzasadniony; ponownie użyj oryginalnej funkcji IsAdmin (int) w nowym IsAdmin (ciąg). oryginał się nie zmienia, nowy func rozszerza swoją funkcjonalność.

strzał w stopę jest jednak taki, że jeśli twój kod używa klasy „cUserInfo”, w której rezyduje IsAdmin (int), to zasadniczo łamiesz regułę i zmieniasz klasę. reguła byłaby niestety zachowana tylko wtedy, gdy utworzysz nową klasę cUserWithNameInfo: publiczną klasę cUserInfo i umieścisz tam nadpisanie IsAdmin (string). Gdybym był właścicielem bazy kodu, nigdy nie przestrzegałbym reguły. Powiedziałbym bzdury i po prostu dokonaj zmiany, którą sugerujesz.

Sassafras_wot
źródło
3

Po pierwsze, twój przykład jest niestety wymyślony. Nigdy nie zrobiłbyś tego w prawdziwym świecie, ponieważ powoduje to niepotrzebne podwójne podwójne. Lub, co gorsza, ponieważ identyfikator użytkownika i nazwa użytkownika mogą w pewnym momencie stać się tego samego typu. Zamiast

public bool IsAdmin(int userid)
{
    User user = UserManager.GetUser(userid);
    return user.IsAdmin();
}

public bool IsAdmin(string username)
{
    int userId = UserManager.GetUser(username).Id;
    return IsAdmin(userId);
}

Powinieneś naprawdę przedłużyć tę klasę.

public bool IsAdmin(int userid)
{
    User user = UserManager.GetUserById(userid);
    return user.IsAdmin();
}

public bool IsUsernameAdmin(string username)
{
    User userId = UserManager.GetUserByName(username);
    return user.IsAdmin();
}

Możesz dodatkowo refaktoryzować i zmienić nazwę pierwszej metody na IsUserIdAdmin, aby zachować spójność, ale nie jest to konieczne. Chodzi o to, że dodając nową metodę i gwarantując, że kod wywołania nie zostanie uszkodzony, odkryłeś, że twoja klasa jest rozszerzalna. Lub innymi słowy, otwarty na rozszerzenie .

A oto kwestia zasady otwartego zamknięcia. Nigdy tak naprawdę nie wiesz, jak dobrze twój kod jest zgodny, dopóki nie spróbujesz go rozszerzyć i nie będziesz musiał go modyfikować (ze zwiększonym ryzykiem, które się z tym wiąże). Ale z doświadczeniem uczysz się przewidywać.

Jak mówi wujek Bob (moje podkreślenie):

Ponieważ zamknięcie nie może być kompletne, musi być strategiczne. Oznacza to, że projektant musi wybrać rodzaj zmian, przed którymi ma zamknąć swój projekt. Wymaga to pewnej wiedzy z doświadczenia . Doświadczony projektant zna użytkowników i branżę wystarczająco dobrze, aby ocenić prawdopodobieństwo różnych rodzajów zmian. Następnie upewnia się, że przy najbardziej prawdopodobnych zmianach przywoływana jest zasada otwartego zamknięcia.

pdr
źródło
Dziękuję za dobre wyjaśnienie. Ale posiadanie 1000 podróży w obie strony lub jednej podróży w obie strony nie jest tutaj głównym celem. Dlatego zapomnijmy o wydajności w tej chwili. Jednak rozszerzyłem klasę, ponieważ dodałem do niej inną metodę, choć o tej samej nazwie, ale o różnych parametrach wejściowych. Ale naprawdę doceniam twój cytat wuja Boba . +1. ;)
Saeed Neamati,
@ SaeedNeamati: Chodzi mi o to, że nie ma znaczenia, czy dodajesz metodę, która wykorzystuje istniejącą metodę, czy dodajesz metodę całkowicie niezależną, niezależnie od tego, czy twoja klasa była otwarta na rozszerzenie. Ale jeśli musiałbyś zmodyfikować istniejący interfejs metody, aby to osiągnąć, to nie jesteś zamknięty na modyfikacje.
pdr
2

Moduły zgodne z zasadą otwartego i zamkniętego mają dwa podstawowe atrybuty.

  1. Są „otwarte na rozszerzenie”. Oznacza to, że zachowanie modułu można rozszerzyć. Możemy sprawić, że moduł będzie zachowywał się w nowy i różny sposób, gdy zmieniają się wymagania aplikacji lub w celu zaspokojenia potrzeb nowych aplikacji.
  2. Są „zamknięte dla modyfikacji”. Kod źródłowy takiego modułu jest nienaruszony. Nikomu nie wolno dokonywać w nim zmian w kodzie źródłowym.
  1. Przeciążając metodę rozszerzasz funkcjonalność istniejącego modułu, spełniając tym samym nowe potrzeby Twojej aplikacji

  2. Nie wprowadzasz żadnych zmian w istniejącej metodzie, więc nie naruszasz drugiej reguły.

Chciałbym usłyszeć, co inni mają do powiedzenia na temat niedopuszczania do zmiany oryginalnej metody. Tak, możesz wprowadzić odpowiednie modyfikatory dostępu i wymusić enkapsulację, ale co jeszcze można zrobić?

Patrz: http://www.objectmentor.com/resources/articles/ocp.pdf

CodeART
źródło
1

Zasada Otwartego Zamknięcia to cel, idealny przypadek, nie zawsze rzeczywistość. Szczególnie w przypadku refaktoryzacji starego kodu pierwszym krokiem jest często ciężka modyfikacja, aby umożliwić OCP. Podstawą tej zasady jest to, że działający kod już działa, a zmiana prawdopodobnie wprowadza błędy. Dlatego najlepszym scenariuszem nie jest zmiana istniejącego kodu, a jedynie dodanie nowego kodu.

Powiedzmy, że masz funkcję o nazwie BigContrivedMethod(int1, int2, string1). BigContrivedMethodrobi trzy rzeczy: rzecz1, rzecz2 i rzecz3. W tym momencie ponowne użycie BCM jest prawdopodobnie trudne, ponieważ robi za dużo. Przeredagowanie go (jeśli to możliwe) na ContrivedFunction1(int), ContrivedFunction2(int)i ContrivedFunction3(string)daje trzy mniejsze, bardziej skoncentrowane metody, które można łatwiej łączyć.

I to jest klucz do OCP w odniesieniu do metod / funkcji: kompozycji. „Rozszerzasz” funkcje, wywołując je z innych funkcji.

Pamiętaj, że OCP jest częścią 5 innych zasad, wytycznych SOLID. Ten pierwszy jest kluczem, Single Responsibility. Jeśli wszystko w bazie kodu wykonało tylko jedną konkretną czynność, nigdy nie musiałbyś modyfikować kodu. Wystarczy tylko dodać nowy kod lub połączyć stary kod w nowy sposób. Ponieważ prawdziwy kod rzadko spełnia tę wytyczną, często trzeba go zmodyfikować, aby uzyskać SRP, zanim będzie można uzyskać OCP.

CodexArcanum
źródło
Myślę, że mówisz tutaj o Dyrektorze ds. Jednolitej Odpowiedzialności, a nie OCP.
Saeed Neamati,
1
Mówię, że nie można realistycznie mieć OCP bez SRP. Kod, który robi za dużo, nie może zostać rozszerzony, nie można go ponownie użyć. SOLID jest prawie napisany w kolejności ważności, przy czym każda zasada opiera się na wcześniejszych, aby była wykonalna.
CodexArcanum
Tę odpowiedź należy bardziej docenić.
Ashish Gupta,
0

Jeśli zmieniasz zachowanie programu, pisząc nowy kod, a nie stary, postępujesz zgodnie z zasadą otwartego zamknięcia.

Ponieważ jest całkiem niemożliwe, aby napisać kod, który jest otwarty na każdą możliwą zmianę (a nie chcesz, ponieważ wprowadzasz paraliż analizy), piszesz kod, który reaguje na wszystkie różne zachowania, nad którymi obecnie pracujesz, poprzez rozszerzenie, a nie modyfikację.

Kiedy natrafisz na coś, co musisz zmienić, zamiast po prostu coś zmienić, aby umożliwić nowe zachowanie, zastanawiasz się, jaki jest sens zmiany, zrestrukturyzujesz program bez zmiany jego zachowania, abyś mógł zmienić to zachowanie, pisząc NOWY kod .

Jak to dotyczy twojej sprawy:

Jeśli dodajesz nowe funkcje do swojej klasy, twoja klasa nie jest otwarta / zamknięta, ale klientami funkcji, którą przeciążasz, są.

Jeśli dodajesz po prostu nowe funkcje, które działają w twojej klasie, zarówno ona, jak i klienci twojej funkcji są otwarci / zamknięci.

Edward Strange
źródło