Kihagyás

Állapotgép

Az állapotgép az objektum működésének szemléltetésére, illetve a tervezés segítésére van.

Megvalósítása külön nem szükséges, érvényesülni a metódusok hívásain keresztül fog történni!

Objektum életciklusa

Életciklus lehetséges jelenségei

Az objektum

  • Létrejön
    • Példányosodik (Konstruktor)
  • Változik
    • Metódusait hívják, adattagjait megváltoztatják
  • Megszűnik
    • Megsemmisül (Destruktor)

Objektum különböző állapotai (state)

  • Fizikai állapot
    • Az objektum adatai által felvett értékek együttese
  • Logikai állapot
    • Valamilyen szempont szerint közös tulajdonságú fizikai állapotok összessége(halmaza)

Az objektum állapota valamilyen esemény hatására változhat meg

Esemény (event)

  • Üzenet (trigger)
  • Tevékenység befejezője
  • Őrfeltétel (guard) teljesülése
    • Logikai állítás
      • When
    • Időhöz kötött várakozás
      • After

Állapot-átmenet gráf

Az objektum életciklusát, logikai állapotának változásait, azokra épülő működését lehet állapot-átmenet gráffal ábrázolni, ez az objektum állapotgépe.

Leírása egy olyan irányított gráffal lehetséges, ahol a csúcsok a logikai állapotok, az élek az állapot-átmenetek, s mind az állapothoz, mind az átmenetekhez tevékenységek tartoznak.

ATM állapotgépe

Az új aktív állapot egyértelműen választódik ki a régiből kivezető élek által mutatott állapotok közül.

Állapotok

  • Jelzése a kerekített sarkú téglalap, ami kaphat nevet, de maradhat anonim is
  • Az állapot neve mellett []-közé írható az objektum fizikai állapotára tett logikai állítás
    • Egy állapotgép állapotainak feltételei teljesen diszjunkt rendszert kell, hogy alkossanak
  • Az állapottot jelző téglalapvan felsorolhatók az állapothoz rendelt különvéle tevékenységek

Állapotok tevékenységei

<állapot>
enter / <belépési tevékenység>
do / <belső tevékenység>
exit / <kilépési tevékenység>
<üzenet>(<paraméterek>)/<tevékenység>

Ezek a folyamatok az aktív állapotban hajtódnak végre.

Fontos megjegyezni, hogy kizárólag egy aktív állapot lehet!

Belépési tevékenység

Az állapot aktivációjával hajtódik végre (és mindíg be is fejeződik)

Belső tevékenység

Lehet végtelenített, vagy befejeződő, amely legfeljebb addig tart, amíg az állapot aktív

Kilépési tevékenység

Akkor hajtódik végre(és mindíg be is fejeződik), amikor az állapot passzívvá válik

Triggerelt tevékenységek

Olyan üzenet váltja ki, amely során az állapot nem változik, így sem a ki-, sem a belépési feltétel nem hajtódik végre

Szokás még belső átmeneti tevékenységnek is nevezni

Tevékenységek sorrendje

Állapotváltásnál a sorrend rendre

  • Az aktív állapot kilépési tevékenysége
  • Az átmenethez tartozó tevékenység (ha van ilyen)
  • Az új állapot belépési tevékenysége
  • Az új állapot belső tevékenysége

Az átmenet lehet reflexív is, tehát visszavezethet önmagába

Pl, az állapot inaktívvá válik, amíg a másik állapotban a belső tevékenység lefut, majd visszatér az előző állapotba.

Hierarhikus állapotok

Ilyenkor az állapot már annyira összetett, hogy azt egy saját állapotgéppel reprezentálunk

Jelölni kell a be- és kilépési pontokat, a kezdő és befejező állapotot is

Hierarchikus állapotok

Pszeudo állapotok

Pszeudo állapotok

A kezdő állapotból csak egy átmenet indulhat, ez szimbolizálja az objektum létrejöttét

A végállapot jelöli az objektum megszűnését

Állapot átmenet

Állapot átmenet

  • Egy állapotok közti átmenet mindíg egy esemény hatására következik be
    • Ha az esemény egy üzenet(szignál, vagy metódus hívás), akkor nevét az átmenet nyilára tesszük
  • Az üzenetnek lehetnek paramérerei, kapcsolódhatnak hozzá őrfeltétel is
    • Ha az őrfeltétel logikai, akkor az a paraméterektől és az objektum adattagjaitól függ
  • Az átmenethez rendelt tevékenység az objektum adattagjaival és az esetleges kiváltó üzenet paramétereivel operáló program

Állapot átmenet tábla

A diagram működése táblázattal is leírható (jelentésük ekvivalens)

\start jelöli a kezdő állapotot

Állapot átmenet tábla

Üzenet vezérelt állapot-átmenetek

Átmenet üzenettel

Akkor következik be, amikor az átmenetet egy üzenet váltja ki.

Bekövetkezése:

  • Őrfeltétel nélkül azonnal
    • Ha volt eddig belső tevékenység, az megszakad
    • Ugyanabból az állapotból egy üzenethez csak egy őrfeltétel nélküli él vezethet ki!
  • Őrfeltétel esetén csak annak teljesülésével
    • Több él csak akkor lehetséges, ha diszjunktak
      • (Nem következhet be mind a kettő egyszerre, hiszen csak egy aktív állapota lehet az objektumnak)

Példa

Üzenet nélüli állapot-átmenetek

Átmenet üzenet nélkül

A belső tevékenységek befejeződésekor következik be, nem üzenet váltja ki

Bekövetkezése

  • Őrfeltétel nélkül azonnal
    • Ebből csak egy lehet
  • Őrfeltételnél az átmenet a teljesülésre vár
    • A várakozás elévül, ha másik őrfeltétel teljesül
      • Ekkor a másik átmenet bekövetkezik
    • Az állapotból kivezető üzenetek nélküli átmenetek diszjunktak
      • (Továbbra is azért, hogy minden időben egy aktív állapot létezzen)

Feladat - Korlátos sor

Korlátos sor - Elképzelés

FIFO elvű homogén tároló meghatározott maximális elemszámmal

  • Enqueue() és Dequeue() metódusokkal rendelkezik
  • Rögzített n méretű tömbbel lehet megvalósítani
    • in és out pozíciók, valamint az aktuális elmeszám külön tárolva van

Elképzelés

Korlátos sor - Állapotgép

Állapotgép

Korlátos sor - Osztály diagram

Osztály diagram

Feladat - Garázskapu vezérlés

Leírás

  • A garázskaput egy távirányító vezérli
    • Parancsai:
      • Nyílj!
      • Záródj!
      • Állj meg!
  • A motor a legutoljára kiadott parancs szerint mozhatja a kaput
  • A kapu automatikusan megáll, ha:
    • Teljesen kinyílt
    • Teljesen becsukódott
    • A szenzor érzékel valamit az útban

Tervezési szint diagramjai

Használati eset

Használati eset

Kommunikációs diagram

Kommunikációs

Szekvencia diagram

Szekvencia

Osztály diagram

Osztály

Állapotgépek

  • Az objektumok egy része (motor, szenzor) aszinkron dolgozza fel a nekik küldött szignálokat, mások (kapu) szinkron metódus-hívásokat fogad.
    • a motor, a vezérlőtől és a szenzortól kapott szignálokat a beérkezésük sorrendjében, a saját működésével aszinkron módon dolgozza fel egy külön szálon megvalósított állapotgéppel
    • a szenzor külön szálon futó állapotgépe figyel az akadályokra, ha az állapota aktív, amelyet a motor állít be.
    • a kaput szinkron hívásokkal vezérli a motor; ezért nem készítünk hozzá külön szálon futó állapotgépet.
    • a vezérlő egység csak a felhasználó jelzéseit továbbítja a motor és a szenzor felé; nincsenek megkülönböztethető állapotai.

Szenzor állapotgépe

Szenzor állapotgépe

Motor állapotgépe

Motor állapotgépe

Motor állapotgépe

Aszinkron üzenetekkel vezérelt állapotgép megvalósítása

  • Az aszinkron érkező üzenetet a küldő száltól eltérő szálon kell futtatni
  • Az objektum a hozzá több irányból érkező szignálokat egy eseménysorban gyűjti.
    • A küldő objektumok a fogadó objektum Küld() metódusának hívásával tehetnek bele szignált a fogadó eseménysorába annak Sorba() metódusával. Ezek a lépések a küldő objektum szálán hajtódnak végre.
    • A fogadó objektum saját szálán futó állapotgépe vesz ki egy szignált az eseménysorból annak Sorból() metódusával.
    • A Sorba() és Sorból() metódusokat szinkronizálni kell: ezek kölcsönösen kizárásos módon használhatják a sort, továbbá üres sor esetén a Vesz() műveletet várakozó utasítással blokkolni kell.

Megvalósítási szint diagramjai

Megvalósítási szint - Kommunikációs diagram

Kommunikációs

Megvalósítási szint - Osztály diagram

Osztály

Garázs megvalósítása kód

Rendszer osztálya

class Program
{
    static void Main( )
    {
        Garagegate system = new ();
        system.Process();
    }
}
class Garagegate
{
    public readonly Engine engine;
    public readonly Sensor sensor;
    public readonly Gate gate;
    private readonly Controller controller;
    public Garagegate()
    {
        controller = new Controller(this);
        gate = new Gate(this, 5);
        sensor = new Sensor(this);
        engine = new Engine(this);
    }
    public void Process()
    {
        controller.Control();
    }
}

Vezérlő osztály

public enum Signal
{
    up, down, stop,
    unrolled, coiled,
    activate, deactivate,
    blockage, none, final
};
class Controller
{
    private readonly Garagegate sys;
    public Controller(Garagegate s) { sys = s; }
    public void Control() { 
        MenuWrite();
        int v;
        do
        {
            v = int.Parse(Console.ReadLine());
            switch (v)
            {
                case 0: system.sensor.Send(Signal.final);
                        system.engine.Send(Signal.final); break;
                case 1: system.engine.Send(Signal.up); break;
                case 2: system.engine.Send(Signal.down); break;
                case 3: system.engine.Send(Signal.stop); break;
            }
        } while (v != 0);
    }
    private void MenuWrite()
    {
        Console.WriteLine("Menupoints:");
        Console.WriteLine("0 - exit");
        Console.WriteLine("1 - up");
        Console.WriteLine("2 - down");
        Console.WriteLine("3 - stop");
    }
}

Kapu osztály

class Gate
{
    public readonly Garagegate system;
    private readonly int maxLength;
    public int CurrentLength { get; private set; }
    public Gate(Garagegate s, int m)
    {
        system = s; maxLength = m; CurrentLength = 0;
    }
    public void Up()
    {
        if (currentLength > 0) --CurrentLength;
        if (0 == CurrentLength) system.engine.Send(Signal.coiled);
    }
    public void Down()
    {
        if (CurrentLength < maxLength) ++CurrentLength;
        if (maxLength == CurrentLength) system.engine.Send(Signal.unrolled);
    }
}

Szálbiztos eseménysor

Ahhoz, hogy az Enqueue() és a Dequeue() ne okozzon hibát a külön szálon futásukkal, szinkronizálni kell őket

Fontos, hogy a Kölcsönös kizárás elve alapján működjenek.

class MyQueue <Signal>
{
    private readonly Queue<Signal> queue = new ();
    private readonly object criticalSection = new ();
    private readonly Signal noneSignal;
    public MyQueue(Signal none) { noneSignal = none; }
    public bool Empty() { return queue.Count == 0; }
    public void Enqueue(Item e)
    {
        Monitor.Enter(criticalSection);
        queue.Enqueue(e);
        Monitor.Exit(criticalSection);
    }
    public Signal Dequeue()
    {
        Signal e;
        Monitor.Enter(criticalSection);
        if (!Empty())
        {
            e = queue.Peek();
            queue.Dequeue();
        }
        else e = noneSignal;
        Monitor.Exit(criticalSection);
        return e;
    }
}

Állapotgép osztály

public abstract class StateMachine<Signal>
{
    private readonly Thread thread;
    private readonly MyQueue<Signal> eventQueue;
    private readonly Signal finalSignal;
    public StateMachine(Signal none, Signal final)
    {
        finalSignal = final;
        eventQueue = new MyQueue<Signal>(none);
        thread = new Thread(new ThreadStart(StateMachineProcess));
        thread.Start();
    }
    public void Send(Signal signal) { eventQueue.Enqueue(signal); }
    private bool inprocess;
    private void StateMachineProcess()
    {
        inprocess = true;
        while (inprocess)
        {
            Signal signal = eventQueue.Dequeue();
            if (signal.Equals(finalSignal)) inprocess = false;
            else Transition(signal);
        }
    }
    protected abstract void Transition(Signal signal);
}

Szenzor osztály

enum SensorState { active, passive };
private SensorState currentState;

Ez az implementáció javítható SensorState ősosztállyal, vagy Singleton-nal

class Sensor : StateMachine<Signal>
{
    public readonly Garagegate system;
    private SensorState currentState;
    public string CurrentState { get { return currentState.ToString(); } }
    public Sensor(Garagegate sys) : base(Signal.none, Signal.final)
    {
        system = sys;
        currentState = SensorState.passive;
    }
    protected override void Transition(Signal signal) {  }
    protected override void Transition(Signal signal)
    {
        switch (currentState)
        {
            case SensorState.passive:
                switch (signal)
                {
                    case Signal.activate: currentState = SensorState.active; break;
                    case Signal.deactivate: case Signal.none: break;
                    default: throw new UnknownSignalException();
                }
                break;
            case SensorState.active:
                switch (signal)
                {
                    case Signal.activate: break;
                    case Signal.deactivate: currentState = SensorState.passive; break;
                    case Signal.none: Observe(); break;
                    default: throw new UnknownSignalException();
                }
                break;
        }
    }

    private readonly Random rand = new();
    private void Observe()
    {
        if (rand.Next() % 100 < 1)
        {
            system.engine.Send(Signal.blockage);
        }
    }
}

Motor osztály

class Engine : StateMachine<Signal>
{
    public readonly Garagegate system;
    private EngineState currentState;
    public string CurrentState { get { return currentState.ToString(); } }
    public Engine(Garagegate sys) : base(Signal.none, Signal.final)
    {
        system = sys;
        currentState =  ;
    }
    protected override void Transition(Signal signal) {  }
}