Zadanie polega na skonfigurowaniu sprzętu w urządzeniu zgodnie z niektórymi specyfikacjami wejściowymi. Należy to osiągnąć w następujący sposób:
1) Zbierz informacje o konfiguracji. Może się to zdarzyć w różnych momentach i miejscach. Na przykład zarówno moduł A, jak i moduł B mogą żądać (w różnym czasie) niektórych zasobów od mojego modułu. Te „zasoby” są w rzeczywistości tym, czym jest konfiguracja.
2) Po tym, jak jasne jest, że nie będzie już realizowanych żądań, do sprzętu należy wysłać polecenie startowe, zawierające podsumowanie żądanych zasobów.
3) Dopiero potem można (i trzeba) dokonać szczegółowej konfiguracji wspomnianych zasobów.
4) Również tylko po 2) można (i trzeba) przekierować wybrane zasoby do zadeklarowanych rozmówców.
Częstą przyczyną błędów, nawet dla mnie, który to napisał, jest pomyłka w tej kolejności. Jakie konwencje nazewnictwa, projekty lub mechanizmy mogę zastosować, aby interfejs mógł być używany przez osobę, która zobaczy kod po raz pierwszy?
źródło
discovery
lubhandshake
?Odpowiedzi:
To przeprojektowanie, ale możesz zapobiec niewłaściwemu użyciu wielu interfejsów API, ale nie dysponując żadną metodą, której nie należy wywoływać.
Na przykład zamiast
first you init, then you start, then you stop
Twój konstruktor
init
jest obiektem, który można uruchomić istart
tworzy sesję, którą można zatrzymać.Oczywiście, jeśli masz ograniczenie do jednej sesji na raz, musisz poradzić sobie ze sprawą, w której ktoś próbuje utworzyć taką z już aktywną.
Teraz zastosuj tę technikę do własnego przypadku.
źródło
zlib
ijpeglib
są dwoma przykładami, które są zgodne z tym wzorem inicjalizacji. Mimo to wiele dokumentów jest potrzebnych, aby nauczyć programistów tej koncepcji.Możesz mieć metodę uruchamiania zwracającą obiekt, który jest wymaganym parametrem do konfiguracji:
Nawet jeśli twoja
MySession
jest tylko pustą strukturą, wymusi to bezpieczeństwo typu, że żadnaConfigure()
metoda nie może zostać wywołana przed uruchomieniem.źródło
module->GetResource()->Configure(nullptr)
?a, b, c, d
, mogę zacząća
, a następnie użyć go,MySession
aby użyć gob
jako obiektu już uruchomionego, podczas gdy w rzeczywistości tak nie jest.Opierając się na odpowiedzi Cashcova - dlaczego musisz przedstawić dzwoniącemu nowy obiekt, skoro możesz po prostu przedstawić nowy interfejs? Wzór Rebrand:
Możesz także pozwolić ITerminated na implementację IRunnable, jeśli sesję można uruchomić wiele razy.
Twój obiekt:
W ten sposób możesz wywoływać tylko odpowiednie metody, ponieważ na początku masz tylko interfejs IStartable, a metoda run () będzie dostępna tylko po wywołaniu start (); Z zewnątrz wygląda jak wzór z wieloma klasami i obiektami, ale klasa podstawowa pozostaje jedną klasą, do której zawsze się odwołuje.
źródło
Istnieje wiele prawidłowych sposobów rozwiązania problemu. Basile Starynkevitch zaproponował podejście „zero biurokracji”, które pozostawia prosty interfejs i polega na programiście używającym odpowiednio interfejsu. Chociaż podoba mi się to podejście, przedstawię inne, które ma więcej eingineeringu, ale pozwala kompilatorowi wykryć pewne błędy.
Określ, w jakich stanach może znajdować się Twoje urządzenie, as
Uninitialised
,Started
,Configured
i tak dalej. Lista musi być skończona.¹Dla każdego stanu określ
struct
gospodarstwo niezbędne dodatkowe informacje istotne dla tego stanu, npDeviceUninitialised
,DeviceStarted
i tak dalej.Zapakuj wszystkie zabiegi w jeden przedmiot
DeviceStrategy
którym metody wykorzystują struktury zdefiniowane w 2. jako dane wejściowe i wyjściowe. W związku z tym możesz miećDeviceStarted DeviceStrategy::start (DeviceUninitalised dev)
metodę (lub dowolną równoważną, zgodną z konwencjami projektu).Przy takim podejściu prawidłowy program musi wywoływać niektóre metody w sekwencji wymuszonej przez prototypy metody.
Różne stany są niepowiązanymi obiektami, wynika to z zasady substytucji. Jeśli użyteczne jest, aby struktury te miały wspólnego przodka, przypomnij sobie, że wzorzec odwiedzających może być użyty do odzyskania konkretnego typu wystąpienia klasy abstrakcyjnej.
Chociaż opisałem w 3. wyjątkową
DeviceStrategy
klasę, zdarzają się sytuacje, w których możesz chcieć podzielić jej funkcjonalność na kilka klas.Podsumowując je, kluczowe punkty opisanego przeze mnie projektu to:
Ze względu na zasadę podstawiania obiekty reprezentujące stany urządzeń powinny być wyraźne i nie mogą mieć specjalnych relacji dziedziczenia.
Pakuj zabiegi urządzeń w obiekty startowe, a nie w obiekty reprezentujące same urządzenia, tak aby każde urządzenie lub stan urządzenia widział tylko siebie, a strategia widziała je wszystkie i wyrażał możliwe przejścia między nimi.
Przysięgałbym, że widziałem kiedyś opis implementacji klienta Telnet zgodnie z tymi wierszami, ale nie byłem w stanie go znaleźć ponownie. Byłoby to bardzo przydatne odniesienie!
¹: W tym celu postępuj zgodnie z intuicją lub znajdź klasy równoważności metod w swojej rzeczywistej implementacji dla relacji „method₁ ~ method₂ iff. ważne jest, aby używać ich na tym samym obiekcie ”- zakładając, że masz duży obiekt otaczający wszystkie zabiegi na twoim urządzeniu. Obie metody wyświetlania stanów dają fantastyczne wyniki.
źródło
Użyj wzorca konstruktora.
Mieć obiekt, który ma metody dla wszystkich operacji wymienionych powyżej. Jednak nie wykonuje tych operacji od razu. Po prostu zapamiętuje każdą operację na później. Ponieważ operacje nie są wykonywane od razu, kolejność ich przekazywania do konstruktora nie ma znaczenia.
Po zdefiniowaniu wszystkich operacji w
execute
kreatorze wywołujesz metodę . Po wywołaniu tej metody wykonuje ona wszystkie wymienione powyżej czynności we właściwej kolejności z operacjami zapisanymi powyżej. Ta metoda jest również dobrym miejscem do przeprowadzenia kontroli poprawności obejmującej całą operację (takich jak próba skonfigurowania zasobu, który nie został jeszcze skonfigurowany) przed zapisaniem ich na sprzęcie. Może to uchronić Cię przed uszkodzeniem sprzętu przez bezsensowną konfigurację (w przypadku, gdy twój sprzęt jest na to podatny).źródło
Musisz tylko udokumentować poprawnie sposób użycia interfejsu i podać przykładowy samouczek.
Możesz również mieć wariant biblioteki debugowania, który sprawdza niektóre środowiska wykonawcze.
Może definiowania i prawidłowo dokumentowania pewnych konwencji nazewnictwa (np
preconfigure*
,startup*
,postconfigure*
,run*
....)BTW, wiele istniejących interfejsów ma podobny wzór (np. Zestawy narzędzi X11).
źródło
Jest to rzeczywiście powszechny i podstępny rodzaj błędu, ponieważ kompilatory mogą tylko wymuszać warunki składniowe, podczas gdy programy klienckie wymagają poprawności gramatycznej.
Niestety konwencje nazewnictwa są prawie całkowicie nieskuteczne w przypadku tego rodzaju błędów. Jeśli naprawdę chcesz zachęcić ludzi, aby nie robili niegramatycznych rzeczy, powinieneś przekazać obiekt polecenia pewnego rodzaju, który musi zostać zainicjowany wartościami warunków wstępnych, aby nie mogli wykonać kroków poza kolejnością.
źródło
Korzystając z tego wzorca masz pewność, że dowolny implementator wykona się w dokładnie takiej kolejności. Możesz pójść o krok dalej i stworzyć ExecutorFactory, który zbuduje Executory z niestandardowymi ścieżkami wykonania.
źródło
step1(); step2(); step3();
. Konstruktorem kroków jest udostępnienie interfejsu API, który ujawnia niektóre kroki, oraz wymuszenie kolejności wywoływania. Nie powinno to uniemożliwiać programistom wykonywania innych czynności między krokami.