window.pipedriveLeadboosterConfig = { base: 'leadbooster-chat.pipedrive.com', companyId: 11580370, playbookUuid: '22236db1-6d50-40c4-b48f-8b11262155be', version: 2, } ;(function () { var w = window if (w.LeadBooster) { console.warn('LeadBooster już istnieje') } else { w.LeadBooster = { q: [], on: function (n, h) { this.q.push({ t: 'o', n: n, h: h }) }, trigger: function (n) { this.q.push({ t: 't', n: n }) }, } } })() Zasada otwarte-zamknięte. Czy kiedykolwiek będę musiał jej użyć? - The Codest
The Codest
  • O nas
  • Nasze Usługi
    • Software Development
      • Frontend Development
      • Backend Development
    • Zespoły IT
      • Programiści frontendowi
      • Backend Dev
      • Inżynierowie danych
      • Inżynierowie rozwiązań chmurowych
      • Inżynierowie QA
      • Inne
    • Konsultacje IT
      • Audyt i doradztwo
  • Branże
    • Fintech i bankowość
    • E-commerce
    • Adtech
    • Healthtech
    • Produkcja
    • Logistyka
    • Motoryzacja
    • IOT
  • Wartość dla
    • CEO
    • CTO
    • Delivery Managera
  • Nasz zespół
  • Case Studies
  • Nasze Know How
    • Blog
    • Meetups
    • Webinary
    • Raporty
Kariera Skontaktuj się z nami
  • O nas
  • Nasze Usługi
    • Software Development
      • Frontend Development
      • Backend Development
    • Zespoły IT
      • Programiści frontendowi
      • Backend Dev
      • Inżynierowie danych
      • Inżynierowie rozwiązań chmurowych
      • Inżynierowie QA
      • Inne
    • Konsultacje IT
      • Audyt i doradztwo
  • Wartość dla
    • CEO
    • CTO
    • Delivery Managera
  • Nasz zespół
  • Case Studies
  • Nasze Know How
    • Blog
    • Meetups
    • Webinary
    • Raporty
Kariera Skontaktuj się z nami
Strzałka w tył WSTECZ
2019-07-02
Software Development

Zasada otwarte-zamknięte. Czy kiedykolwiek będę musiał jej użyć?

Mateusz Leśniak

Większość programistów słyszała o zasadzie open - closed - jednej z zasad SOLID wujka Boba. Brzmi to rozsądnie, ale może być nieco niewyraźne do czasu pierwszego użycia na "żywym" kodzie. Pełna treść zasady brzmi: jednostki oprogramowania (klasy, moduły, funkcje itp.) powinny być otwarte na rozszerzenia, ale zamknięte na modyfikacje.

Co to tak naprawdę oznacza?

Natknęliśmy się na problem programistyczny, który pokazał nam, na czym tak naprawdę polega zasada open-closed. W jednej z naszych aplikacji webowych mieliśmy formularz z dwiema sekcjami (między innymi):

  • kanały popytu
  • filtry dynamiczne

Użytkownicy mogą dodawać dowolną liczbę filtrów, ale istnieją pewne zasady - dostępność filtrów zależy od wybranych kanałów.

Kanały popytu: ADWYMIANA, NAGŁÓWEKBIDDING, RESERVATION, OTHER Dynamiczne filtry (wymiary): strona internetowa, ogłoszenieunit, geo, creativerozmiar, urządzenie

Ten artykuł dotyczy głównie refaktoryzacji kodu, więc poniżej będzie dużo fragmentów kodu. Starałem się je zredukować, ale pewna ilość kodu jest niezbędna do pokazania refaktoryzacja kodu. Nie musisz rozumieć każdej małej części kodu, aby zrozumieć główną ideę.

Pierwsza implementacja problemu była prosta:

class ResearchFormStateUpdater {
  update () {
    (...)
    this._updateDynamicFilters();
  }

  _updateDynamicFilters () {
    $('.dynamic-filter').each((_, filter) => {
      $(filter).trigger('dynamicFilter:disableWebsites', this._shouldDisableWebsitesFields());
    });
  }

  _shouldDisableWebsitesFields () {
    return this._shouldDisableFields(ResearchFormStateUpdater.WEBSITE_DISABLING_DEMAND_CHANNELS);
  }

  _shouldDisableFields(disablingDemandChannels) {
    // czy którykolwiek z disablingDemandChannels jest zaznaczony?
  }
}

ResearchFormStateUpdater.WEBSITE_DISABLING_DEMAND_CHANNELS = ['header_bidding', 'reservation', 'other'];

class ResearchDynamicFilter {
  _setDynamicFilterDisableWebsitesEvent () {
    $(this._getBody()).on('dynamicFilter:disableWebsites', (event, shouldDisableWebsites) => {
      // wyłącz filtry stron internetowych
    });
  }
}

Jak widać, filtr witryny powinien być niedostępny dla HEADERKanały BIDDING, RESERVATION i OTHER są więc dostępne tylko dla ADKanał EXCHANGE.

Ostatnią rzeczą, jaką można powiedzieć o kodzie, jest to, że jest stały lub statyczny. Mamy więc więcej żądań od naszych klientów, dzięki czemu te klasy są większe i bardziej złożone.

Rozwój funkcji

  • Dodaj kolejny kanał - EBDA (Filtr witryny powinien być niedostępny po wybraniu EBDA):

    • rozwinąć się DISABLING_DEMAND_CHANNELS według kanału popytu EBDA
    • dużo zmian nazw - w pierwszej implementacji określiliśmy stronę internetową w nazwach metod i stałych. Na przykład:

      • isWebsitesDimensionDisabled do _areFormStateDimensionsDisabled
      • WEBSITE_DISABLING_DEMAND_CHANNELS do DISABLING_DEMAND_CHANNELS

Uwaga spoiler -> gdy komponent jest otwarty na zmiany, w przyszłości będzie wiele zmian nazw. Nie będziemy zwracać na to uwagi w następnych krokach.

  • Dodaj kolejny filtr dla "Produktu (Produkt Schemat dostępności filtrów jest taki sam jak na stronie internetowej)

    • ResearchDynamicFilter klasa musi sprawdzać jeszcze jeden wymiar podczas wyłączania/włączania pól
  • Przejdźmy na większy poziom i dodajmy przełącznik nad kanałami -> "Źródło". Wszystkie kanały popytu, które mieliśmy do tej pory, znajdują się w źródle Ad Managera. Nowe źródło - SSP - nie ma kanałów popytu, a jedynym dostępnym filtrem jest strona internetowa.

    • Zasady:

      • Istnieją dwa stany źródła: Ad Manager, SSP.
      • Wszystkie nasze kanały popytu są dostępne tylko dla źródła Ad Manager.
      • Nie ma kanałów popytu dla źródła SSP
      • "Strona internetowa" jest jedynym filtrem dostępnym dla źródła SSP.
    • Wdrożenie:

      • Po wybraniu opcji "SSP":

        • Wyłącz kanały na żądanie.
        • spust 'dynamicFilter:disableWebsitesAndProducts' <- włącz oba
        • spust 'dynamicFilter:disableNonSspOptions'
      • Po zaznaczeniu opcji Menedżer reklam:

        • spust 'dynamicFilter:disableWebsitesAndProducts' <- sprawdź pogodę włączoną lub wyłączoną
  • Dodaj kolejny filtr dla "Platformy

    • Zasady:

      • Platforma jest dostępna tylko wtedy, gdy źródłem jest SSP
    • Trudność:

      • Teraz mamy "Witrynę", która jest dostępna dla kanału AD_EXCHANGE z Menedżera reklam i dla Ssp oraz mamy "Platformę", która jest dostępna dla Ssp, ale nie dla Menedżera reklam.
      • Przełączanie stan formularza może być naprawdę skomplikowany i mylący

Wdrożenie z nową funkcjonalnością:

Przedstawiam następny snippet głównie w celu pokazania złożoności kodu. Zapraszam do pozostawienia go ukrytego.

class ResearchFormStateUpdater {
  update () {
    (...)
    this._triggerCallbacks();
  }

  _triggerCallbacks () {
    // wybierz wywołania zwrotne w zależności od źródła
  }

  _adManagerSourceCallbacks () {
    (...)
    this._enableDemandChannels(ResearchFormStateUpdater.AD_MANAGER_DEMAND_CHANNELS);
    this._updateDefaultStateOfDynamicFilters();
    this._updateAdManagerDynamicFilters();
  }

  _sspSourceCallbacks () {
    (...)
    this._removeDemandChannelsActiveClassAndDisable(ResearchFormStateUpdater.AD_MANAGER_DEMAND_CHANNELS);
    this._updateDefaultStateOfDynamicFilters();
  }

  _updateDefaultStateOfDynamicFilters () {
    $('.dynamic-filter').each((_, filter) => {
      $(filter).trigger('dynamicFilter:enableSspFilters', this.isSourceSsp);
    });
  }

  _updateAdManagerDynamicFilters () {
    $('.dynamic-filter').each((_, filter) => {
      $(filter).trigger('dynamicFilter:disableWebsitesAndProducts', this._areFormStateDimensionsDisabled() && !this.isSourceSsp);
    });
  }

  _shouldDisableFields (disablingDemandChannels) {
    // czy którykolwiek z disablingDemandChannels jest zaznaczony?
  }
}

ResearchFormStateUpdater.AD_MANAGER_DISABLING_DEMAND_CHANNELS = ['header_bidding', 'reservation', 'other', 'ebda'];

class ResearchDynamicFilter {
  // Nie uprościłem tych dwóch metod, aby pokazać obecną złożoność implementacji.

  _setDefaultDynamicFiltersToggleEvent () {
    $(this._getBody()).on('dynamicFilter:enableSspFilters', (event, shouldEnableSspOptions) => {
      this._setDefaultFiltersOptionDisabledState(shouldEnableSspOptions);

      const selectedFilterDimension = this._getFiltersDimension().find('option:selected').val();
      if (selectedFilterDimension == 'website') {
        this._toggleChosenFilterDisabledState(false);
      } else if (selectedFilterDimension == 'platform') {
        this._toggleChosenFilterDisabledState(!shouldEnableSspOptions);
      } else {
        this._toggleChosenFilterDisabledState(shouldEnableSspOptions);
      }
    });
  }

  _setDynamicFilterDisableWebsitesAndProductsEvent () {
    $(this._getBody()).on('dynamicFilter:disableWebsitesAndProducts', (event, shouldDisableWebsitesAndProducts) => {
      const selectedFilterDimension = this._getFiltersDimension().find('option:selected').val();
      if ($.inArray(selectedFilterDimension, ['website', 'product']) >= 0) {
        this._toggleChosenFilterDisabledState(shouldDisableWebsitesAndProducts);
      }
      this._setMethodSelectWebsiteAndProductOptionDisabledState(shouldDisableWebsitesAndProducts);
    });
  }

  _toggleNonSspFilters (dimensionSelect, shouldDisable) {
    $.each(ResearchDynamicFilter.NON_SSP_FILTERS_OPTIONS, (_, option) => {
      // przełączanie stanu filtra w zależności od "shouldDisable
    });
  }
}

ResearchDynamicFilter.NON_SSP_FILTERS_OPTIONS = ['ad_unit', 'creative_size', 'geo', 'device', 'product'];

Nadal używamy niektórych 'toggle' mechanizm. Naprawdę trudno jest przełączyć 4 dźwignie i osiągnąć oczekiwany stan, a teraz DynamicFilter musi wiedzieć, które wymiary nie są dla źródła ssp.

Mamy ResearchFormStateUpdater, dlaczego nie miałby być odpowiedzialny?

Wniosek końcowy

Dodaj kolejny filtr dla "Yield partner

Dokładnie w tym momencie zdecydowaliśmy się na refaktoryzację tych klas. Analizowane kanały i filtry to tylko niewielka część problemu. Jest tu wiele sekcji formularzy i wszystkie mają ten sam problem. Nasz refactor powinien zneutralizować potrzebę zmiany metod wewnątrz tych klas *aby* dodać nowe kanały lub wymiary.

W następnym fragmencie pozostawiłem główne klasy prawie tak, jak są w naszym kodzie produkcyjnym, aby pokazać, jak łatwo je teraz zrozumieć.

class ResearchFormStateUpdater {
  update () {
    (...)
    this._updateDynamicFilters();
  }

  _updateDynamicFilters () {
    this._toggleAllDynamicFiltersState(this._dynamicFiltersDimensionsToBeDisabled());
  }

  _dynamicFiltersDimensionsToBeDisabled () {
    if (this.isSourceSsp) { return ResearchFormStateUpdater.NO_SSP_FILTERS; }

    var disabledFilters = ResearchFormStateUpdater.ONLY_SSP_FILTERS;
    if (this.areDemandChannelsExceptAdxSelected) {
      disabledFilters = disabledFilters.concat(ResearchFormStateUpdater.ONLY_ADX_FILTERS);
    }
    return disabledFilters;
  }

  _toggleAllDynamicFiltersState (disabledFilters) {
    $('.dynamic-filter').each((_, filter) => {
      this._toggleDynamicFilterState(filter, disabledFilters);
    });
  }

  _toggleDynamicFilterState(dynamicFilter, disabledFilters) {
    $(dynamicFilter).trigger('dynamicFilter:toggleDynamicFilters', disabledFilters);
  }
}

ResearchFormStateUpdater.NO_SSP_FILTERS = ['ad_unit', 'creative_size', 'geo', 'device', 'product'];

ResearchFormStateUpdater.ONLY_SSP_FILTERS = ['platform'];

ResearchFormStateUpdater.ONLY_ADX_FILTERS = ['website', 'product'];

class ResearchDynamicFilter {
  _setDynamicFiltersToggleEvent () {
    $(this._getBody()).on('dynamicFilter:toggleDynamicFilters', (event, disabledFilters) => {
      this._disableFilters(disabledFilters.split(','));
      this._enableFilters(disabledFilters.split(',')));
    });
  }

  _disableFilters (filtersToDisable) {
    // wyłącz filtryToDisable
  }

  _enableFilters (filtersToDisable) {
    const filtersToEnable = $(ResearchDynamicFilter.ALL_FILTERS).not(filtersToDisable).get();
    // włącz filtersToEnable
  }
}

ResearchDynamicFilter.ALL_FILTERS = ['website', 'ad_unit', 'creative_size', 'geo', 'device', 'product', 'platform'];

Udało nam się! Naprawdę?

Teraz jedyną rzeczą, którą "ResearchDynamicFilter" musi znać, jest lista wszystkich filtrów - wydaje się sprawiedliwe. Reszta logiki i kontroli pochodzi z góry - niektóre wyższe metody i stałe.

Wypróbujmy więc naszą nową strukturę, dodając filtr dla "Yield_partner":

class ResearchFormStateUpdater {
  _dynamicFiltersDimensionsToBeDisabled () {
    (...)
    if (this.areDemandChannelsExceptEbdaSelected) {
      disabledFilters = disabledFilters.concat(ResearchFormStateUpdater.ONLY_EBDA_FILTERS);
    }
    return disabledFilters;
  }
}

ResearchFormStateUpdater.NO_SSP_FILTERS = [(...), 'yield_partner'];

ResearchFormStateUpdater.ONLY_EBDA_FILTERS = [(...), 'yield_partner'];

ResearchDynamicFilter.ALL_FILTERS = [(...), 'yield_partner'];

Jak widać, wszystko polega na dodaniu pewnych wartości do stałych i kilku dodatkowych warunków.

Dzięki zasadzie "open-closed" jesteśmy w stanie zmienić logikę biznesową formularza, dodając jedynie pewne wartości i warunki na wyższym poziomie abstrakcji. Nie musimy wchodzić do komponentu i zmieniać czegokolwiek. Ten refaktoring wpłynął na cały formularz i było więcej sekcji, a wszystkie są teraz zgodne z zasadą open-closed.

Nie zmniejszyliśmy ilości kodu - w rzeczywistości nawet ją zwiększyliśmy (przed/po):

  • ResearchFormStateUpdater - 211/282 linie
  • ResearchDynamicFilter - 267/256 linii

Chodzi o kolekcję w stałych -> to teraz nasz publiczny interfejs, nasza konsola do kontrolowania procesu bez dziesiątek przełączników.

Czytaj także:

  • Jak napisać dobry i jakościowy kod?
  • Vuelendar. Nowy projekt Codest oparty na Vue.js
  • Czym jest Ruby on Jets i jak zbudować aplikację przy jego użyciu?

Powiązane artykuły

Software Development

Tworzenie przyszłościowych aplikacji internetowych: spostrzeżenia zespołu ekspertów The Codest

Odkryj, w jaki sposób The Codest wyróżnia się w tworzeniu skalowalnych, interaktywnych aplikacji internetowych przy użyciu najnowocześniejszych technologii, zapewniając płynne doświadczenia użytkowników na wszystkich platformach. Dowiedz się, w jaki sposób nasza wiedza napędza transformację cyfrową i biznes...

THEECODEST
Software Development

10 najlepszych firm tworzących oprogramowanie na Łotwie

Dowiedz się więcej o najlepszych łotewskich firmach programistycznych i ich innowacyjnych rozwiązaniach w naszym najnowszym artykule. Odkryj, w jaki sposób ci liderzy technologiczni mogą pomóc w rozwoju Twojej firmy.

thecodest
Rozwiązania dla przedsiębiorstw i scaleupów

Podstawy tworzenia oprogramowania Java: Przewodnik po skutecznym outsourcingu

Zapoznaj się z tym niezbędnym przewodnikiem na temat skutecznego tworzenia oprogramowania Java outsourcing, aby zwiększyć wydajność, uzyskać dostęp do wiedzy specjalistycznej i osiągnąć sukces projektu z The Codest.

thecodest
Software Development

Kompletny przewodnik po outsourcingu w Polsce

Wzrost liczby outsourcing w Polsce jest napędzany przez postęp gospodarczy, edukacyjny i technologiczny, sprzyjający rozwojowi IT i przyjazny klimat dla biznesu.

TheCodest
Rozwiązania dla przedsiębiorstw i scaleupów

Kompletny przewodnik po narzędziach i technikach audytu IT

Audyty IT zapewniają bezpieczne, wydajne i zgodne z przepisami systemy. Dowiedz się więcej o ich znaczeniu, czytając cały artykuł.

The Codest
Jakub Jakubowicz CTO & Współzałożyciel

Subskrybuj naszą bazę wiedzy i bądź na bieżąco!

    O nas

    The Codest - Międzynarodowa firma programistyczna z centrami technologicznymi w Polsce.

    Wielka Brytania - siedziba główna

    • Office 303B, 182-184 High Street North E6 2JA
      Londyn, Anglia

    Polska - lokalne centra technologiczne

    • Fabryczna Office Park, Aleja
      Pokoju 18, 31-564 Kraków
    • Brain Embassy, Konstruktorska
      11, 02-673 Warszawa, Polska

      The Codest

    • Strona główna
    • O nas
    • Nasze Usługi
    • Case Studies
    • Nasze Know How
    • Kariera
    • Słownik

      Nasze Usługi

    • Konsultacje IT
    • Software Development
    • Backend Development
    • Frontend Development
    • Zespoły IT
    • Backend Dev
    • Inżynierowie rozwiązań chmurowych
    • Inżynierowie danych
    • Inne
    • Inżynierowie QA

      Raporty

    • Fakty i mity na temat współpracy z zewnętrznym partnerem programistycznym
    • Z USA do Europy: Dlaczego amerykańskie startupy decydują się na relokację do Europy?
    • Porównanie centrów rozwoju Tech Offshore: Tech Offshore Europa (Polska), ASEAN (Filipiny), Eurazja (Turcja)
    • Jakie są największe wyzwania CTO i CIO?
    • The Codest
    • The Codest
    • The Codest
    • Privacy policy
    • Warunki korzystania z witryny

    Copyright © 2025 by The Codest. Wszelkie prawa zastrzeżone.

    pl_PLPolish
    en_USEnglish de_DEGerman sv_SESwedish da_DKDanish nb_NONorwegian fiFinnish fr_FRFrench arArabic it_ITItalian jaJapanese ko_KRKorean es_ESSpanish nl_NLDutch etEstonian elGreek pl_PLPolish