10 травня 2011 р.

"Патерн Вівторка" #24: Проксі (Proxy)

Taken from here: http://news.malaysia.msn.com/photogallery.aspx?cp-documentid=4417270&page=7 Спробуйте себе уявити у процесі знешкодження бомби? Невже не лячно? Навіть якщо вам важко таке уявити або ви вважаєте що й так ніколи нічого подібного не будете робити, комусь всештаки приходиться знешкоджувавати бомби час від часу. Можу поручитися, що сапери, за винятком тих які хильнули чвертку, також відчувають страх перед вибухівкою. На наше щастя зараз є багато різних технологій, які дозволяються обійтися без присутності людити під-час знешкодження вибухового матеріалу. Більше того робот може проробити дещо більш складніші операції аніж людина, оскільки він точніший і більш потужніший (проте напевно неповороткий). Принаймні в робота не трясуться руки на відміну від сапера-напідпитку.
Комунікація із таким роботом звісно безпровідна, керування здійснюється за допомогою кінекту, або якогось костюму або принаймні супер джойстиків, робот також має панель управління на собі, яка б дозволила керувати ним безпосередньо на місці, на випадок якщо якийсь внучок Бен Ладена буде блокувати сигнал до робота.

ПРОКСІ


Проксі це дизайн патерн, що підміняє реальний об'єкт та надсилає запити до нього тоді коли це потрібно, проксі також може ініціалізувати реальний об'єкт, якщо той до того не існував.
Говорячи про приклад вище реальним об'єктом є робот (RobotBombDefuser) це купа важкого залізаччя яким ми можемо керувати за допомогою віддалених контролів (RobotBombDefuserProxy). Так як чудо-кінект установка передає сигнали на робот вона звісно знає як приєднатися до справжнього робота і передавати йому сигнали від ваших рухів. Насправді щось подібне відбувається коли ви приєднуєтеся до віддалених сервісів і викликаєте якісь методи на них.
Проксі також часто використовується коли потрібна лінива ініціалізація. В такому випадку реальний об'єкт не буде створений допоки не буде звертання до якихось методів. Всередині проксі методу добаляється перевірка чи справжній об'єкт не ініціалізований і якщо ні, то тоді ініціалізує його.
Давайте глянемо на наш приклад. Ось сам робот:

public class RobotBombDefuser
    {
        private Random _random = new Random();
        private int _robotConfiguredWavelength = 41;
        private bool _isConnected = false;

        public void ConnectWireless(int communicationWaveLength)
        {
            if(communicationWaveLength == _robotConfiguredWavelength)
            {
                _isConnected = IsConnectedImmitatingConnectivitiyIssues();
            }
        }
        public bool IsConnected()
        {
            _isConnected = IsConnectedImmitatingConnectivitiyIssues();
            return _isConnected;
        }
        private bool IsConnectedImmitatingConnectivitiyIssues()
        {
            return _random.Next(0, 10) < 4; // immitates 40% good connection, aka. very bad
        }

        public virtual void WalkStraightForward(int steps)
        {
            Console.WriteLine("Did {0} steps forward...", steps);
        }

        public virtual void TurnRight()
        {
            Console.WriteLine("Turned right...");
        }

        public virtual void TurnLeft()
        {
            Console.WriteLine("Turned left...");
        }

        public virtual void DefuseBomb()
        {
            Console.WriteLine("Cut red or green or blue wire...");
        }
    }

Основні методи, що роблять усю роботу є WalkStraightForward, TurnRight, TurnLeft, DefuseBomb. Є також методи, які з'єднуться безпровідно та перевідка чи є з'єднання (IsConnected) вони потрібні для більшої реальності цього прикладу. Можливо вони несуть додатковий шум, якщо так то можна їх упустити при читанні коду.

А ось реалізація самого Проксі. Проксі завжди має посилання на реальний об'єкт і часто наслідується від того ж класу яким є реальний об'єкт.
public class RobotBombDefuserProxy : RobotBombDefuser
    {
        private RobotBombDefuser _robotBombDefuser;
        private int _communicationWaveLength;
        private int _connectionAttempts = 3;

        public RobotBombDefuserProxy(int communicationWaveLength)
        {
            _robotBombDefuser = new RobotBombDefuser();
            _communicationWaveLength = communicationWaveLength;
        }

        public virtual void WalkStraightForward(int steps)
        {
            EnsureConnectedWithRobot();
            _robotBombDefuser.WalkStraightForward(steps);
        }

        public virtual void TurnRight()
        {
            EnsureConnectedWithRobot();
            _robotBombDefuser.TurnRight();
        }

        public virtual void TurnLeft()
        {
            EnsureConnectedWithRobot();
            _robotBombDefuser.TurnLeft();
        }

        public virtual void DefuseBomb()
        {
            EnsureConnectedWithRobot();
            _robotBombDefuser.DefuseBomb();
        }

        private void EnsureConnectedWithRobot()
        {
            if (_robotBombDefuser == null)
            {
                _robotBombDefuser = new RobotBombDefuser();
                _robotBombDefuser.ConnectWireless(_communicationWaveLength);
            }

            for (int i = 0; i < _connectionAttempts; i++)
            {
                if (_robotBombDefuser.IsConnected() != true)
                {
                    _robotBombDefuser.ConnectWireless(_communicationWaveLength);
                }
                else
                {
                    break;
                }
            }
            if(_robotBombDefuser.IsConnected() != true)
            {
                throw new BadConnectionException("No connection with remote bomb diffuser robot could be made after few attempts.");
            }
        }
    }

Таким чином наш проксі просто передає запити до справжнього об'єкту і завжди перед тим перевіряє чи з'єднання не пропало, і якщо пропало пробує тричі його відновити. Якщо і після цього не виходить, то викидується помилка.
Ось використання проксі:
public static void Run()
        {
            int opNum = 0;
            try
            {
                var proxy = new RobotBombDefuserProxy(41);
                proxy.WalkStraightForward(100);
                opNum++;
                proxy.TurnRight();
                opNum++;
                proxy.WalkStraightForward(5);
                opNum++;
                proxy.DefuseBomb();
                opNum++;

                Console.WriteLine();
            }
            catch (BadConnectionException e)
            {
                Console.WriteLine("Exception has been caught with message: ({0}). Decided to have human operate robot there.", e.Message);

                PlanB(opNum);
            }
        }

        private static void PlanB(int nextOperationNum)
        {
            RobotBombDefuser humanOperatingRobotDirectly = new RobotBombDefuser();

            if(nextOperationNum == 0)
            {
                humanOperatingRobotDirectly.WalkStraightForward(100);
                nextOperationNum++;
            }
            if (nextOperationNum == 1)
            {
                humanOperatingRobotDirectly.TurnRight();
                nextOperationNum++;
            }
            if (nextOperationNum == 2)
            {
                humanOperatingRobotDirectly.WalkStraightForward(5);
                nextOperationNum++;
            }
            if (nextOperationNum == 3)
            {
                humanOperatingRobotDirectly.DefuseBomb();
            }
        }
    }

В коді вище видно використання проксі для робота, а також "план Б", якщо неможливо приєднатися до робота. В такому випадку створється пряме посилання на робота і методи виконуться уже безпосередньо на ньому. Код може виглядати чуть складо із-за opNum та nextOperationNum, які були додані щоб зімітувати довиконання операції після обірвання зв'язку. Див. вивід нижче.

Вивід:
Did 100 steps forward...
Turned right...
Exception has been caught with message: (No connection with remote bomb diffuser robot could be made after few attempts.). Decided to have human operate robot there.
Did 5 steps forward...
Cut red or green or blue wire...


Моя імплементація дещо відрізняється від пропонованої у книзі "банди чотирьох" тим що проксі і справжній об'єкт не наслідуються від одного суперкласу або інтерфейсу. Але мені здається що така реалізація є більш частою тепер. Особливо у різних фреймворках. Наприклад ORM, або якийсь мокін фреймворк вимагають від вас мати віртуальні методи. Я думаю що тепер ви здогадуєтеся чому.
А ось UML-діаграма чудасії, яку ми закодили:
image
Моя табличка Патернів
Developer's RoadMap To Success

Немає коментарів:

Дописати коментар