Refaktoryzacja - zastąpienie instrukcji if/switch polimorfizmem - potrzebna pomoc

0

Cześć, przychodzę z prośbą o nakierowanie mnie na właściwe rozwiązanie.
(Nie jestem zawodowym programistą)
(PS2. Pisałem na forum C# i .NET ale tam nie bylo odzewu wiec ponawiam tutaj.)

Opis funkcjonalności programu który już jest i działa.
Program jest wykorzystywany w magazynie podczas pakowania wyrobów w pudełka jednostkowe. Praca polega na włożeniu wyrobu do pudelka, sczytaniu z wyrobu numeru seryjnego czytnikiem kodów kreskowych, program na podstawie odczytu czytnikiem drukuje etykietę z tym numerem i etykietę tą pracownik magazynu nakleja na zamknięte pudełko. To tak bardzo w skrócie. Dodatkowo zrobiłem to tak ze program jest obsługiwany tylko czytnikiem kodów kreskowych, praktycznie nie wymaga podchodzenia do klawiatury ani myszki. Czyli po uruchomieniu programu kursor jest na jednym TextBox'ie i zależnie jaki typ odczytu zostanie mu wczytany program zachowa się inaczej.
Typu "rozkazów":

  1. odczyt nr zlecenia - program pobiera z bazy danych artykuł tego zlecenia i otwiera okno dla kierownika gdzie weryfikowane są dane czy wszystko jest ok, jeżeli ok to kierownik zatwierdza i magazynier może używać programu do pakowania tego zlecenia (kierownik używa klawiatury w swoim oknie )
  2. odczyt nr seryjnego - program pobiera nr seryjny i drukowana jest etykieta na Zebrze z nr seryjnym, nazwa wyrobu i grafikami wcześniej pobranymi dla tego artykułu z odczytu nr zlecenia. Dodatkowo co odczyt jest pobierany czas wydruku i na podstawie tego rejestrowany jest czas pakowania każdego z wyrobów i łącznie całego zlecenia.
  3. odczyt "Raport" - program drukuje etykietę na której jest mały raport dla magazyniera do rozliczenia swojej pracy, co pakował, kiedy, ile zapakował i w jakim czasie łącznie z średnim czasem pakowania na sztukę.
  4. odczyt "Czyść" - czyści stan programu i przywraca do ustawień początkowych gdzie należy znów odczytać nr zlecenia żeby ustawić program do pakowania nowych wyrobów.

Obecnie dekoder typów odczytów/rozkazów jest na "if'ach" a sama logika interpretacji odczytu na jednym dużym switchu który w swoich case'ach ma kolejne switche - przykład na samym dole.

Chciałbym z odczytu/rozkazu zrobić obiekt który będzie implementował jakiś interfejs z np. metodą Wykonaj(). Wtedy łatwiej będę mógł dodawać nowe typy etykiet i nowe typy rozkazów i teraz pytanie:
Jak to ogarnąć?

  1. Wzorzec FabrykaAbstrakcyjna jest względnie zrozumiały dla mnie i chce iść w tym kierunku - czy to dobry kierunek? potrzebuje rady kogoś doświadczonego.
  2. Jak maja wyglądać te obiekty "Odczyt/Rozkaz" to chyba nie będą jakieś proste encje, wyobrażam sobie ze dla odczytów typu "Raport" będę musiał wstrzyknąć mój obiekt który odpowiada za rejestracje czasów i generowanie statystyk pakowania, dla rozkazu "Czyść" powinienem wstrzyknąć obiekt "PresenteraOknaGlownego" za pomocą którego wyczyszczę stan całej aplikacji i zresetuje widok aplikacji, za pomocą odczytu "nrZlecenia" będę musiał powstrzykiwac chyba repozytoria z danymi z baz danych a w obiekcie odczytu "nrSeryjny" chyba musze wstrzyknąć obiekt odpowiedzialny za wydruki etykiet (on już będzie musiał zapanować nad tym co to za etykieta i z jaka treścią - wiec tu chyba tez wzorzec fabryka abstrakcyjna?)

Program napisałem na bazie swojego starego programu sprzed prawie 10lat jeszcze z innej firmy w WinForms pod zdarzeniami. Teraz przerabiam go na architekturę MVP, względnie dobrze idzie - stopniowo. Spod zdarzeń przenoszę kod do wydzielonych obiektów, widok już całkowicie odciąłem od logiki. Teraz mam problem z tym kawalkiem architektury logiki - jakos obawiam sie ze te obiekty "odczyty" to będą kobyły czy to dobrze?

dekoder odczytow czytnika obecnie

    public class DekoderOdczytowCzytnika
    {
        string regexUID = @"100\d{7}\b";
        string regexNrZlecenia = @"501[0-9]{7}\b";

        public TypRozkazuEnum RozpoznajOdczytCzytnika(string odczyt)
        {
            TypRozkazuEnum wynik = TypRozkazuEnum.NiezdefiniowanyTekst;

            if (odczyt == "Raport")
                wynik = TypRozkazuEnum.Raport;
            else if (odczyt == "Czysc")
                wynik = TypRozkazuEnum.Czysc;
            else if (odczyt == "WalidatorProg")
                wynik = TypRozkazuEnum.WalidatorProg;
            else if (Regex.IsMatch(odczyt, regexUID))
                wynik = TypRozkazuEnum.OdczytUID;
            else if (Regex.IsMatch(odczyt, regexNrZlecenia))
                wynik = TypRozkazuEnum.OdczytNrZlecenia;
            else
                wynik = TypRozkazuEnum.NiezdefiniowanyTekst;

            return wynik;
        }
    }

i fragment obiektu odpowiedzialnego za interpretacje odczytow i wydruki - tez obecnie

    public class KontrolerDrukowania
    {
        private string nazwaDrukarki;
        private DekoderOdczytowCzytnika dekoderOdczytow;
        private RejestratorCzasu rejestratorCzasu;
        private PresenterOknaGlownego presenterOknaGlownego;

        public KontrolerDrukowania(PresenterOknaGlownego presenter)
        {
            this.presenterOknaGlownego = presenter;
            dekoderOdczytow = new DekoderOdczytowCzytnika();
            rejestratorCzasu = new RejestratorCzasu();
        }

//(...........)
public void DrukujEtykiete(TypEtykiety parametryWybranejEtykiety, 
                                    string txtB_NazwaKlienta, 
                                    string txtB_UID, 
                                    string txtB_WersjaProg)
        {
            if (txtB_NazwaKlienta != null
                && txtB_UID != null
                && txtB_NazwaKlienta != "Nazwa_Klienta"
                && txtB_UID != "UID"
                && txtB_WersjaProg != "Wersja_Programu")
            {
                IEtykietaDrukowalna etykietaDoWydruku = null;

                switch (dekoderOdczytow.RozpoznajOdczytCzytnika(txtB_UID))
                {
                    case TypRozkazuEnum.Raport:
                        switch (parametryWybranejEtykiety.RozmiarPapieruEtykiety)
                        {
                            case RozmiarPapieruEtykietyEnum.Rozmiar82x56:
                                etykietaDoWydruku = new EtykietaRaport_82x56(
                                                                txtB_NazwaKlienta,
                                                                rejestratorCzasu.ZwrocTekstem_CalkowityCzasPakowania(),
                                                                rejestratorCzasu.ZwrocTekstem_SredniCzasPakowania());
                                break;
                            case RozmiarPapieruEtykietyEnum.Rozmiar40x20:
                                Informator.PokazBlad(
                                    "Nie można obecnie drukowac raportu na małej etykiecie",
                                    "Za mała etykietka na raport");
                                break;
                            default:
                                Informator.PokazBlad(
                                    "Niezdefiniowany rozmiar papieru etkiety",
                                    "Blad wyboru etykiety");
                                break;
                        }
                        presenterOknaGlownego.WyczyscPoleCzytnikaUID();                     
                        break;

                    case TypRozkazuEnum.Czysc:
                        rejestratorCzasu.WyczyscRejestrOdczytow();
                        presenterOknaGlownego.ZresetujWidok();

                        Informator.PokazInformacje("Wyczyszczenie danych historii wydrukow i czasow pakowania", "Zerowanie aplikacji");
                        break;

                    case TypRozkazuEnum.OdczytUID:
                        switch (parametryWybranejEtykiety.RozmiarPapieruEtykiety)
                        {
                            case RozmiarPapieruEtykietyEnum.Rozmiar82x56:
                                etykietaDoWydruku = new EtykietaWydruk_82x56(
                                                                txtB_UID,
                                                                txtB_NazwaKlienta,
                                                                txtB_WersjaProg);
                                break;
                            case RozmiarPapieruEtykietyEnum.Rozmiar40x20:
                                etykietaDoWydruku = new EtykietaWydruk_40x20(
                                                                txtB_UID);
                                break;
                            default:
                                Informator.PokazBlad(
                                    "Niezdefiniowany rozmiar papieru etkiety",
                                    "Blad wyboru etykiety");
                                break;
                        }

                        rejestratorCzasu.DodajOdczyt(new CzasOdczytuUID(txtB_UID, DateTime.Now));
                        presenterOknaGlownego.WyczyscPoleCzytnikaUID();
                        presenterOknaGlownego.WyswietlPodsumowanie(rejestratorCzasu.ZwrocIloscZarejestrowanychOdczytow().ToString() + " [szt.]");
                        break;

                    default:
                        //TODO tutaj zamiast wydruku dodac wyswietlenie messageBox
                        etykietaDoWydruku = new EtykietaBledu();
                        presenterOknaGlownego.WyczyscPoleCzytnikaUID();        
                        break;
                }

                if (etykietaDoWydruku != null)
                {
                    //zewnetrzna biblioteka do drukarek Zebra
                    RawPrinterHelper.SendStringToPrinter(
                        NazwaPrzypisanejDrukarkiZebra,
                        etykietaDoWydruku.ZwrocKodEtykietyZPL());
                }
            }
            else
            {
                Informator.PokazBlad("Któreś z poniższych pól zostało niewłaściwie wypełnione:" + Environment.NewLine +
                    "- 'Nazwa Klienta'" + Environment.NewLine +
                    "- 'Wersja Programu'" + Environment.NewLine +
                    "- 'pole ID' ?", "Źle wypełnione pola");
            }
        }

2

Na twoim miejscu użyłbym tablicy akcji (dispatch table). Innymi słowy deklarujesz sobie słownik, w której kluczem jest regex określający komendę, a w wartości funkcję, która opisuje dane zdarzenie. Właściwie to bym zrobił tylko jeden regex parsujący linijkę (albo po prostu split) i wybierał akcję na podstawie stringa. Tak byłoby szybciej, bo w przypadku regexa musisz wszystkie po kolei sprawdzać, a przez stringa to jest O(1), czyli czas wykonania nie zależy od ilości opcji.

Przecież to są tylko akcje, które nie mają swojego stanu (chyba, że coś przeoczyłem), więc nie ma potrzeby tworzyć klas, a już w ogóle Fabryka Abstrakcyjna to przegięcie (pomijając to, że dla mnie to jest antywzorzec).

0
  1. czyli taki obiekt ze słownikiem [klucz odczytu , delegat do metody innych obektow] miałby nazwę np. KontrolerOdczytow i byłby tworzony w prezenterze okna głównego?

  2. co do tego czy aplikacja ma stany, z tym tez mam problem z interpretacja, bo z jednej strony jest konieczna kolejność uruchamiania aplikacji (uruchomienie -> odczyt zlecenia -> walidacja poprawności -> normalne pakowanie ->raport lub czyszczenie) z każdego z tych stanów może być pare dopuszczalnych wyjść ale tez zabronione np. z normalnego pakowania mogę wejść w raport i z raportu albo na czyszczenie albo na dalszy ciąg normlanego pakowania. Chyba ze źle identyfikuje stany aplikacji i to załatwię tylko tym słownikiem [klucz odczytu , delegat do akcji]

Ps. I jeszcze jedno mnie interesuje w tej apce mam dwa cele.

  1. Krytyczny. To zferaktoryzowac ją do zjadliwej postaci zgodnej z wzorcem MVP. I oddać do użytku w formie gdzie łatwo będę mógł dodawać nowe etykiety i nowe rozkazy.
  2. Niekrytyczny. Zferaktoryzowac to możliwie najwygodniej pod refaktoryzacja WPF MVVM, to działanie traktuje jako wprawkę.

PS3. Wydaje się że filmik z wczoraj na YouTube mówi to samo co Ty @elwis , podłubię w tą stronę i jakby co będę podpytywał o poprawność architektury dalej

1
Varran napisał(a):
  1. czyli taki obiekt ze słownikiem [klucz odczytu , delegat do metody innych obektow] miałby nazwę np. KontrolerOdczytow i byłby tworzony w prezenterze okna głównego?

Nie jestem pewny czy byłby to delegat, bo nie programuję w C#. Jeśli jest to to samo co referencja na funkcję to pewnie koniec końców by się do tego sprowadziło, ale na poziomie kodu, jeśli to możliwe, warto użyć wyrażeń lambda, bo wtedy powstaje nam ładny czytelny kod i nie trzeba szukać funkcji. Niestety nie wiem jak to zapisać w C# (o ile można, np. w Javie trzeba to robić bardzo na około), ale idealnie jeśli możesz napisać coś podobnego do tego:

my %actions = (
    dodaj => sub {
       #akcja dodaj…
    }, odejmij => sub {
       # akcja odejmij…
    } …
);

#… użycie.

my $line = readline(STDIN);
my @args = split (/\S+/, $line);
$actions{$args[0]}->();
  1. co do tego czy aplikacja ma stany, z tym tez mam problem z interpretacja, bo z jednej strony jest konieczna kolejność uruchamiania aplikacji (uruchomienie -> odczyt zlecenia -> walidacja poprawności -> normalne pakowanie ->raport lub czyszczenie) z każdego z tych stanów może być pare dopuszczalnych wyjść ale tez zabronione np. z normalnego pakowania mogę wejść w raport i z raportu albo na czyszczenie albo na dalszy ciąg normlanego pakowania. Chyba ze źle identyfikuje stany aplikacji i to załatwię tylko tym słownikiem [klucz odczytu , delegat do akcji]

To, że aplikacja ma stan to nie ulega wątpliwości, bo każda aplikacja okienkowa ma jakiś stan. Chodzi mi o to czy akcje — tak jak bloki case mają stan. Z tego co widzę, stan trzymasz w obiektach, które trzymasz sobie gdzieś nazewnątrz, więc same akcje nie muszą utrzymywać swojego lokalnego stanu. Na przykład w takim przypadku miałbyś akcję ze stanem:

   nastepny_id => sub {
        state $last = 0;
        return $last++;
   }

czyli mamy akcję, która zwróci następną liczbę.

PS3. Wydaje się że filmik z wczoraj na YouTube mówi to samo co Ty @elwis , podłubię w tą stronę i jakby co będę podpytywał o poprawność architektury dalej

To są jeszcze inne sposoby na uniknięcie if i else. Wydaje mi się, że wzorzec Strategy jest najbliższy temu, choć teoretycznie jest do czegoś innego.

5

Zacznijmy od tego: po co chcesz to refaktorować? Jaki problem z kodem chcesz rozwiązać?
Dopóki nie będziesz mieć twardego merytorycznego uzasadnienia, to się za to nie bierz.

"Słyszałem, ze switche są złe" to nie jest powód a jedynie jakaś tam opinia, nie mająca żadnej wartości bez kontekstu.

Co do samego kodu - kod jest trochę rozwlekły i należałoby go najpierw podzielić na mniejsze kawałki - bo teraz jest pomieszanie różnych poziomów abstrakcji w jednej metodzie. Ale co do zastępowania switcha polimorfizmem (zapewne wzorzec Command tu by najbardziej pasował) to już miałbym wątpliwości, bo to zależy od tego co chcesz osiągnąć i w którą stronę planujesz ten kod potem rozwijać.

0

Refaktoryzuje go żeby łatwiej moc później dodawać nowe etykiety i rozkazy sterujące programem.

1
Krolik napisał(a):

"Słyszałem, ze switche są złe" to nie jest powód a jedynie jakaś tam opinia, nie mająca żadnej wartości bez kontekstu.

@Varran przecież przyznał, że nie jest programuje zawodowo, Takie coś można powiedzieć seniorowi, być może mocnemu midowi, ale początkujący czy hobbysta musi się opierać na opiniach bardziej doświadczonych. :) Taka osoba przecież na ogół nawet nie wie, czym utrudnia sobie życie, mi w każdym razie zajęło kilka lat pracy zawowodowej, żeby to zacząć ogarniać.

Switch akurat jest zły z trzech względów. Po pierwsze, ze względu na breaki, które mogą się zgubić, po drugie, wszystkie przpadki stanowią jeden blok. Dodatkowo, duży switch powoduje, że nie są wydzielane funkcje, które powinny być wydzielone. Switch to jest relikt sprzed kilku dekad, nie mam pojęcia po co istnieje we współczesnych językach programowania, bo znacznie lepiej zrobić tablicę akcji lub użyć dziedziczenia. W tym akurat przypadku nie ma się nad czym rozowodzić.

Odnośnie refactoru to polecam bardzo dobrą heurystykę, której uczyli nas na pierwszym roku studiów: funkcje (czy metody, na jedno wychodzi) powinny być krótkie. Jeden ekran (~30 linii) to jest max, a najlepiej, jeśli mają nie więcej niż 10. Ta zasada się sprawdza, bo im jest dłuższa tym później trudniej dojść co właściwie robi, łatwiej też przeoczyć jakiś szczegół. Plus, funkcja ma swoją nazwę (lub klucz w przypadku tablicy akcji), która dodatkowo ją dokumentuje. W przypadku przytoczonego kawałka kodu, wydzielenie poszczególnych przypadków ze switcha, zdaje się, wystarczy.

2

Switch akurat jest zły z trzech względów. Po pierwsze, ze względu na breaki, które mogą się zgubić, po drugie, wszystkie przpadki stanowią jeden blok. Dodatkowo, duży switch powoduje, że nie są wydzielane funkcje, które powinny być wydzielone. Switch to jest relikt sprzed kilku dekad, nie mam pojęcia po co istnieje we współczesnych językach programowania, bo znacznie lepiej zrobić tablicę akcji lub użyć dziedziczenia. W tym akurat przypadku nie ma się nad czym rozowodzić.

Switch taki jak jest w Javie 17 czy w Kotlinie jest jak najbardziej OK w odpowiednich zastosowaniach, czyli mamy datę i robimy logikę która obsługuje osobno pon-piątek, sobotę i niedzielę, czy też robimy pattern matchich. Ważne żeby to był expression a nie statement i wymuszał obsługę wszystkich przypadków ( @jarekr000000 pewnie wie jak to się mądrze nazywa).

0

mam trochę problem z tym słownikiem poleceń, to jak to teraz próbuje, poniżej:

wątpliwości i pytania:

  1. ten obiekt dyspozytorPoleceń musiałby mieć referencje - ustawionych już w konstruktorze - do wszystkich obiektów w sobie żeby przełożyć do niego kod ze switchy (przeklejając kod ze switchy jeden do jednego) to dziwne
  2. wolałbym przekazywać referencje do obiektów w konkretnych metodach obsługi polecenia, ale wtedy jak przekazywać do tych metod parametry jeżeli wywołuje je przez Invoke()?
  3. przykład rozkazu "Raport" i "OdczytUID" - oba przygotowują etykiety do druku ale na podstawie parametru podanego w switchu "parametryWybranejEtykiety.RozmiarPapieruEtykiety" i wtedy tworzona jest właściwa etykieta, czy tu ma być taki słownik poleceń zagnieżdżony? tak jak switche są zagnieżdżone?
    public class DyspozytorPolecen
    {
        private Dictionary<TypRozkazuEnum, Action> slownikPolecen;

        private DekoderOdczytowCzytnika dekoderOdczytow;
        private RejestratorCzasu rejestratorCzasu;
        private PresenterOknaGlownego presenterOknaGlownego;

        public DyspozytorPolecen(PresenterOknaGlownego presenter)
        {
            this.presenterOknaGlownego = presenter;
            dekoderOdczytow = new DekoderOdczytowCzytnika();
            rejestratorCzasu = new RejestratorCzasu();

            slownikPolecen.Add(TypRozkazuEnum.Raport, ObsluzRaport);
            slownikPolecen.Add(TypRozkazuEnum.Czysc, ObsluzCzyszczenie);
            slownikPolecen.Add(TypRozkazuEnum.OdczytUID, ObsluzOdczytUID);
            slownikPolecen.Add(TypRozkazuEnum.OdczytNrZlecenia, ObsluzOdczytNrZlecenia);
            slownikPolecen.Add(TypRozkazuEnum.WalidatorProg, ObsluzWalidatoraProgramu);
        }

        public void WykonajPolecenieDla(TypRozkazuEnum rozkazEnum)
        {
            slownikPolecen[rozkazEnum]?.Invoke();
        }

        private void ObsluzRaport() { }
        private void ObsluzCzyszczenie() 
        {
            rejestratorCzasu.WyczyscRejestrOdczytow();
            presenterOknaGlownego.ZresetujWidok();
            Informator.PokazInformacje("Wyczyszczenie danych historii wydrukow i czasow pakowania", "Zerowanie aplikacji");
        }
        private void ObsluzOdczytUID() { }
        private void ObsluzOdczytNrZlecenia() { }
        private void ObsluzWalidatoraProgramu() { }
    }
3
elwis napisał(a):

@Varran przecież przyznał, że nie jest programuje zawodowo, Takie coś można powiedzieć seniorowi, być może mocnemu midowi, ale początkujący czy hobbysta musi się opierać na opiniach bardziej doświadczonych. :) Taka osoba przecież na ogół nawet nie wie, czym utrudnia sobie życie, mi w każdym razie zajęło kilka lat pracy zawowodowej, żeby to zacząć ogarniać.

Przecież @Krolik bardzo ważną rzecz napisał, o podzieleniu kodu na mniejsze kawałki i od tego początkujący powinien zacząć

0

drugie co wymyśliłem to takie cos, ale tez mam wrażenie że to jakieś dziwne ładowanie wszystkiego do obiektów obsługi:

taka tablica przypisan ale nie oakcji tylko obiektow ktore reprezentuja jakis dzialania;

    public class DyspozytorPolecen
    {
        private Dictionary<TypRozkazuEnum, IPolecenie> slownikObiektowPolecen;

        public IEtykietaDrukowalna WykonajPolecenieDla(TypRozkazuEnum rozkazEnum, string odczyt)
        {
            return slownikObiektowPolecen[rozkazEnum]?.WykonajDzialanie(odczyt);
        }

        public void ZdefiniujObsluge(TypRozkazuEnum rozkaz, IPolecenie obiektPolecenia)
        {
            slownikObiektowPolecen.Add(rozkaz, obiektPolecenia);
        }
    }

interfejsy po których będę wykonywa akcje lub zwracał właściwa etykietę.

public interface  IPolecenie
    {
        IEtykietaDrukowalna WykonajDzialanie(string odczyt);
    }

i przykładowy obiekt obsługi jednego typu polecenia:

    public abstract class ObslugaBase
    {
        protected IEtykietaDrukowalna etykietaDoWydruku = null;
        protected TypEtykiety parametryEtykiety 
        { 
            get { return presenterOknaGlownego.TypWybranejEtykiety; }
            set { presenterOknaGlownego.TypWybranejEtykiety = value; }
        }
        protected DaneArtykuluProgramowalnego pakowanyArtykul 
        { 
            get { return presenterOknaGlownego.wybranyDoPakowaniaArtykul; }
            set { presenterOknaGlownego.wybranyDoPakowaniaArtykul = value; }
        }

        protected RejestratorCzasu rejestratorCzasu;
        protected PresenterOknaGlownego presenterOknaGlownego;

        public ObslugaBase(PresenterOknaGlownego presenter, RejestratorCzasu rejestrator)
        {
            rejestratorCzasu = rejestrator;
            presenterOknaGlownego = presenter;
        }
    }

public class ObslugaWydrukuRaportu : ObslugaBase, IPolecenie
    { 
        public ObslugaWydrukuRaportu(PresenterOknaGlownego presenter, RejestratorCzasu rejestrator)
            : base(presenter, rejestrator) { }

        public IEtykietaDrukowalna WykonajDzialanie(string odczyt)
        {
            switch (parametryEtykiety.RozmiarPapieruEtykiety)
            {
                case RozmiarPapieruEtykietyEnum.Rozmiar82x56:
                    etykietaDoWydruku = new EtykietaRaport_82x56(
                                                    pakowanyArtykul.NazwaKlienta,
                                                    rejestratorCzasu.ZwrocTekstem_CalkowityCzasPakowania(),
                                                    rejestratorCzasu.ZwrocTekstem_SredniCzasPakowania());
                    break;
                case RozmiarPapieruEtykietyEnum.Rozmiar40x20:
                    Informator.PokazBlad(
                        "Nie można obecnie drukowac raportu na małej etykiecie",
                        "Za mała etykietka na raport");
                    break;
                default:
                    Informator.PokazBlad(
                        "Niezdefiniowany rozmiar papieru etkiety",
                        "Blad wyboru etykiety");
                    break;
            }
            presenterOknaGlownego.UstawPoleCzytnikaUID(String.Empty);

            return etykietaDoWydruku;
        }
    }

Ustawianie obiektu z przypasowaniami typów rozkazów do konkretnych obiektów poleceń

        public EgzekutorPolecen(PresenterOknaGlownego presenter)
        {
            this.presenterOknaGlownego = presenter;
            weryfikatorZebry = new WeryfikatorDrukarkiZebra();

            dekoderOdczytow = new DekoderOdczytowCzytnika();
            rejestratorCzasu = new RejestratorCzasu();

            dyspozytor = new ZarzadcaPolecen();
            dyspozytor.ZdefiniujObsluge(TypRozkazuEnum.Raport, new ObslugaWydrukuRaportu(presenterOknaGlownego, rejestratorCzasu));
            dyspozytor.ZdefiniujObsluge(TypRozkazuEnum.Czysc, new ObslugaCzyszczenia(presenterOknaGlownego, rejestratorCzasu));
            dyspozytor.ZdefiniujObsluge(TypRozkazuEnum.OdczytUID, new ObslugaDrukowaniaEtykietyUID(presenterOknaGlownego, rejestratorCzasu));
        }

.

Nowa metoda zamiast switch'ów byłaby taka:

public void DrukujEtykiete(DaneArtykuluProgramowalnego artykul, string odczytWartosc)
        {
            TypRozkazuEnum rozkaz = dekoderOdczytow.RozpoznajOdczytCzytnika(odczytWartosc);
            IEtykietaDrukowalna etykietaDoWydruku = dyspozytor.WykonajPolecenieDla(rozkaz, odczytWartosc);

            if (etykietaDoWydruku != null)
            {
                Informator.PokazInformacje(
                "Poszedl Wydruk: " + Environment.NewLine +
                etykietaDoWydruku.ZwrocKodEtykietyZPL(),
                "Info o wydruku etykiety");
                //RawPrinterHelper.SendStringToPrinter(
                //    NazwaWybranejZebry,
                //    etykietaDoWydruku.ZwrocKodEtykietyZPL());
            }
        }

zakręciłem się w tym? czy to dobry kierunek?

0

Rozpędziłem się z tymi obiektami implementującymi interfejs "IPolecenie" a teraz coraz więcej poleceń mi dochodzi, które nie powinny zwracać żadnej etykiety. Są to polecenia zmieniające stan aplikacji. Przykład resetowania stanu aplikacji poniżej:

Jak poprawić ten kod aby móc wykonywać dwa rodzaje poleceń: jedno zwracające wydruk i drugie tylko systemowe zwracające void. Czy musze robić to dwoma "EgzekutoramiPolecen"?

 public IEtykietaDrukowalna WykonajDzialanie(string odczyt)
        {
            rejestratorCzasu.WyczyscRejestrOdczytow();
            presenterOknaGlownego.UstawWidokStartowyAplikacji();
            parametryEtykiety = new TypEtykiety();
            pakowanyArtykul = new DaneArtykuluProgramowalnego();
           
            Informator.PokazInformacje("Wyczyszczenie danych historii wydrukow i czasow pakowania", "Zerowanie aplikacji");

            return null;
        }

1 użytkowników online, w tym zalogowanych: 0, gości: 1