Паттерн проектирования «Адаптер» / «Adapter»

    Почитать описание других паттернов.

    Пожалуй, начнем.
    Для начала, поясню несколько организационных вопросов.
    • Описание того или иного паттерна, является моей сугубо личной интерпретацией теоретического и практического материала, собранного из книг и интернет-статей;
    • При построении UML-диаграмм, я буду использовать свободный редактор от компании astah, ввиду его простоты и независимости от конкретного языка или среды. При этом, диаграммы не будут отличатся изобилием картинок и цветов, но будут ясно отображать суть паттерна;
    • При реализации практических примеров, язык программирования будет выбираться совершенно случайно. Однако, я буду стараться подбирать те языковые средства, на которых данный паттерн реализуется не тривиально;
    • Каждый мой пост, будет содержать как минимум 5 секций — Проблема, Описание патерна, Практическая задача, Диаграмма классов и Реализация;
    • Если Вы, с чем-то не согласны или у Вас есть дополнения к материалу, изложенному мной — я буду рад их почитать в комментариях. Однако, помните — я тоже изучаю паттерны вместе с Вами :)


    Интро


    Как Вы уже успели заметить, данная секция является не обязательной в моих постах про паттерны. Но я не мог начать писать о конкретном паттерне, не объяснив, зачем они собственно нужны.

    Не так давно, когда я был на младших курсах, на мое высказывание «Да мы уже умеем писать программы!», мой коллега сказал — «Максимум, что вы умеет писать — это алгоритмы, но не программы.» Эти его слова я вспоминаю до сих пор с улыбкой на лице. Он был совершенно прав, все чему нас научили за 3 года (я тогда был на третьем курсе) — реализация базовых алгоритмов из Кормена/Кнута. Мы действительно могли писать эти алгоритмы, могли писать функции, процедуры реализующие их. Даже могли написать класс на C++ или Java в котором описать все логику работы с данным классом объектов. Но когда таких классов становилось два или даже три :) начинались проблемы. И при любой попытке написания какой-либо «программы» начиналось изобретение велосипеда (я думаю, что каждый кто читает этот пост, сам изобрел несколько таких велосипедов). Тогда, я начал подозревать, что должно быть что-то, какая теоретическая база, аппарат, механизм, называйте это как хотите, которая в принципе расскажет мне — «как писать программы».

    Оказалось, такая база есть — это паттерны проектирования. По большому счету, паттерн — это типичное решение общих проблем проектирования. Паттерны решают ряд основных проблем, связанных с программированием/проектированием. Это — изобретение велосипеда, проблема повторного использования кода, проблема сопровождения кода.

    Безусловно, шаблоны проектирования если не решают, то упрощают решение данных проблем. Так, разработчик или проектировщик, зная хотя бы базовый набор паттернов, использует их в замену придумываемым велосипедам. Повторное использование кода становится более прозрачное и оправданное. А сопровождать код, написанный с использованием паттернов как минимум понятно и не затруднительно.

    Теперь, мне кажется можно перейти к рассмотрению конкретного паттерна — «Адаптера» («Adapter»).

    Проблема


    Обеспечить взаимодействие объектов с различными интерфейсами. Адаптировать, а не переписывать существующий код к требуемому интерфейсу.

    Описание


    Паттерн «Адаптер», на самом деле, является одним из немногих, который программисты применяют на практике, сами того не осознавая. Адаптер можно найти, пожалуй в любой современной программой системе — будь это простое приложение или, например, Java API.

    Взглянем более детально на проблему, для понимая того как должен выглядеть Адаптер. Проблема, опять-таки, заключается в повторном использовании кода. Иными словами, есть клиент, который умеет работать с некоторым интерфейсом, назовем его клиентским. Есть класс, который, в принципе, делает то, что нужно клиенту но не реализует клиентский интерфейс. Безусловно, программирование нового класса довольно бессмысленная трата времени и ресурсов. Проще адаптировать уже существующий код к виду, пригодному для использования клиентом. Для этого и существует адаптер. Причем, разделяют два вида адаптеров — Object Adapter (адаптер на уровне объекта) и Class Adapter (адаптер на уровне класса). Мы рассмотри оба, но по порядку.

    Практическая задача



    Для примера, рассмотрим простую ситуацию. Есть некоторый класс — SequenceGenerator, генерирующий последовательности целых чисел, по определенному закону — это и есть наш клиент. Есть интерфейс — Generator, который использует клиент непосредственно для генерации каждого отдельного элемента последовательности — это наш клиентский интерфейс. К тому-же, есть класс RandomGenerator, который уже умеет генерировать случайные числа. Конечно, SequenceGenerator не может использовать RandomGenerator для генерации элементов, потому что он не соответствует клиентскому интерфейсу. Наша задача — написать адаптер (двумя способами) RandomGenerator к SequenceGenerator.

    Диаграммы классов



    Object Adapter




    Class Adapter




    Итак, имея диаграммы классов давайте поговорим об отличиях между адаптером на уровне объекта и адаптером на уровне класса. На самом деле различия видны уже из названия. В первом случае, адаптируемый объект (RandomGenerator) является полем (ссылкой) в классе адаптера (RandomGeneratorAdapter), во втором же он и является адаптером за счет использования механизма наследования. В реальных проектах, рекомендуется использовать Object Adapter, за счет его меньшей связности с адаптируемым объектом.

    Реализация



    Рассмотрим реализацию поставленной задачи. Object Adapter я реализовывал на С++, Class Adapter на Java.

    Object Adapter


    class Generator {
    public:
      virtual int next() = 0;

    };

    class SequenceGenerator {
    private:
        Generator *generator;
    protected:
    public:
      SequenceGenerator(Generator& generator);

      int* generate(int length);
    };

    SequenceGenerator::SequenceGenerator(Generator& generator) {
      this->generator = &generator;
    }

    int* SequenceGenerator::generate(int length) {
      int *ret = new int[length];
      
      for (int i=0; i<length; i++) {
        ret[i] = this->generator->next();
      }

      return ret;
    }

    class RandomGenerator {
    public:
      inline int getRandomNumber() { return 4; }; // It`s really random number.

    };

    class RandomGeneratorAdapter : public Generator {
    private:
      RandomGenerator *adaptee;
    public:

      RandomGeneratorAdapter(RandomGenerator& adaptee);

      virtual int next();

    };

    RandomGeneratorAdapter::RandomGeneratorAdapter(RandomGenerator& adaptee) {
      this->adaptee = &adaptee;
    }

    int RandomGeneratorAdapter::next() {
      return this->adaptee->getRandomNumber();
    }


    // Использование

    int main(int argc, char *argv[]) {

      RandomGenerator rgenerator;
      RandomGeneratorAdapter adapter(rgenerator);
      SequenceGenerator sgenerator(adapter);

      const int SIZE = 10;

      int *seq = sgenerator.generate(SIZE);

      for (int i=0; i<SIZE; i++) {
        cout << seq[i] << " ";
      }
      
    }


    * This source code was highlighted with Source Code Highlighter.


    Class Adapter

    Классическая реализация паттерна Class Adapter подразумевает использование множественного наследования. В Java, можно воспользоваться имплементацией интерфейсов. На мой взгляд, это даже как-то корректнее.

    public interface Generator {

      public int next();

    }

    public class SequenceGenerator {
      
      private Generator generator;

      public SequenceGenerator(Generator generator) {
        super();
        this.generator = generator;
      }

      public int[] generate(int length) {
        int ret[] = new int[length];
        
        for (int i=0; i<length; i++) {
          ret[i] = generator.next();
        }
        
        return ret;
      }
    }

    public class RandomGenerator {
      
      public int getRandomNumber() {
        return 4;    
      }
    }

    public class RandomGeneratorAdapter extends RandomGenerator implements Generator {

      @Override
      public int next() {
        return getRandomNumber();
      }
      
    }

    // Использование
    public class Main {

      public static void main(String[] args) {
        
        RandomGeneratorAdapter adapter = new RandomGeneratorAdapter();    
        SequenceGenerator generator = new SequenceGenerator(adapter);
        
        for (int i: generator.generate(10)) {
          System.out.print(i + " ");
        }
      }
    }


    * This source code was highlighted with Source Code Highlighter.


    На этом все. Жду Ваших отзывов в комментариях.

    Share post
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 62

    • UFO just landed and posted this here
        +7
        В принципе, прочитав Ваш комментарий, понимаю, что потратил время не зря :) Спасибо Вам.
          +30
          Автору спасибо!

          <офтопик>

          Я могу рассказать мотивацию минусующих.

          Публика на хабре (да и не только на хабре) довольно чётко делится на школьников и нешкольников (возраст и принадлежность к школе тут не причём).

          Школьник имеет узкий кругозор (скажем, знает только PHP4), всё, что выходит за рамки этого кругозора он воспринимает, как враждебное пространство, и защищая свой убогий мирок, сразу бросается в атаку — минусует люто. Эти люди сидят на хабре постоянно и жмут в браузере кнопку «обновить». Как только появляется новая статья — на неё налетают эти минусяторы и… правильно! — минусуют люто :-) Поставив минус, школьник переключается на новые мишени и наш рассказ про школьника заканчивается.

          Нешкольник же имеет более широкий кругозор, но главное, то, что выходит за пределы его знаний, он воспринимает не враждебно, а с любопытством. Эти люди приходя на хабр раз в день или даже раз в неделю и плюсуют.

          Поэтому практически любая стоящая статья сперва уходит в минус. Этого не надо бояться, потом она выплюсовывается, когда школьники потеряют к ней интерес.

          </офтопик>

            –18
            НЕНАВИСТЬ!!!11
            • UFO just landed and posted this here
                0
                важен не мирок, а то, как человек относится к тому, что снаружи
                кстати, ещё одно наблюдение, минусяторы минусуют и в коммент и в карму, а вот плюсяторы в карму не плюсуют; мне этот коммент дорого обошёлся :-)
                  0
                  Вам ещё нужны какие-то баллы для самооценки? Тогда мы идём к Вам =)
                  Если Вы написали, что-то стоящее и Вас оценили — хорошо. Если не оценили — Вы же писали это дял самого себя, для своей самореализации.
                0
                Есть подозрение из-за методичности и постоянства, что школьники написали скрипты которые просто все минусуют. Selenium IDE например умеет делать такие скрипты или IMacros т.п.
                +3
                Возможно минусы продиктованы тем, что список паттернов, которые автор собирается осветить очень похож на оглавление книги «Банды четырех», где эти паттерны замечательно описаны.
                  0
                  Список паттернов который я привел, взят из учебной программы курса, которая, в свою очередь, составлялась видимо по этой книге.
                    +1
                    А так же возможно минус продиктован тем, что диаграмма, приведенная в качестве примера адаптера в википедии, в несколько раз более понятна и актуальна, (даже без «простыни» исходника на джаве) чем ваша статья:


                    Впрочем, вы то молодец, что вникаете и пробуете реализовывать, это конечно полезно — только на публику лучше выкатить не коня в вакууме, а действительно распространенный пример. Ведь с этим паттерном имел дело чуть ли не каждый второй на хабре) — поскольку он активно используется почти во всех веб-движках и цмс-ках, а вы тут какието рэндом дженераторы на сях наваяли))
                    +1
                    А Вы попробуйте рассказать что-нибудь про паттерны и не пересечься с GoF :). На самом деле, даже слово «паттерн» в программирование внесли именно они. Из архитектуры (но это уже — классика жанра).
                    +2
                    У меня есть теория, почему минусуют:
                    думаю, все, кто хотел знать, что такое адаптер, уже очень давно знает и эта тама им как сортировка пузырьком, они просто просматривают, видят что все то же самое, десяток раз разказанное. Вот и минусуют/игнорируют от разочерования.
                    те, кто считает ООП корнем зла могут тоже минуснуть. Хотя в последнюю группу можно отнести любителей фп, и подростков с пехепе. Первые обычно культурные люди — остальные минусуют с криком «че этот дядя нам парит».
                    Если есть люди плюсуют. Думаю, это люди с самой первой моей когорты, которые подумали что вдруг найдется человек который не знает адаптера, и хочет сделать мир лучше. К ним себя отношу. Ну и конечно те кто сейчас активно учатся, хотя думаю таких очень мало.
                      +4
                      проблема в системе инвайтов, их получают в основном те, кто удачно шутит. а за «иные» взгляды на технические вещи любят поминусовать. у тоге хабр превращается в башорг, достаточно просто посмотреть посты за последнее время, которые пишут «чисто постебаться».
                      0
                      Достаточно хорошее описание, мне понравилось. Какой следующий паттерн собираетесь описывать? Я бы почитал про Visitor
                        0
                        Все по списку — spiff.habrahabr.ru/blog/84706/. Далее мост и компоновщик.
                          +1
                          о, круто. может будете вставлять там ссылки на написанные посты?
                            0
                            Хорошая идея :) сейчас сделаю.
                        +1
                        Ждать примеры на питоне?
                          0
                          Безусловно. Но не к этому паттерну :)
                            +2
                            А какая разница?
                              –2
                              Наличие множественного наследия, достаточная причина?
                                +1
                                Замените часть агрегированием. Какие проблемы? На все языки примеров не напасёшься, а хороший программист должен ориентироваться в любой обстановке.
                                  0
                                  А кто сказал что есть проблемы? Просто, думаю, раз

                                  «Классическая реализация паттерна Class Adapter подразумевает использование множественного наследования.»

                                  то разумно было бы использовать язык оным, а не заменять близким, но не эквивалентным понятием. Только и всего.
                            0
                            Хорошее описание с чёткой структурой повествования, спасибо
                              +8
                              Статья очень хорошая, побольше бы таких авторских статей. Буду ждать обещанных следующих. Немного прокомментирую «интро».

                              Я разделяю (довольно распространённое) мнение, что изучение (и, как следствие, попытки «обдуманного» использования) шаблонов проектирования на ранних этапах становления программиста скорее вредно, чем полезно. В развитии через «велосипеды» нет ничего плохого, скорее наоборот — все проходят через эти стадии. И в итоге вырабатываются свои пути мышления, и свои особенности видения каждой практической проблемы и придумываются свои фишки для каждого конкретного случая. Прикол как раз в том, что набор шаблонов охватывает большинство разумных случаев таких архитектурных фишек. И направление их применения должно быть именно «проблема» -> «о, я знаю как решить» -> «да это же шаблон '...'!», а не обратное. Видел в практике кучу случаев, когда пытались сувать шаблон туда, куда он ну никак не лез. Говоришь: «ну не получается ведь, сделай ты вот так и так, ведь короче, проще и логичнее», а он: «нет, здесь, походу, нужна фабрика и я её приделаю». Зачем вот так вот?

                              К чему я это? К описанному Вами назначению шаблонов. Немного непонятно, как шаблоны могут помочь избежать велосипедов, ведь это просто схематические описания архитектурных конструкций. Всё же я считаю, что основное назначение шаблонов — в классификации кода и, возможно, приведении его к какому-либо каноническому виду. Чтобы, например, при обсуждении с коллегами каких-либо поставленных проблем и их решений или самого кода «этот вот класс, хранящий статический экземпляр объекта в единственном виде» называть синглтоном, а «типа очередь объектов определённой длины, чтобы их не создавать каждый раз, а брать оттуда и класть обратно» — пулом объектов. А шаблоны ради шаблонов — зло. Имхо.
                                0
                                Абсолютно с Вами согласен. Когда я писал про сопровождение кода, я именно это и имел ввиду. Код написанный в соответствии паттернам проще и читать и сопровождать и соответственно обсуждать на тех-же code review.
                                +3
                                Не знаю, но я почему-то не понял с ходу суть. Т.е. её можно понять, но тогда проще сделать googel Adapter.

                                BTW, фасад — это не то же самое?

                                + множественное наследование смущает.

                                  +5
                                  Фасад не тоже самое. Фасад нужен для того, чтобы предоставить сложный API существующей архитектуры в более «легком» виде, скрывая конкретную имплементацию более упрощенной.

                                  Адаптер можно например использовать при переходе от старой версии приложения к новой, когда старая версия больше не переписывается (не поддерживается etc.), но её функционал еще используется и в новой версии, через адаптер.

                                  Мне кажется, в статье можно было бы понятнее что ли определить «Проблему», хоть в «Описании» позже все разьясняется. Что-то типа «Паттерн „Адаптер“ обеспечивает взаимодействие API клиентского объекта с API адаптируемого обьекта. Он разруливает работу обьектов, которые из-за сильно различающихся API не совместимы друг с другом».
                                    0
                                    А что конкретно Вас смущает во множественном наследовании?
                                      0
                                      То же, что и в goto.

                                      www.osp.ru/os/2001/02/179920/

                                      googel «минусы множественного наследования»

                                      А если одной фразой, то — мультипликативное повышение сложности, с которым на самом деле ООП призвано бороться.
                                        0
                                        Я понимаю Вашу неприязнь к множественному наследованию. Я сам, на самом деле ни разу на практике не применял его. Попросту не было необходимости. Однако, в классической литературе, написанной кстати говоря давненько, говорится, что именно множественное наследование реализует паттерн Class Adapter. И то, что сейчас мы вправе заменить это самое множественное наследование на термин «реализация интерфейса» — это наше право :)
                                          0
                                          Ммм… Не уверен, что мы так можем это сходу сделать.

                                          Само мн.наследование наследует:
                                          — private, protected и public;
                                          — атрибуты, свойства и методы.

                                          А интерфейсы из этого зла оставляют только архитектурно удобные public-описания методов (есть конечно же и свойства/атрибуты, но это уже реже применяется), как описания дырок для розеток — не более.

                                          Поэтому я здесь больше за интерфейсы, нежели множественное наследование, которое где-то и разрешено. Прыгать с моста тоже разрешено :)
                                    +1
                                    Может перенести в тематический блог? Скажем, в «Совершенный код», или в «Разработка»?
                                    • UFO just landed and posted this here
                                        0
                                        Спасибо за предложения, я обязательно их учту.
                                        • UFO just landed and posted this here
                                            +1
                                            Почему-же, вполне себе объектно-ориентированный язык :) Значит имеет право считаться пригодным для реализации шаблонов.
                                            • UFO just landed and posted this here
                                                +1
                                                только не С#. Java — отличный выбор. Она ровно настолько немногословна и абстрактна, как это нужно для примеров
                                            +2
                                            Большинство патернов как раз таки имеют классическую имплементацию на java и с++, потому как возникли задолго до c#…
                                            –2
                                            Зачем читить хабравыжимки когда надо читать Александруску на ночь www.ozon.ru/context/detail/id/3829080/. Главное не заболеть ООП мозга.
                                            • UFO just landed and posted this here
                                              +2
                                              Статью не минусовал, т.к. тема хорошая, но знаю за что можно было бы:
                                              1. Слишком долгое вступление. И вообще зачем оно, если есть ссылка на «почитать о других паттернах»?
                                              2. Диаграммы из свободного редактора само собой… но они унылые. Да и что значит какая стрелочка — это тема для целой отдельной статьи.
                                              3. Кода достсточно много. Пустые строки лучше было заполнить коментариями.
                                              4. Нет описания недостатков паттерна, как и обоснования использования (хотя каждый опытный программист и так знает, но статья ведь не для них).
                                              5. Нет особенностей, связанных с языками программирования.

                                              Но все же буду рад продолжению.

                                                0
                                                1. Дело в том, что вступление — единственное, его нет в других постах. Просто я не хотел выносить его в отдельный пост.
                                                2. Диаграммы, кстати, очень даже очевидные, стрелочки — чистой воды классика. И да, они унылые, но я предупреждал :)
                                                3. Полностью согласен.
                                                4. Согласен, учту на будущее.
                                                5. Ну, точнее есть одна — перед реализацией Class Adapter, я пояснил про замену множественного наследования на реализацию интерфейсов в Java.
                                                +1
                                                sourcemaking.com/
                                                Хороший сайт по паттернам и антипаттернам с кучей примеров на C++/С#/Java/PHP/Delphi, a также UML диаграммами.
                                                  +1
                                                  Личная просьба: не больше 1 паттерна раз в пару дней. Написать можно хоть сейчас всё, а вот публиковать постепенно, давая возможность оценить, обсудить и пофлеймить :)
                                                    0
                                                    Договорились :)
                                                    0
                                                    Нужно вынести «интро» в отдельную заметку, выбросить «проблему», заменить «практическую задачу» практической задачей, выбросить диаграммы, и заменить примеры кода лаконичным примером псевдокода.
                                                      0
                                                      Хорошая статья, автору спасибо! Интересно будет почитать и про другие паттерны. Особенно интересны UML-диаграммы и код на каком-нибудь языке. Кстати говоря, когда читал классическую книгу «Банды Четырех», многого вообще не понял, а здесь так понятно все расписано…

                                                        +1
                                                        Кстати, по поводу использования, очень часто такой паттерн называют «обёртка» или wrapper. Возможно, в этом названии он даже более знаком большинству программистов.
                                                          –1
                                                          Зачем эта статья если в GOF-е все это написано и причом на порядок подробнее? Русский перевод GOF — rsdn.ru/res/book/oo/design_patterns.xml
                                                            –1
                                                            Хорошо бы использовать комментарии для кода. А то из-за мало понятного мне синтаксиса С++ все никак не могу уловить разницу между двумя подходами.
                                                              0
                                                              В первом случае (Object Adapter) адаптер использует ссылку на адаптируемый объект и делегирует его методы. Во втором (Class Adapter), адаптеру нет необходимости хранить какие либо ссылки, ввиду того что он уже содержит необходимые методы, полученные в результате наследования.
                                                              +1
                                                              Спасибо за статью, примеры кода наверняка помогут начинающим! Считаю, что сам паттерн давно разжеван в классических книжках, но разнообразные примеры, уверен, новичкам пригодятся. Отдельное спасибо за указание бесплатного UML-редактора! Недавно задавался целью найти свободный редактор, но гугл решил мне про astah не рассказывать. :)
                                                              • UFO just landed and posted this here
                                                                –1
                                                                Че-то я не понял ни по диаграммам, ни по коду. Для адаптера нужно всего 3 элемента:

                                                                1. адаптируемый код (класс, объект или функция)

                                                                2. образец, к которому нужно адаптировать (базовый класс / интерфейс / протокол / прототип функции)

                                                                3. адаптер — код, выполненный по образцу, который внутри себя обращается к адаптируемому коду.

                                                                Хорошие диаграммы в английской википедии.
                                                                На примере функций вообще все просто.
                                                                  –1
                                                                  специально для запутывания на разных языках написали?

                                                                  объясняю понятно:
                                                                  есть программа которая умеет принимать запросы через spring
                                                                  есть другая программа которая ей по spring запросы посылает
                                                                  в каждой программе есть spring — 30 мб либ
                                                                  надо научить третью программу посылать запросы в первую, но не вставляя spring в нее
                                                                  пишем адаптер в первой проге, который принимает через обычный rmi, а внутри себя уже конвертирует в spring
                                                                  вторая и третья проги используют этот класс (точнее его интерфейс). во второй spring больше не нужен.
                                                                  вот пример адаптера.
                                                                  а то что в этой статье — ну нихрена неясно же.
                                                                  • UFO just landed and posted this here
                                                                    • UFO just landed and posted this here
                                                                      –1
                                                                      спасибо за статью, как раз сдаю лабы с паттернами, пригодится
                                                                        +1
                                                                        мне вот интересно как наредо разбирающийся в теме, рассуждает при выборе шаблона.

                                                                        к примеру, есть задача: создать генератор для заполнения базы данных.

                                                                        первое что приходит на ум:
                                                                        — интерфейс с единственным методом Generate
                                                                        — куча классов генерирующих конкретные типы данных (Имена, Параграфы, E-mail'ы и т.п.)
                                                                        — каждый конкретный генератор имеет множество методов Generate с множеством параметров, к примеру для генерации параграфов — это количество этих самых параграфов, а для генерации даты — это mix, max значения.

                                                                        — все генераторы сложены в отдельную библиотеку, клиентом для которой есть генератор запросов в базу — связь между библиотекой и генератором должна быть максимально слабой для того чтобы при изменении последних не нужно было править кучу кода.

                                                                        хотел спросить как лучше уменьшить связь между самими генераторами, к примеру генератор параграфов использует генератор слов — но как быть если атрибуты меняются — опять менять кучу кода, но с другой стороны если делать все через «обертку» библиотеки — то проблем не будет.

                                                                        вроде как все.

                                                                        В общем что получается, как правильно было записано в интро, мы все можем наклепать алгоритмы для генерации чего либо, а вот как это все в кучу слепить?
                                                                        Проблема в том, что как я понимаю, в такой задаче одним шаблоном не обойтись и тут самое интересное, потому как оно вроде все понятно когда читаешь описания шаблонов, но вот когда надо собрать это все в кучу — начинаются проблемы, собственно потому и интересно услышать мнение людей понимающий чтото в этой теме…

                                                                        Only users with full accounts can post comments. Log in, please.