Фишки Kotlin в Java

Язык Kotlin известен своим синтаксическим сахаром (чем он по большей части и является). Сегодня мы рассмотрим одну особенность этого языка и попробуем сделать то же самое в языке Java.

Для начала рассмотрим обычный класс на Kotlin.

Обычный класс с 2 параметрами, которые помечены как final, но у которых есть дефолтные значения (defaultName, 0). Это значит, что в Kotlin не нужно указывать все параметры, как если бы мы делали в Java. Давайте посмотрим на это чтобы понимать о чем речь.

Сначала мы создаем объект без параметров и выводим данные в консоль. Значит поля класса будут иметь дефолтные значения. После чего мы попытаемся задать параметры и опять выведем в консоль. Трудность в Java в том, что мы должны указать или все параметры (а их может быть сколько угодно) или нисколько. Итак, вывод будет такой.

А теперь посмотрим на возможности языка Kotlin на том же примере.

Здесь мы видим, что можем создать объект нашего класса передавая тот параметр, который мы хотим, остальные, которые не указаные явно будут иметь дефолтное значение. Т.е. я могу создать объект с первым или со вторым параметром или же как и в Java с обоими (тогда не нужно указывать имя параметра как и в Java).

Посмотрим что выводится в консоль в этом случае.

Явно указанный параметр и дефолтное значение.
Дефолтное значение и явно указанный параметр.
И случай с аналогией с Java.

Теперь, как бы выглядел тот же класс в Java? Как-то так:

Т.е. для 2 параметров нам бы понадобилось 4 конструктора со всеми вариантами.

Посмотрим использование аналогично котлиновскому классу.

И вывод в консоль аналогично будет таковым как и в котлиновском классе.

Обход трудностей в Java.

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

Итак, у нас поля не являются final, они имеют изначальное дефолтное значение. У нас есть как и конструктор со всеми параметрами (если хотите указать все сразу) и пустой конструктор. Интерес здесь представляют сеттеры, суть их в том, что они сначала проверяют значение поля на равенство дефолтному и только после этого сетят новое значение. Т.е. если вы устанавливаете значение поля первый раз, то проверку пройдет в сеттере и новое значение удачно будет проставлено полю, НО если вы попытаетесь установить значение второй раз и дальше, то проверку оно не пройдет, так как в поле уже будет иное значение нежели дефолтное. Этой проверкой мы гарантируем простановку значения лишь 1 раз.

Давайте протестируем и убедимся в этом. Junit4 тест.

Если его запустить, то он пройдет все проверки. Сетим первое значение и проверяем, что у объекта оно установилось, после чего сетим новое значение и проверяем, что оно не изменилось.

Итак, мы сымитировали фишку Kotlin.

Рубрика: Программирование Java | Оставить комментарий

Шаблон проектирования MVP

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

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

Именно поэтому был создан шаблон проектирования MVP. Прочитаем описание в википедии.

MVP — шаблон проектирования пользовательского интерфейса, который был разработан для облегчения автоматического модульного тестирования и улучшения разделения ответственности в презентационной логике (отделения логики от отображения):

  • Модель (англ. Model) — хранит в себе всю бизнес-логику, при необходимости получает данные из хранилища.
  • Вид (англ. View) — реализует отображение данных (из Модели), обращается к Presenter за обновлениями.
  • Представитель (англ. Presenter) — реализует взаимодействие между моделью и представлением.

Картинки по запросу model view presenter

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

Суть шаблона MVP именно в том, что представление и модель данных не имеют прямой связи и «ничего не знают друг о друге». А единым связующим звеном является презентер.

Давайте рассмотрим простейший код для разработки андроид.

Выделяем интерфейс, в котором будут методы отображения прогресса, скрытия и отображение данных (на вход будет принимать некий объект, содержащий необходимые для отображения данные).

Дальше необходим интерфейс для модели. В нашем случае это будет получение данных из сети.

Реализация модели может выглядеть таким образом.

В конструкторе передается инстанс презентера и при получении данных передается ему.

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

Теперь напишем реализацию презентера.

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

Теперь напишем реализацию вью. В андроид это будет Активити.

Итак, на экране есть поле ввода, текст для отображения (может быть что угодно) и кнопка, при нажатии на которую идет поиск. В методе onCreate инициализируем презентер, элементы отображения и устанваливаем обработчик нажатия на кнопку, при котором лишь вызываем метод у презентера. Переопределяем методы представления в каждом из которых прописываем код для отображения.

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

Самое замечательное в шаблоне проектирования MVP то, что можно писать простые тесты для каждого слоя. Необходимо лишь заменить один слой на предоставление мокированных (фальшивых/предопределенных) данных и тестировать логику вызова методов другого слоя.

Рубрика: Программирование Android, Программирование Java | Оставить комментарий

Шаблон проектирования MVC

Перед прочтением данной статьи настоятельно рекомендую ознакомиться с этой статьей про принципы объектно-ориентиванного программирования SOLID.

Что такое шаблон проектирования и зачем он нужен. Предположим вы разрабатываете андроид приложение, где одним из главных компонентов является Activity, класс, который отвечает за пользовательское взаимодействие. Без применения шаблона проектирования многие новички пишут в этом классе все (от обращений в сеть и базу данных до логики обработки этих данных и отображения пользователю).

Здесь в первую очередь нарушется первый принцип ООП SOLID, S — принцип единой ответственности. Каждый класс должен делать лишь то, что он должен, не больше и не меньше. Тогда возникает вопрос, а что должен делать тот или иной класс и как, собственно, разделить ответственности?

На этот вопрос отвечает как раз шаблон проектирования MVC (как пример). Давайте посмотрим что о нем написано в википедии.

Model-View-Controller (MVC, «Модель-Представление-Контроллер», «Модель-Вид-Контроллер») — схема разделения данных приложения, пользовательского интерфейса и управляющей логики на три отдельных компонента: модель, представление и контроллер — таким образом, что модификация каждого компонента может осуществляться независимо.

  • Модель (Model) предоставляет данные и реагирует на команды контроллера, изменяя свое состояние.
  • Представление (View) отвечает за отображение данных модели пользователю, реагируя на изменения модели.
  • Контроллер (Controller) интерпретирует действия пользователя, оповещая модель о необходимости изменений.

Значит весь код, который новичок пишет в одном классе можно и нужно разбить на 3 отдельных класса. В модели будут происходить запросы в сеть или в базу данных, при получении команды от контроллера и передавать эти данные в представление для отображения пользователю. Т.е. пользователь, предположим, ввел данные для поиска, эти данные через контроллер перейдут в модель, получив ответ от сервера новые данные через представление будут отображены пользователю.

MVC-Process.png

Данный шаблон проектирования не совсем подходит для Андроид (об этом в следующей статье). Если коротко, то в андроиде сложно разделять одни элементы как контроллеры, а другие как представление и поэтому в случае андроида один и тот же класс Activity будет частично и тем и другим.

Шаблон MVC хорошо подходит например для игровых приставок, где пользователь взаимодействует с контроллером (джойстиком) нажимая определенные кнопки, а отображение данных происходит на экране монитора. Здесь есть явное разделение ответственности. В мобильном же устройстве и ввод и отображение происходит посредством одного и того же элемента (если мы говорим о сенсорных мобильных устройствах, а не кнопочных аналогах).

Также этот шаблон хорошо подходит например если вы разрабатываете игру для настольного компьютера. Тогда весь ввод будет осуществляться через клавиатуру (как пример), а отображение через монитор.

Тогда у нас будут эти 3 класа, для ввода данных, для обработки и для отображения. Каждый класс отвечает за одно действие и не зависит от другого. Это не только упрощает саму разработку, но и позволяет покрывать тестами каждый слой (класс), как плюс можно переиспользовать одни классы для других слоев.

Итого 100500 строчек кода в одном классе разбились на 3 отдельных класса по 100 строк, что довольно упрощает как чтение этого кода, так и поддержку.

Можно рассмотреть пример игры в коде.

Вот так может выглядеть класс представление. Он знает лишь о модели и взаимодействует лишь с ней.

Так может выглядеть класс Контроллер. В нем методы обработки клавиатуры, которые обновляют данные в модели.

А так может выглядеть класс Модель. При каждом изменении позиции вызывается метод у класса представление чтобы отрисовать персонажа по новым координатам.

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

Рубрика: Программирование Android, Программирование Java | Оставить комментарий

Junit тесты на простом примере

Давайте рассмотрим как писать junit тесты на простом примере.

Допустим стоит задача, определить, образуют ли 3 точки на плоскости прямоугольный треугольник. Как видим из самой постановки задачи требуется написать по крайней мере 2 класса, Точка и Треугольник.

Так как точка находится на плоскости, для упрощения жизни будем брать дискретные значения, поэтому х и у будут у нас целочисленными. Также в этом классе нам нужен будет метод, который находит расстояние между 2 точками. Как параметр логично будет передавать другую точку. Так как в нее могут передать null нужна первая обработка и выброс исключения. Также точки могут совпадать, обезопасим это проверкой через переопределенный equals & hashCode (см. предыдущие статьи зачем это делать ссылка).

Теперь нам необходимо для начала протетировать наш метод получения расстояния между 2 точками. Для этого создадим Junit Test в папке test (ее надо пометить тестовой). Сам класс теста создается через комбинацию ctrl+shift+T.

Так как у нас могут быть по сути 3 случая (2 ошибки и рабочий вариант), то мы напишем 3 отдельных теста. В первом проверим ожидаемый результат и фактический через матчер is.  Во втором и третьем тесте проверим выброс ошибки, класс ошибки нужно указывать в скобках @Test(expected = MyException.class)

Теперь напишем класс Треугольника у которого есть 3 стороны. Треугольник можно создать через 3 стороны или 3 точки. Но не каждые 3 стороны могут образовать треугольник. Для этого напишем проверки. Если длины сторон не больше 0, то выбрасываем ошибку прямо в конструкторе, потому что мы хотим знать где что-то пошло не так. Если все длины сторон больше нуля, то нужно проверить что каждые 2 стороны в сумме дают больше чем третья сторона (это и есть верный способ проверки существования треугольника в принципе). Если стороны треугольник не могут образовать, то опять бросаем исключение с поясняющим текстом.

В конструкторе через 3 точки вызываем конструктор 3 сторон, передавая длины через метод расчета класса Точка (единственный минус здесь, что могут передать null вместо точек, но NullPointerException сразу покажет где ошибка).

Теперь, когда у нас есть 3 стороны и они образуют треугольник, можно уже проверить, является ли треугольник прямоугольным. Для этого напишем простой метод, который будет перебирать все комбинации сторон. А саму проверку по теореме Пифагора вынесем в собственный метод.

Хорошо, мы написали метод проверки, но его надо протестировать. Для этого опять создадим класс Теста. У нас есть несколько случаев выброса исключения и их надо бы проверить, поэтому для каждого случая написали отдельный тест с ожидаемым выбросом исключения. Метод проверки на прямой угол может вернуть 2 значения, true || false, поэтому мы напишем несколько возможных комбинаций (через стороны и через точки) для каждого случая. Все эти треугольники можно положить в список и назвать соответственно прямоугольные треугольники и непрямоугольные. Пройдя через цикл проверяем значение метода (можно было написать и через список актуальных значений, т.е. положить булевые переменные в список и проверить 2 списка).

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

При написании тестов необходимо учитывать все возможные и невозможные случаи  и в зависимости от полноты тестов можно быть уверенным в том, что методы написаны правильно.

Рубрика: Программирование Java | Оставить комментарий

Агрегация и композиция или обход множественного наследования в Java

Когда нас спрашивают про принципы ООП многие (большинство новичков) говорят лишь о 3(4) принципах — Инкапсуляция, Наследование, Полиморфизм (+Абстракция). Некоторые также перчисляют принципы SOLID. Но лишь немногие также вспоминают про еще 2 понятия, такие как Агрегация и Композиция (они являются частными случаями Ассоциации, когда 2 объекта имеют некую связь).

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

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


class A {
public void doSomethingA(){}
public void doElseA(){}
}
class B {
public void doThingB(){}
}


class Main {
private A a;
private B b;
public doSomething() {
a.doSomethingA();
}
public void doElse{
a.doElseA();
}
public void doThing(){
b.doThingB();
}
}

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

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

Рубрика: Программирование Java | Оставить комментарий

Принципы DRY, KISS, YAGNI

В предыдущей статье мы рассмотрели принципы SOLID (если вы не читали этот пост, то настоятельно рекомендую ознакомиться с ним перед тем как читать этот). Наряду с ними есть еще пару принципов, следуя которым ваш код будет более чистым.

Итак, первый принцип —   DRY (Don’t Repeat Yourself) который переводится как — не повторяйте себя. Суть этого принципа в том, чтобы замечать дублирующиеся участки кода и избавляться от копирующихся строк кода.

Давайте рассмотрим следующий пример из андроида. Есть 2 активити, внутри которых одни и те же вью элементы и они одинаково инициализируются в методе onCreate. И мы видим одинаковые строчки кода в обоих активити. В этом случае можно поступить так — выделить базовый класс активити и оставить эти вью в нем, а 2 активити отнаследовать от них.

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

В чем плюс этого принципа — не только уменьшать объем кода, который видим в проекте, но и в том, что если в одном месте придется модифицировать код, то его придется модифицировать и в другом аналогичном месте, а иметь 1 точку, которую можно будет спокойно модифицировать и знать, что в остальных местах изменения применятся — это ли не замечательно? Так например и работает принцип внедрения зависимостей. У нас есть интерфейс, который инжектится в класс, а конкретную реализацию всегда можно поменять в 1 месте и во всем проекте все методы будут отрабатывать иначе.

 

Теперь давайте рассмотрим принцип KISS (Keep It Short & Simple) что можно перевести как — держите все коротким и простым. Т.е. суть в том, чтобы писать короткие и простые для понимания функции-методы, классы, интерфейсы и т.д.

Об этом принципе неявно говорит Роберт Мартин в книге Чистый Код. Он подчеркивает, что при написании методов надо помнить несколько правил — методы должны быть короткими, второе правило — они должны быть еще короче. Также он подчеркивает важность количества аргументов. Лучшая функция та, у которой нет аргументов, когда есть 1 аргумент то неплохо, 2 — максимальное оптимальное количество, но когда их 3 и больше, то это скорей всего значит что у вас довольно сложная функция, которую было бы неплохо разбить на мелкие составляющие. Также о простоте — надо именовать переменные и методы насколько возможно ясно, чтобы не было необходимости даже писать джавадок над этим методом или комментарий, поясняющий суть.

Что насчет классов, если в нем более 500 строк (условно) значит что-то пошло не так и надо посмотреть, можно ли выделить второй класс и отделить логику. Никто не любит громоздские классы в овер9000 строк кода, потому что его сложно в первую очередь читать, а потом уже поддерживать и модифицировать.

И еще один момент с интерфейсами. Они должны хранить в себе насколько возможно меньше методов. Лучше иметь 2 интерфейса, 1 из которых наследуется от второго, чем 1 интерфейс в котором все 6 методов например.

 

И третий принцип, который коррелирует с предыдущим принципом YAGNI (You Aren’t Going to Need It) что переводится как — тебе это не понадобится. В чем суть принципа — не писать код, который вам в данную минуту не нужен. Зачастую, когда программист поделил свою большую задачу на мелкие и занят первым куском, то он может за одно и написать код, который относится ко второй части большой задачи. Почему это плохо, спросите вы, а я отвечу — это плохо тем, что при чтении кода в данный момент (пулреквест) ревьюера это собьет с толку, т.е. ситуация будет следующая — ок, ты пишешь класс запроса, а зачем ты написал еще и этот класс, оно не имеет никакого отношения к этой задаче? А в ответ можно будет услышать — ну, это задел на будущее, в следующей задаче мне это понадобится. На что опытный программист ответит — вот когда оно понадобится, тогда и напишешь. Суть в том, что когда ты приступишь к написанию этого участка кода, возможно, ты уже будешь думать иначе и удалишь этот код, который написал. Поэтому необходимо заниматься тем, что важно на данный момент. Еще один момент, который может произойти, ты напишешь код, который потом долго еще не будешь трогать и в какой-то момент полностью удалишь. А в проекте останется мусор из комментариев TODO завершить тогда-то и так-то. В итоге имеем так называемую утечку времени.

И самая частая ситуация. При решении задачи ты пишешь еще и код, который возможно когда-нибудь тебе пригодится, но как показывает практика врядли. Это из серии — написать вместо объекта список объектов, с мыслью, что, возможно, когда-нибудь в очень маловероятном случае, тебе понадобится не один объект, а несколько. Если эта вероятность слишком мала, то оно не стоит того. Вот когда произойдет этот маловероятный случай и нужно будет иметь не объект, а список, тогда и наступит лучший момент модифицировать код. Да, кто-то скажет что думает о масштабируемости, но мы не рассматриваем этот случай, а тот, когда действительно нет никакой явной и разумной причины и даже намека, что в этом куске кода понадобится еще один объект. Иначе бы весь наш код выглядел бы как классы с протектед методами и кучей пустых методов, на потом, которые бы 10 лет еще никто бы не трогал и что в итоге бы засоряло проект.

 

Все эти принципы не являются чем-то, чему надо следовать очень жестко, но в любом случае, в большинстве ситуаций, следование этим принципам сделать ваш код простым, коротким (чистым) и легким для расширения и изменения.

Рубрика: Программирование Java | Оставить комментарий

Принципы SOLID (продолжение ООП)

В предыдущей статье мы рассмотрели базовые принципы ООП (инкапсуляция, наследование и полиморфизм). Также многие стараются придерживаться принципов SOLID, которые являются чем-то более расширенным, чем основные принципы ООП. Итак, давайте рассмотрем каждый из 5 принципов.

S — Single responsibility — Принцип единственной ответственности. (ссылка на вики)

Я люблю упрощать и именно поэтому буду трактовать этот принцип с простой точки зрения. Этот принцип говорит лишь об одном, класс должен делать только то, что он должен делать и ничего больше. Т.е. он не должен зависеть напрямую от другого класса, потому как любое изменение во втором классе приведет к изменениям в текущем классе. Пример. Если нам нужно запросить какие-то данные с сервера и отобразить их каким-либо образом, то необходимо четко разделять ответственность. Т.е. у нас будет отдельный класс для создания запроса, отдельный класс для принятия ответа от сервера и отдельный класс, который будет работать с этими 2 классами.

class Request {
public Request makeRequest(String host, String path) {
//some implementation
}
}

class Response {
public Response getResponse() {
//some implementation
}
}

class NetManager {
private Request request;
private Response response;
public NetManager(Request request, Response response) {
this.request = request;
this.response = response;
}
public void getData() {
//use request to get response
}
}

На самом деле наши классы Request и Response должны были быть абстрактными, чтобы их реализации легко можно было подменить другими. Но суть принципа единственной ответственности именно в том, что каждый класс делает только то, что должен и ничего больше. Обработка результата например уже должна быть в другом классе, который принимает на вход сырые данные и лепит из них уже красивые для юзер интерфейса. Вы можете легко проверить, придерживаетесь ли вы этого принципа, если захотите в какой-то день порефакторить код и заменить одни классы на другие. Если это приводит к лавинному рефакторингу других классов, что в итоге приводит к тому, что вы затронули почти весь код в проекте — значит вы изначально проектировали ваши классы не по принципу единственной ответственности. Итого — не вводите в заблуждение других программистов, если ваш класс должен получить данные из базы данных, то в нем не должно быть других методов, кроме как получения данных.

O — Open closed principle — Принцип открытости/закрытости (ссылка на вики)

Этот принцип говорит о том, что наши классы должны быть доступны (открыты) для расширения, но (недоступны) закрыты для изменения. На мой взгляд, этот принцип пересекается с инкапсуляцией, потому как исходя из этого принципа нужно закрывать от конечного потребителя реализацию и не давать возможности изменять код. Как этого добиться? Использовать private поля и публичные методы, с учетом принципа открытости к расширению методы могут быть protected, но чтобы не давать возможности к переопределению, их можно помечать final. А сам класс оставить с модификатором public, чтобы можно было наследоваться от него. И еще один лайфхак — все главные классы можно переместить в модуль под названием Ядро и подрубать в проект в виде .aar, чтобы уж точно никто не мог модифицировать код.

L — Liskov substitution principle — Принцип подстановки Барбары Лисков (ссылка на вики)

В википедии расписано не очень очевидно и ясно, поэтому попробую передать суть принципа. Объекты в программе должны быть заменяемыми на экземпляры их подтипов без изменения правильности выполнения программы. А если проще, то необходимо построить архитектуру приложения таким образом, чтобы с легкостью заменять существующие классы их наследниками и не нарушать логики работы всего проекта. Т.е. как и в первом принципе, нужно например создать абстрактные классы или интерфейсы и работать с ними, а конкретные реализации уже например можно предоставлять в отдельном модуле при старте приложения (как пример абстрактная фабрика). Т.е. если например вы используете библиотеку Picasso для загрузки картинок в андроид, то необходимо спроектировать приложение таким образом, чтобы с легкостью можно было бы перейти например на библиотеку Glide, при этом не обрушив половины проекта. Для этого например можно написать свой класс обертку для загрузки картинок и уже внутри использовать механизм загрузки.

I — Interface segregation principle — Принцип разделения интерфейса (ссылка на вики)

Этот принцип весьма прост — он говорит о том, чтобы дробить интерфейсы на мелкие. Для чего это делать — предположим есть 1 класс и ему нужно 3 метода. Если все эти 3 метода записать в 1 интерфейсе, то, возможно, мы придем к следующей ситуации. Через какое-то время нам нужен будет класс, в котором понадобится 1 из этих 3 методов. Что делать тогда? Имплементировать весь интерфейс и оставить 2 метода пустыми? Данный принцип рекомендует разбить наш большой интерфейс на 3 мелких и имплементировать в первом классе все 3, а во втором только 1 из них. В итоге, когда поменяется какой-либо метод, то в том классе, в котором они не нужны, изменений и вовсе не будет. Или рассмотрим следующий пример — есть 2 класса, обоим нужен один и тот же метод, а еще первому классу нужен другой метод, который не нужен во втором классе, тогда мы напишем 2 интерфейса в каждом из которых по 2 метода, но в обоих интерфейсах будет один и тот же метод. И когда необходимость возникнет менять один метод в первом интерфейсе, то нужно будет не забыть, что точно такой же метод есть и в другом интерфейсе. Опять мы пришли к двойной работе и чтобы ее избежать стоит разделять логику в интерфейсы. т.е. лучше много мелких интерфейсов, чем 1 громоздский.

D — Dependency inversion principle — Принцип инверсии зависимостей (ссылка на вики)

Формулировку возьмем из википедии, так как она весьма ясна и понятна

  • Модули верхних уровней не должны зависеть от модулей нижних уровней. Оба типа модулей должны зависеть от абстракций.
  • Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций.

т.е. при построении приложения необходимо выстроить вертикаль зависимостей. Если у вас есть абстрактный класс машина, то он ничего не должен знать о конкретной реализации — форде мустанг. И если у вас есть 2 абстракции, то они не должны также зависеть друг от друга, нужно создать одну общую абстракцию и наследоваться обоим от нее. Этот принцип опять-таки помогает в рефакторинге кода. Если в вашем проекте происходит такая ситуация как — в одном месте затронул, в 2 других сломалось, значит вам следует обратить внимание на принципы SOLID.

 

Важно заметить, что эти принципы являются чем-то типа рекомендаций к построению архитектуры приложения, но не жестким законом. Если например вы не предполагаете дальнейшую расширяемость проекта, то вам не нужно создавать 3 уровня абстракции и придерживаться всех принципов. Но если вы будете придерживаться этих принципов, то можете уверенно называть ваш код чистым и быть уверенным, что его будет легко модифицировать и поддерживать другим программистам.

Очень рекомендую к прочтению книгу Роберта Мартина — Чистый код.

Рубрика: Программирование Java | Оставить комментарий

Объектно Ориентированное Программирование Java ООП

Давайте поговорим о том, как мы пишем код. Скорей всего все начинающие программисты вспомнят свой первый более менее сложный код, который выглядел одной портянкой в мейн методе, что напоминал больше процедурный язык (сделать сначала первое, потом второе и т.д.). Изначально мыслить в парадигме ООП непросто. Итак, что это такое? Это стиль, если так можно сказать, программирования, в котором все предствляется объектом. Точно так же как и в нашем мире все является объектом — машина, магазин, продавец, кофеварка, дата, время. И у каждого объекта есть свои свойства и функции, т.е. признаки и возможности — у машины есть цвет, марка, тип двигателя (свойства) и функции — перемещаться в пространстве, возить вещи и др.

Все выше сказанное можно написать на Java в виде простого класса:

public abstract class Car {
@NotNull
private String color;
@NotNull
private final String model;
@NotNull
@EngineType
private final String engineType;
protected Car(@NotNull String color, @NotNull String model, @NotNull @EngineType String engineType) {
this.color = color;
this.model = model;
this.engineType = engineType;
}
protected abstract void move();
public String getColor() {
return this.color;
}
public void setColor(@NotNull String color) {
if (color.length > 0) this.color = color;
}
public String getModel() {
return this.model;
}
}

И на этом простом примере (упрощенном) мы сейчас рассмотрим все 3 базовых принципа ООП: инкапсуляция, наследование, полиморфизм.

Суть инкапсуляции в том, чтобы скрывать некоторые детали от пользователя (в нашем случае пользователем будет программист, который унаследуется от этого класса). Здесь мы видим, что цвет может определиться в конструкторе и позднее переопределиться через сеттер. Но суть инкапсуляции не просто в том, чтобы сделать приватным поле и публичные геттеры и сеттеры, а в том, чтобы не определять переменную как вздумается, а наложить некоторые условия (их видим в сеттере — т.е. если передать пустую строку, то цвет машины не должен измениться).

Также мы видим, что у нас неизменяемая переменная марка автомобиля, ее необходимо определить при создании и единственное, что можно получить ее название (для пущей закрытости можно применить Defensive Copy в геттере создается новый объект и наружу отдается его свойство, а не текущего объекта).

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

Теперь вкратце поговорим про наследование. Наш класс является абстрактным, т.е. нельзя создать экземпляр абстрактной машины. Все особенности должны быть определены в наследнике. Давайте рассмотрим маленький пример.
public class MustangShelbyGt500 extends Car {
public MustangShelbyGt500(@NotNull String color) {
super(color, "MustangShelbyGt500", ENGINE_TYPE_PATROL);
}
public void move() {
// implement here
}
}

Здесь мы видим конкретный класс Мустанг Шелби, которому можно лишь определить цвет при создании, так как марка уже определена в конструкторе и тип двигателя бензиновый. Также необходимо определить абстрактный метод перемещения. Суть наследования именно в том, чтобы каждый раз не писать одни и те же вещи, такие как цвет, модель и др. Можно создать абстрактный класс Машина и в нем хранить все общие свойства и методы. Это упрощает написание кода и понимание его.  Также можно было вынести метод move() в интерфейс interface Moveable и имплементить у абстрактного класса, чтобы явно указать, что данный класс имеет свойство перемещаться и этот метод нужно определить. т.е.

public abstract class Car implements Moveable{ ... }

public interface Moveable {
void move();
}

О налседовании можно говорить долго, но думаю суть ясна. Поэтому перейдем к полиморфизму. Итак, мы определили интерфейс для передвижения. И он реализуется классом машина. Но также этот интерфейс можно применить и к другим классам, например самолет, собака, человек и все, что может иметь метод перемешаться. Единственное отличие каждого класса от другого в том, что он этот метод будет реализовывать по-своему. Например:
class Human implements Moveable {
public void move() {
System.out.println("I am a human and I'm walking");
}

class Airplane implements Moveable {
public void move() {
System.out.println("The airplane is flying");
}

class Dog implements Moveable {
public void move() {
System.out.println("The dog is running!");
}

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

class Polymorphysm {
public static void main(String[] args) {
List moveables = new ArrayList();
moveables.add(new Human());
moveables.add(new Airplane());
moveables.add(new Dog());

for (Moveable moveable : moveables) {
moveable.move()
}
}

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

I am a human and I'm walking
The airplane is flying
The dog is running!

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

p.s. в данной статье не были освещены все детали ООП, но на мой взгляд, важные аспекты были рассмотрены

Рубрика: Программирование Java | Оставить комментарий

Виды ссылок в Java

Мы немало уже говорили про Garbage Collector и про то, как именно он работает.

Теперь давайте рассмотрим какие бывают ссылки в языке Java и чем они отличаются друг от друга и как их обрабатывает Сборщик Мусора.

Для ссылок есть абстрактный класс Reference<T>, от которого наследуются остальные типы ссылок (не все).

Итак, есть 4 типа ссылок

  1. Сильная ссылка (Strong Reference)
  2. Слабая ссылка (Weak Reference)
  3. Мягкая ссылка (Soft Reference)
  4. Фантомная ссылка (Phantom Reference)

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

  1. Сильная ссылка. Она создается каждый раз, когда аллоцируем место в памяти через оператор new.

Итак, в данном примере создается некий объект, сохраняется по сильной ссылке в переменную. После чего переменная уже указывает на null. Значит уже на второй строке объект SimpleObject(123) доступен для сборщика мусора, так как на него нет ни одной явной ссылки. Здесь нужно понимать, что simpleObject и есть ссылка, которая при создании указывала на объект с айди 123. После обнуления сама ссылка уже хранит null и доступна для сборщика мусора.

2. Слабая ссылка.

Несколько фактов.

  • Этот тип ссылок используется как ключ в WeakHashMap чтобы обращаться к объектам.
  • Если JVM обнаружила объект только со слабой ссылкой (т.е. нет ни сильной ни мягкой ссылки) то объект будет помечен для сборщика мусора. (см. статью)
  • Для создания слабой ссылки используется класс java.lang.ref.WeakReference

Давайте напишем маленький пример использования.

Сначала создаем объект по сильной ссылке, вызываем у него метод вывода данных в консоль. После чего создаем слабую ссылку от нашей сильной ссылки. После чего обнуляем сильную ссылку. Теперь SimpleObject(123) доступен для сборщика мусора (но будет собран только если JVM понадобится память). Но, так как мы обернули в слабую ссылку, то можем теперь присвоить первой ссылке объект через метод get() у слабой ссылки. После чего проверяем, что она не пуста (на экране предупреждение о NullPointerException).

Итак, в чем суть? Что мы создали слабую связь с объектом и можем обращаться к нему даже после того, как сильная ссылка обнулилась. Грубо говоря, пока у нас есть слабая ссылка, мы можем вернуть наш объект.

Область применения слабой ссылки. Можно использовать для кратковременного сохранения данных (кэш).

3. Мягкая ссылка.

Посмотрим исходники и прочитаем документацию.

Мягкие ссылки используются для создания кэшей. Так как они подчищаются тогда, когда JVM остро нуждается в памяти. Все мягкие ссылки гарантированы будут подчищены перед тем как бросить OutOfMemoryError (см. пример).

В самом классе наблюдаем 2 переменных: clock & timestamp и как говорит документация, первую обновляет сборщик мусора, а вторая нужна для обновления при каждом вызове метода get( ). JVM может использовать второе поле при пометке объекта для сбора, но это не точно 🙂

Давайте рассмотрим тот же пример что и со слабой ссылкой, но с мягкой.

Пройдемся дебагером и заметим, что в referent мягкой ссылки мы имеем тот же самый объект, что и в сильной ссылке.

4. Фантомная ссылка.

В чем отличие фантомной ссылки от остальных слабых ссылок — в том, что она хоть и также доступна для сборщика мусора, но в отличие от остальных типов ссылок JVM кладет фантомные ссылки в очередь ссылок (reference queue). Они кладутся в очередь после того, как метод finalize( ) был вызван.

Самое интересное, что получить объект нельзя как в случае с мягкой ссылкой или слабой, так как здесь метод get( ) возвращает null. Тогда каково предназначение фантомной ссылки? Хранить объект, но не давать сборщику мусора уничтожить его?

Более подробно о фантомной ссылке можете прочитать здесь. Позже распишем свои примеры.

 

 

Рубрика: Программирование Java | Оставить комментарий

OutOfMemory Пример

В предыдущей статье мы говорили про память в Java и сборщик мусора.

В этой статье мы посмотрим на реальном примере как забивается память и приложение выбрасывает исключение OutOfMemory.

Для этого создадим простой класс с 2 полями

Далее создадим поток (HeavyThread), в котором будем миллион раз добавлять в коллекцию новый объект, также выводить в консоль информацию сколько раз мы закончили процесс успешно. Так как переменные objectList и count являются глобальными и статическими, то сборщик мусора не доберется до них до окончания программы.

Далее в главном методе будем создавать 100 потоков и стартовать их, по окончанию выведем в консоль информацию о размере списка объектов и количестве потоков (спойлер, мы этого не увидим). Переменная threads также будет недоступна для сборщика мусора, так как используется после цикла в 100 повторов. Итак, в памяти джава машины будут перманентно находиться несколько переменных, размер которых будет постепенно увеличиваться (objectList, threads, count).

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

Как видим всего 9 потоков успели завершиться перед тем как память закончилась. Т.е. в этот миг сборщик мусора попытался подчистить память, но, увы, нечего было подчищать, так как все переменные использовались. Можно конечно же поставить try catch finally чтобы все же узнать при каком количестве элементов выбросилось исключение. Для этого добавим конструкцию в код и запустим.

Теперь мы видим, что на 11-ом потоке бросилось исключение, при этом было создано и сохранено в переменную 10 149 835 более десяти миллионов объектов (все зависит от оперативной памяти вашей машины). Поэтому в приложениях нужно быть осторожным с использованием статичных объектов и избегать подобной ситуации (утечка памяти), которая приводит к ошибке OutOfMemory.

Рубрика: Программирование Java | Оставить комментарий