Problem z zaplanowaniem programu wg. wzorca Strategia

0

Witam

W ramach ćwiczeń m.in. z klasą String, próbuję napisać program do wprowadzania korekt w plikach programów maszyny.

W Skrócie - Program Maszyny:

  1. maszyna przyjmuje pliki tekstowe jako programy wsadowe,
  2. programy tej maszyny używaja około 30 różnych rozkazów,
  3. rozkazy jeżeli chodzi o składnię róznią sie typem rozkazu (string) liczbą parametrów (int lub decimal) umieszczonych za rozkazem rozdzielonych spacjami
  4. w linii każdego rozkazu po średniku może być dowolny komentarz

przykladowo wyglada on tak:

 
BEGIN
; HOME 0
 OFF     40 ;osl. G
; WAITON  06 ;czek. osl. G
 WAITON  00 ;czek. zac. G
 nop   0.1
 MOVA     94.30  52.50  46.00 80;nad korek OK  z=45
 MOVA     94.30  51.30  46.70 30
 MOVA     95.00  49.00  46.50  50;w prawo i PRZOD
 MOVE    -40  00.00  00.00  00.50 100
 move   -25; 30  ;25
 MOVE   -140  0.70   0.00   0.00;
; on 45
 OFF     47 ;zaciski D
 WAITON  01 ;czek. D
 NOP     00.20
 MOVA     93.0  35.80  46.90  100;
 HOME     00
 OFF     55 ;licznik
 END.

Moje wymagania Dla programu:
Przypadek gdy maszyna się psuje, idzie do serwisu, wraca juz sprawna ale ma przesunięcie w osi X o 1,5mm (osie ruchów X, Y, Z - i w tych osiach takie przesuniecia sie zdarzaja). Rozkazy które korzystają z osi X, Y, Z to MOVE i MOVA. Chciałbym w całym programie wprowadzać offset w wybranej osi ruchów (czyli w tych rozkazach MOVE i MOVA, rozkaz MOVE jest "przeciazony" i moze miec różna ilosc parametrów).
poza tym chciałbym jako opcję dodac "Porządkowanie/Standaryzowanie kodu programu". tzn. wszystkie rozkazy pisane sa tylko wielkimi literami, parametry przyjmuja ten sam ujednolicony format, parametry rozkazów wystepują zawsze na tej samej pozycji w linii, komentarz jeżeli jest to zaczyna się zawsze na tej samej pozycji w linii, jeżeli linia zaczyna się od ";" to nic z nią nie robić ale mam mieć możliwość usunięcia takich martwych linii, dla powyzszego przykładu wygladałoby to mniej więcej tak:

 
 BEGIN
; HOME 0
 OFF         40                             ;osl. G
; WAITON  06                                            ;czek. osl. G
 WAITON      00                             ;czek. zac. G
 NOP      00.10
 MOVA     94.30  52.50  46.00  80           ;nad korek OK  z=45
 MOVA     94.30  51.30  46.70  30
 MOVA     95.00  49.00  46.50  50           ;w prawo i PRZOD
 MOVE    -40.00  00.00  00.00  00.50  100
 MOVE    -25.00                             ; 30  ;25
 MOVE   -140.00  00.70  00.00  00.00;
; on 45
 OFF         47                             ;zaciski D
 WAITON      01                             ;czek. D
 NOP      00.20
 MOVA     93.00  35.80  46.90  100;
 HOME        00
 OFF         55                             ;licznik
 END.

Wstępnie wymyśliłem taki rozkład klas dla tego programu:

Klasa całego programu:

 
class ProgramMaszyny
    {
        private List<LiniaProgramu> poszczegolneLinieProgramu;

        public string NazwaProgramu { get; private set; }  
        
        public ProgramMaszyny(string initNazwa)
        {
            poszczegolneLinieProgramu = new List<LiniaProgramu>();
            NazwaProgramu = initNazwa;
        }

        public int PobierzIloscLinii()
        {
            return poszczegolneLinieProgramu.Count;
        }

        public string PobierzLinieProgramu(int index)
        {
            return poszczegolneLinieProgramu[index].ToString();
        }

        public void DodajLinieProgramu(string initLinia)
        {
            poszczegolneLinieProgramu.Add(new LiniaProgramu(initLinia));
        }

Klasa jednej linii programu:

 
class LiniaProgramu
    {
        string czescRozkazowa;
        string czescKomentarzowa;
        
        public int PozycjaPoczatkuKomentarza { get; private set; }
        //public int PozycjaPoczatkuRozkazu { get; private set; }        

        public LiniaProgramu(string init)
        {
            this.PozycjaPoczatkuKomentarza = init.IndexOf(";");

            if (this.PozycjaPoczatkuKomentarza > 0)
            {
                czescRozkazowa = init.Remove(this.PozycjaPoczatkuKomentarza);
                czescKomentarzowa = init.Remove(0, this.PozycjaPoczatkuKomentarza);
            }
            else
            {
                czescRozkazowa = init;
                czescKomentarzowa = null;
            }
        }

        public override string ToString()
        {
            return czescRozkazowa + czescKomentarzowa + "\n";
        }        
    }

Wczytuję go tak:

        private void btnOtworz_Click(object sender, EventArgs e)
        {
            StreamReader reader = null;           
     
            if (openFileDialog1.ShowDialog() == DialogResult.OK)
            {
                try
                {
                    if (openFileDialog1.OpenFile() != null)
                    {
                        using (reader = new StreamReader(openFileDialog1.FileName))
                        {
                            progMaszyny = new ProgramMaszyny(openFileDialog1.SafeFileName);

                            while (!reader.EndOfStream)
                            {
                                progMaszyny .DodajLinieProgramu(reader.ReadLine());
                            }        
                            WyswietlProgram();                                                  
                        }
                    }
                }
                catch (Exception ex)
                {
                    MessageBox.Show("Problem z odczytaniem pliku.\nSzczegóły błędu:\n\n" 
                        + ex.Message);
                    progMaszyny  = null;
                }
            }
        }

        private void WyswietlProgram()
        {
            richTextBoxPodgladProgramu.Clear();
            labelNazwaOtwartegoProgramu.Text = "";

            for (int i = 0; i < progMaszyny .PobierzIloscLinii(); i++)
            {
                richTextBoxPodgladProgramu.AppendText(progMaszyny.PobierzLinieProgramu(i));
            }

            labelNazwaOtwartegoProgramu.Text = progMaszyny.NazwaProgramu; 
        } 

Wydaje mi się, że do rozwiązania swojego problemu mogę zastosować wzorzec Strategia, ale pewny nie jestem jak to zrobić i czy mogę bazowaćna tym co do tej pory zrobiłem.

Konkretnie w klasie LiniaProgramu, pola:
string czescRozkazowa;
string czescKomentarzowa;
chce zastąpić klasami o tych samych nazwach.

Moje pytania:
I tu zaczynam mieć problem z wyobrażeniem i zaplanowaniem sobie kolejnych warstw "obiektów w obiektach".
Chyba klasa **CzescRozkazowa **powinna być abstrakcyjna. Z niej dziedziczyłyby podklasy i tez nie wiem jakie (kazdy rozkaz jako klasa(30 podklas)?, grupa rozkazów ze względu na ilośc przyjmowanych parametrów (6 podklas)?)
Wg. Wzorca Strategia abstrakcyjna klasa CzescRozkazowa powinna zawierać pola InterfejsWprowadzeniaOffsetu i InterfejsUsystematyzowaniaSkladniRozkazu.
Te interfejsy byłyby implementowane przez klasy które byłyby "odpowiednikami?" klas dziedziczących z CzescRozkazowa i implementowałyby sposoby wprowadzania offstów albo porządkowania rozkazów?

Czy moglibyście mnie nakierować czy dobrze kombinuje z tym wzorcem Strategia, czy mam gdzies jakis błąd w rozumowaniu? I ogólnie czy ten podział na klasy Program, LiniaProgramu jest ok czy to jakoś inaczej trzeba zrobić?
Szczególnie interesuje mnie - zakładając że do tej pory dobrze to rozplanowałem - sposób w jaki zaplanować klasy CzescRozkazowa i CzescKomentarzowa.

Przyszłościowo przewiduję, że moge potrzebować rozszerzyc program o opcję zmian wartosci argumentów rozkazów ON i OFF (WAITON oraz WAITOFF i innych) z wartości jaką maja na nową w całym programie (oraz we wszystkich programach na maszynie).

Za wszelkie podpowiedzi będę wdzięczny.

0

Nie wiem co to jest „wzorzec strategia”, bo pisząc program staram się rozwiązać problem a nie „stosować wzorce”.
Zacząłbym od przedstawienia danych. Skoro typowa linijka kodu wygląda tak:

MOVA 94.30 52.50 46.00 80 ;nad korek OK z=45

potrzebna jest struktura o takich polach:
• rozkaz (najlepiej enum, albo ewentualnie string) - MOVA, BEGIN, OFF, HOME
• argumenty - tablica intów/floatów, albo kilka osobnych pól, zależnie jakie i ile jest możliwych kombinacji różnych typów argumentów
• komentarz - string. specjalny kod rozkazu (np. BRAK) będzie oznaczał że linijka składa się tylko z komentarza

• statyczna metoda .Parse(string) łyka stringa i zwraca zakodowaną linijkę z wypełnionymi powyższymi polami
• metoda .PoprawOsie(double x, double y, double z) poprawia argumenty o zadany ofset (albo nic nie robi jeżeli danego rozkazu to nie dotyczy)
• metoda .ToString() wypluwa sformatowany rozkaz w takim formacie w jakim chcesz

informacje o tym, który rozkaz przyjmuje ile argumentów i same stringi rozkazów ("MOVA", "BEGIN" itd.) trzymaj w jakichś tablicach, strukturach czy listach.

0

Dokładnie tak jak to opisałeś już przerobiłem, tyle tylko, że zamiast zastosowania struktury użyłem klasy z polem typu enum na rozkaz, ewentualnie w przypadku braku rozkazu wartosc enum BRAK. Liste<string> dla argumentów, w zalezności od wartosci pola rozkazu convertowałem na int lub decimal. i zwykłym stringiem dla komentarza. ToString() takiej klasy wypluwał tak jak mówisz połączone i uporządkowane stringi pól tej klasy.

Tyle tylko, że sporo było ifów, switchów itp. Dlatego zacząłem doczytywać i trafiłem na ten wzorzec. Skojarzyło mi się z moim problemem i o ile do wczoraj nie mogłem jakoś tego sobie poskładać w głowie to dzisiaj po dniu przerwy zaczynam to widziec i zaraz zacznę dłubać jak mi coś wyskoczy to bede się chciał pochwalić na pewno ;)

PS. Zrezygnowałem z tego rozwiązania które opisałem wcześniej i które mi sam opisałeś ponieważ: Nowe wymaganie było takie aby formatowanie/standaryzacja linii byłą opcją takżę, musiałbym np. wprowadzic tylko jeden offset na jednej osi ale nie przestawiać nic poza tym w programie ani nie zmieniac rozkazów z małych na wielkie litery (także .Split() z opcją StringSplitOptions.RemoveEmptyEntries tez odpadł). poza tym tak jak napisałem wczesniej moge tam zaraz musiec wprowadzic kolejne modyfikacje i nie chce się zakopać w ifach switchach i tym podobnych.

0

Wszystkie problemy o których piszesz (np. niemożliwość zmiany liter z małych na wielkie) nie istnieją i są niezależne od stosowania takiego czy innego wzorca.
To co ci opisałem (metody Parse i ToString) to wzorzec (mniejsza o jego nazwę) szeroko stosowany w wielu klasach .Net

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