Keri sisuni

Singleton disainimuster

Esimene disainimuster, mida vaatame, on singleton. Singleton on disainimuster, mille eesmärgiks on lubada klassist ainult ühe objekti (ehk instantsi) loomist. Ainult ühe instantsi lubamine on vajalik juhtudel kui klassist mitme instantsi loomine tuleb keelata.

Probleem

Oletame, et meil on klass, mis kontrollib mõnda välist seadet. Olgu selleks näiteks mõni tootmisliini osa. Seadmele on määratud kindel töötsükkel. Olukord, kus kaks objekti meie koodis seadmele samaaegselt käske saadaks, on keelatud. Käske tohib seadmele saata ainult üks objekt ja meie ülesandeks on garanteerida, et seadet kontrolliva klassi põhjal saab teha ainult ühe objekti. Selle probleemi aitab meil lahendada singleton disainimuster.

Oletame, et meil on seadme kontrollimiseks klass CutterController, mis kontrollib lõiketera tööd.

public class CutterController
{
    public void StartEngine() { }   // käivita mootor
    public void StopEngine() { }    // peata mootor
    public void RaiseBlade() { }    // tõsta lõiketera üles
    public void PushMaterial() { }  // lükka materjal ette
    public void BladeDown() { }     // langeta lõiketera (lõika)
}

Praegu ei takista meil miski kirjutamast järgnevat koodi:

var controller1 = new CutterController();
var controller2 = new CutterController();

Me võime neid muutujaid kasutada selliselt, et kord saadab seadmele käsu üks ja kord teine objekt. Halvemal juhul võib selle segaduse tagajärjel liinioperaator tõsiselt viga saada. Probleemi vältimiseks kirjutame CutterController-klassi ümber selliselt, et see kasutab Singleton disainimustrit ning mitme instantsi loomine pole võimalik.

Singletoni rakendamine

Esimene samm Singletoni rakendamisel on klassile privaatse konstruktori loomine.

public class CutterController
{
    public void StartEngine() { }
    public void StopEngine() { }
    public void RaiseBlade() { }
    public void PushMaterial() { }
    public void BladeDown() { }

    private CutterController()
    {
    }
}

Kui privaatne konstruktor on klassi ainus konstruktor, siis klassist uut objekti väljapool seda klassi ennast luua ei saa.

Järgnev pole enam võimalik:

var controller = new CutterController();

Järgmiseks loome klassile staatilise muutuja, mis on temaga samat tüüpi ja lisame klassile staatilise omaduse, mis seda muutujat tagastab. Enne tagastamist kontrollime, kas muutujal on väärtus. Kui väärtust pole siis väärtustame muutuja ära omistades sellele uue instantsi CutterController klassist.

public class CutterController
{
    public void StartEngine() { }
    public void StopEngine() { }
    public void RaiseBlade() { }
    public void PushMaterial() { }
    public void BladeDown() { }

    private CutterController()
    {
    }

    private static CutterController _instance;
    public static CutterController Instance
    {
        get
        {
            if(_instance == null)
            {
                _instance = new CutterController();
            }

            return _instance;
        }
    }
}

Nüüd on meil CutterController-klassist garanteeritult ainult üks instants. See asub klassi staatilises skoobis ning staatilises skoobis on klassil kõiki liikmeid ainult üks - need on jagatud klassi instantside vahel.

Singletoni kasutamine

CutterController-klassi võiksime kasutada nagu alltoodud näites ja proovimiseks mõeldud koodis.

var controller = CutterController.Instance;

controller.StartEngine();  // Käivita seade
controller.RaiseBlade();   // Lõiketera peab olema üleval

while(!stopMachine)
{
    controller.PushMaterial(); // Lükka materjal ette
    controller.BladeDown();    // Langeta lõiketera ja lõika
    controller.RaiseBlade();   // Tõsta lõiketera üles
}

controller.StopEngine();
using System;
using System.Threading;
using System.Threading.Tasks;

namespace OOP
{
    public class CutterController
    {
        // Kontrollimismeetodid väljastavad lihtsalt teate
        public void StartEngine() { Report("Engine started"); } 
        public void StopEngine() { Report("Engine stopped"); }
        public void RaiseBlade() { Report("Blade is up"); }
        public void PushMaterial() { Report("Material is ready"); }
        public void BladeDown() { Report("Blade is down"); }

        private void Report (string message)
        {
            Console.WriteLine(message);
        }

        private CutterController()
        {
        }

        private static CutterController _instance;
        public static CutterController Instance
        {
            get
            {
                if (_instance == null)
                {
                    _instance = new CutterController();
                }

                return _instance;
            }
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            // Kas masinal on käsk peatuda
            var stop = false;

            // Käivitame masina töötsükli eraldi lõimes
            var task = Task.Run(() =>
            {
                var controller = CutterController.Instance;

                controller.StartEngine();
                controller.RaiseBlade();

                while(!stop) // Töötsükkel kordub kuni stop on true
                {
                    controller.PushMaterial();
                    Thread.Sleep(500);
                    controller.BladeDown();
                    Thread.Sleep(500);
                    controller.RaiseBlade();
                    Thread.Sleep(1000);
                    Console.WriteLine(" ");
                }

                controller.StopEngine();
            });

            // Ootame konsoolil enteri vajutamist
            Console.ReadLine();

            // Määrame stop väärtuseks true ja ootame kuni tegevus lõimes lõpeb
            stop = true;
            task.Wait();
        }
    }
}

Viited