Программирование

Читай код, cука

июл 29, 21:06

Как-то недавно я наткнулся на статью под названием Читай код, где автор доказывал важность такого скилла, как умение читать чужой код:

Создать свою структуру и пришлепать ее сбоку может любой дурак. Квалифицированный инженер-программист (с упором на первом слове, не путать с “программером”) умеет проводить анализ “чужой” подсистемы, восстановит мысль и идею автора, сможет мысль автора развить, продолжить ее, и эффективно решить свою задачу в рамках чужого подхода к проблеме. Все это – работая с кодом. Это отличительная компетенция архитектора, высший уровень инженерного мастерства. И это имеет весьма отдаленное отношение к “рефакторингу”.

Тогда у меня возникла идея провести эксперемент:

  1. Взять некоторый известный паттерн проектирования.
  2. C одной стороны показать, как выглядит код до применения паттерна.
  3. Рядом же, с другой стороны, показать как выглядит этот же код с применением паттерна.
  4. Попытаться понять, что этот код делает и как применяется паттерн.
  5. Сравнить такой вариант описания паттернов с традиционным подходом “UML-диаграмма, словесное объяснение, пример использования”.

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

Итак, сам эксперимент. Для описания я выбрал паттерн Декоратор.

Традиционный подход

Декоратор, Decorator — структурный шаблон проектирования, предназначенный для динамического подключения дополнительного поведения к объекту. Шаблон Декоратор предоставляет гибкую альтернативу практике создания подклассов с целью расширения функциональности.

Задача

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

Реализация

Создается абстрактный класс, представляющий как исходный класс, так и новые, добавляемые в класс функции. В классах-декораторах новые функции вызываются в требуемой последовательности — до или после вызова последующего объекта.

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

UML-диаграмма

Описание паттерна взято из Википедии. А теперь перейдем ко второму способу объяснения.

Подход «Читай код, сука»

Итак, Вопрос

Что лучше? Что легче для понимания? Подход «Читай код, сука» сработал или нет?

Комментарии [10]

---

MIT's Introduction to Algorithms

июл 25, 22:01

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

Прежде всего, нужно определится зачем эти лекции смотреть, какова цель. Тут все просто. Если вспомнить Programmer Competency Matrix, то выяснится, что алгоритмы – это один из тех скиллов, которые востребованы. Интересная цитата оттуда:

Working with someone who has a good topcoder ranking would be an unbelievable piece of luck!

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

Итак, что касается процесса как смотреть эти лекции с большой для себе пользы, то тут дела обстоят так:

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

Книга переведена на русский язык и ее даже можно скачать с торрентов или натахауса. Называется «Алгоритмы. Построение и анализ» Томаса Кормена, Чарльза Лейзерсона и других. Выглядит она следующим образом:

Замечательно в этой книге то, что профессор Чарльз Лейзерсон, который читает лекции, есть один из ее соавторов. Да и сама книга – одна из самых популярный по алгоритмам. Кстати, второй профессор Eric Damaine – живой гений, самый молодой профессор MIT, – стал им в 20 лет.

Без лекций книгу читать сложновато и немного скучно, но после просмотра – идет на ура. В общем, советую.

Комментарии [1]

---

How to Design a Good API по мотивам презентации Джошуа Блоха (продолжение)

июл 19, 15:15

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

Для начала вспомним, как выглядит процесс разработки API по Блоху. Он состоит условно из трех этапов: сбор сведений, краткая спецификация и программирование. На последнем этапе должен получится прототип API, некоторый каркас будущей программы, который состоит из интерфейсов, прототипов классов и методов с комментариями и юнит-тестами.

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

Следующий шаг, он же последний, – реализация. Здесь мы создаем реальный код. В оставшейся части презентации Джошуа Блох дает набор общих принципов по проектированию в целом. Ниже я привел список наиболее существенных, по-моему мнению. Все они сформулированы кратко, но очень точно.

Если это трудно назвать, то это плохой знак

Или, другими словами, Good Names Drive Development. Под этим понимается, прежде всего, что API должно выполнять одну функцию и делать это хорошо. Хороший признак такой модульности – это понятные и простые имена для классов и методов. Если возникли проблемы с именами, возможно следует каким-либо образом применить композицию или декомпозицию для того, чтобы функции, которые выполняет разрабатываемый модуль, были легко понятны и их можно было бы дать подходящее имена.

Если сомневаешься, оставь

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

Хороший совет в случае, когда вы решаете, стоит ли добавить новый параметр в метод или стоит выделить новый метод, или сделать еще один класс – если сомневаешься, бросай. Не стоит делать этого сейчас. Лучше сделать это потом, когда реальность сама рассеет все сомнении. Добавлять легче, чем изменять.

Документируй дотошно

Одна из прописных истин, но все же часта про нее забывают. Любой самый совершенный дизайн, который, кстати, не так часто и увидишь, не может обойтись без документации. Следует документировать каждый класс, интерфейс, метод, конструктор, параметр и исключение.

Не заставляй клиента делать то, что может сделать твоя программа

Хороший пример того, что под понимается, приводит Джошуа Блох, описывая дизайн DOM API:


import org.w3c.dom.*;
import  java.io.*;
import  javax.xml.transform.*;
import  javax.xml.transform.dom.*;
import  javax.xml.transform.stream.*;
// DOM code to write an XML document to a specified output stream.
private static final void writeDoc(Document doc, OutputStream out)throws IOException{
  try {
    Transformer t = TransformerFactory.newInstance().newTransformer();
    t.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM, doc.getDoctype().getSystemId());
    t.transform(new DOMSource(doc), new StreamResult(out));
  } catch(TransformerException e) {
    throw new AssertionError(e); // Can’t happen!
  }
}

Метод writeDoc() пишет XML документ в заданный поток вывода. Такая простоя вещь как запись документа в поток заняла 15 строк кода, большая часть из которых вообще непонятного предназначения: исключения, которые не могут возникнуть, создание дополнительного объекта Transformer и так далее.

Общее правило в этом случае – уменьшайте код, который нужно будет писать клиенту для использования вашего API. Клиенту не нужно знать, какие дополнительные действия нужно совершить прежде, чем вызвать тот или иной метод, какие исключения нужно отлавливать, какие параметры установить. Проблемы такого рода решается, в основном, с помощью copy-paste, что само по себе есть неприятно, скучно и приводит к ошибкам.

Совершенства не достичь, но можно попытаться

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

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

Комментарии

---

Java Diff Utills: первый релиз

июл 17, 16:19

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

Screenshot from Google Code

Лицензия GNU. Cкачать можно отсюда.

Понравился Google Code. Особенно понравилась возможность добавить счетчик Google Analytics и тут же следить за посещаемостью. Приятно удивило, что уже и без первого релиза народ начал приходить.

В общем, сделал доброе дело. Дальше – больше :)

Комментарии

---

Презентация: Про Dependency Injection и Inversion Of Control

июл 17, 11:22

Решил выложить слайды с доклада, которою я делал для студенческой конференции. Старался сделать веселую и интересную презентацию. Тема – управление зависимостями, dependency injection и inversion of control. Доклад обзорный.

View more presentations from iobit.

Комментарии

---

How to Design a Good API по мотивам презентации Джошуа Блоха

июл 12, 19:49

Оригинальная презентация находится здесь. В своей презентации Джошуа Блох, один из дизайнеров Java libraries, дает советы о том, как создать хороший API, приводя большое количество примеров удачного и неудачного дизайна.

Мое вольное трактование этой презентации находится ниже. Здесь я попытался в удобной форме показать и рассказать, как эти советы воплощаются в жизнь. Такой себе reald-world example from scratch.

Для примера я выбрал разработку java-библиотеки – аналога diff-утилиты с возможностью применять патчи. Главная причина такого выбора – отсутствие удобного API для решения подобных задач на Java. Существующие программы (diff-match-patch от Гугла, java-diff, jrcs-diff) хотя и обладают некоторыми достоинствами, но чаще всего неудобны в использовании. Говорю это, опираясь на свой личный опыт.

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

Первый шаг: Сбор сведений

На этом этапе главная задача разработчика – собрать реальные требования к разрабатываемой системе. Не стоит углубляться в технические детали и фокусироваться на том, как должна работать система. Нас не интересует как она работает. Нам интересно, что именно она делает, какие задачи выполняет и для чего эта система нужна в принципе. It’s no about an activity, it’s about the result.

Джошуа Блох советует выделить такие требования в формате use cases (примеров использования) и наиболее общем виде. Такие use cases представляют из себя краткие описание задач, которые необходимо выполнять с помощью API.

Вернемся к примеру diff-библиотеки. Прежде всего у меня есть собственные требование на основе тех задач, которыми я планирую выполнять с помощью этой бибилотеки. Также уже есть и существующие библиотеки, со своим API и примерами использования. Подумав и подключив фантазию я остановился на следующем списке:

  • Вычисления diff между двумя текстами
  • Применения патча, который содержит diff, к оригинальном тексту
  • Операция обратная к патчу, когда по пропатченному тексту и патчу вычисляется оригинальный текст (я этим не пользовался, но вдруг такая возможность будет необходима)
  • Вывод diffов в разные формы для дальнейшего, более удобного просмотра (двух-панельный вид, отображение изменений внутристрочно).
  • Возможность использовать разные форматы для diff (unified diff, normal format и т.д.)

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

Для примера, мы можем предположить, что алгоритм вычисления diff возможно будет меняться в зависимости от многих факторов : обращать внимания на пробелы или нет, игнорировать пустые строки или считать их изменением, какую именно реализацию алгоритма стоит использовать (некоторые алгоритмы подходят для некоторых текстов лучше, чем другие) и так далее. Другой пример – это обработки разных форматов для диффа (unified diff, normal format и т.д.). В большинстве случаев будет использовать unified diff, как наиболее распространенный, но возможность других форматов нужно также брать во внимание.

Второй шаг: Написание краткой спецификации

Здесь главное не испугаться слова “спецификация”. Никаких 50 страниц и 2-недельного корпения. Все что необходимо – это одна страничка формата А4. Краткость является огромным плюсом, потому что спецификацию в одну страницу намного легче исправлять и даже переделывать заново.

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

Итак, мой пример спецификация для diff-библиотеки, написанный на некотором псевдо-коде, который я придумал сам. Надеюсь, будет понятно:


DiffRow:
  String tag;
  String oldLine;
  String newLine;
---------------------
Chunk:
  int position;
  int size;
  List<String> changedLines;
---------------------
Delta:
  Chunk original;
  Chunk revised;
  applyTo(target)
---------------------
AddDelta extends Delta:
  applyTo(target):
    go to line with position  original.position
    insert lines from revised.changedLines
---------------------
DeleteDelta extends Delta:
  applyTo(target):
    go to line with position  original.position
    delete n lines, where n = original.size
---------------------
ChangedDelta extends Delta:
  applyTo(target):
    go to line with position == original.position
    delete n lines, where n = original.size
    insert n lines from revised.changedLines
---------------------
Patch:
  List<Delta> deltas;
  Patch(String unifiedDiff);
  Patch(String rcsDiff);
  Patch()
  addDelta()
  getDeltas()
  applyTo(target);
---------------------
DiffLib:
  diff(String original, String revised, DiffAlgorytm algorytm): Patch
  patch(String original, Patch patch): String revised
  original(String revised, Patch patch): String original
  getDiffRows(String original, String revised)

Основным кирпичиком diff файла является Delta. Дельта описывает какие именно изменения произошли (Insert, Add или Change). Дельта состоит из 2 Сhunk – один Chunk описывает оригинальный файл, другой – измененный. Chunk содержит описание того, начиная с какой строки произошли изменения и какие именно. Патч состоит из набора дельт. На выходе операции diff() получается патч. Патч можно применить к тексту, точно также как и любую дельту.

Третий шаг: Начинаем программировать API

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

При написании прототипов, кстати, можно убить 2 зайцев.

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

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

Пока все

Дальше – больше

Комментарии [2]

---

Загадка по Java № 1 (boxing и unboxing)

фев 28, 21:43

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

Прелюдия

Есть код. Нужно ответить, что он выведет.


Integer a = 200;
Integer b = 200;
System.out.println(a == b);

Здесь все просто. Выведет False. Для тех, кто в танке и читают книжки невнимательно, объясню.

Ява сравнивает примитивные типы (byte, short, int, long и т.д.) и объектные (Byte, Short, Integer и Long, соответственно) по разному. У примитивных типов проверяется их значение. У объектных типов проверяется указывают ли они на один и тот же объект. А так как мы создали два разных объекта (пусть даже и с одинаковыми значениями), проверка возвращает False.

Завязка

Теперь немного изменим наш код. Всего лишь уберем по паре нулей у целых чисел. Что выведет?


Integer a = 2;
Integer b = 2;
System.out.println(a == b);

Не буду томить с ответом, выведет — True. Опять же — почему? Чтобы ответить на этот вопрос нужно знать как устроена Ява, а точнее JVM.

Устроена JVM так, как ни странно, что любит оптимизировать и кешировать. По-этому для нужд производительности виртуальная машина кеширует объекты для часто встречающихся примитивов (чисел от -127 до 127, некоторых символов, true и false). Другими словами, когда мы пишем:


Integer a = 2;

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

Итог

Мораль этой истории проста — избегать сравнения объектных типов, соответствующим стандартным примитивам. Лучше приводить их вручную через type casting, а потому уже сравнивать их значения. Так кошернее.

Комментарии

---

Небольшое введение в Ant

фев 9, 16:47

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

Что мы узнаем?

  • Что такое Ant и для чего он нужен.
  • Как написать простой build.xml файл.
  • Как скомпилировать с помощью Ant проект.
  • Как подключить библиотеки при компиляции.

Про Ant двумя словами

Ant – это утилита для сборки java-программ. Это ее основное назначение. Она использует инструкции, определенными в файле build.xml, для того, чтобы собирать программу (компилировать, копировать куда надо и так далее). В Unix аналогом такой программы является команда make и написанный для нее makefile. В общем-то, здесь можно провести такую аналогию:

Естественно знать, что такое make и makefile необязательно. Но с этого с этого можно сделать вывод, что понимая суть ant и build.xml вам уже не нужно объяснять, что такое make и makefile. И в обратную сторону, зная, что такой make и makefile, вам не нужно объяснять, для чего нужен ant и build.xml. Синтаксис у них совершенно разный, и по-этому такое понимание практических навыков вам никаких не даст, но все-таки это лучше, чем ничего. Но сейчас не об этом.

Для тех, кто не сталкивался ни с make, ни с ant мы уже сказали, что Ant – собирает программу. А как именно, определенно в файле build.xml. И этого вполне достаточно, чтобы начать.

h2 Написание первого, простого build.xml и компиляция нашей программы

Синтаксис build.xml вполне прост. Вот так выглядит обычный build файл:


<project name="My Sample Application" default="compile" basedir=".">
   <target name="target" description="Some text here">
   </target>
</project>

Один проект — один build.xml. При объявлении проекта мы должны указать его имя (name), команду, выполняемую по умолчанию (default) и папку, относительно которой высчитываются относительные пути (basedir). Проект может состоять из нескольких команд (Target). Каждый Target может состоять из нескольких действий (Actions).

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

Напишем Target для компиляции. Взяли клавиатуру, и написали. И вот, что получилось:


<project name="My Sample Application" default="compile" basedir=".">
    <target name="compile">
	<javac destdir="./build/classes" srcdir="./src">
	</javac>
    </target>
</project>

Здесь мы объявили Target с именем compile. Внутри него находится действие javac, которое принимает как параметры папку с исходными текстами, и папку в которую будет записан результат в виде .class файлов.

Для того, чтобы вызвать наше действие следует выполнить ant в командой строке. При вызове ant мы можем передать какой build файл использовать, и какую команду следует выполнить. Если этого не сделать, то по умолчанию ant будет использовать build.xml, который находится в текущей папке и Target по умолчанию, указанный в нем.

Т.е. нам достаточно написать:

:/path/to/project$ ant

или

:/path/to/project$ ant compile

или

:/path/to/project$ ant -buildfile build.xml compile

Подключаем дополнительные библиотеки

Сегодня уже вряд ли найдешь сколько-нибудь сложное приложение, которое бы не использовало сторонние библиотеки. Мы можем указать Ant какие библиотеки стоит использовать следующим образом:


<path id="libs.dir">
	<fileset dir="lib">
		<include name="*.jar" />
	</fileset>
</path>
<target name="compile">
	<javac destdir="./build/classes" srcdir="${src.dir}">
		<classpath refid="libs.dir" />
	</javac>
</target>

Строка <classpath refid=“libs.dir” /> указывает, что необходимо подключить дополнительные библиотеки и что находятся они по адресу libs.dir. Чуть выше мы определили, что такое это самое libs.dir.

Здесь следует сделать отступление и сказать, что в Ant присутствует некоторая концепция типов данных. Подробнее о них стоит рассказать в отдельной статье, но пока стоит запомнить, что такие вещи как путь, списки файлов, упорядоченные или неупорядоченные, – все есть некоторый объект со своим типом, который можно использовать в действиях. Одним из примеров таких типов есть path, внутри которого используется другой тип fileset для представления списка файлов.

Делаем красиво. Выносим свойства за скобки

В прошлых примерах мы использовали путь ./build/classes для указания папки, в которую мы хотим скомпилировать наши классы. В больших build.xml файлах, в которых содержаться намного больше разных Targets велика вероятность, что эта же строка понадобиться нам еще где-либо.

По-этому хорошей практикой считается определение таких параметров отдельно от самих Targets. Приведу весь build.xml для примера:


<project name="My Sample Application" default="compile" basedir=".">
    <property name="build.classes.dir" location="./build/classes" />
    <property name="src.dir" location="./src" />
    <path id="libs.dir">
	<fileset dir="lib">
		<include name="*.jar" />
	</fileset>
    </path>
    <target name="compile">
	<javac destdir="${build.classes.dir}" srcdir="${src.dir}">
		<classpath refid="libs.dir" />
	</javac>
    </target>
</project>

Здесь есть еще один тонкий момент. Значения свойства могут быть присвоены как с помощью location атрибута, так и с помощью value. Разница между ними в том, что если location содержит путь к папке или файлу, разделитель между папками (/ или \) будет подставляться правильно в зависимости от того, под Виндовс или под Линуксом запущен Ant. Это просто удобно. Не придется при случае переименовывать все пути, как однажды пришлось мне на своей работе. Хотя вроде бы умные люди писали, а такой мелочи, наверное, не знали.

Заключение

То, что я тут рассказал хватит, чтобы делать 60-70% основной работы. Конечно, лучше знать больше. Возможно, в следующей статье расскажу подробнее на тему типов данных (path, fileset и так далее) и все, что с ними связано. Еще хотелось бы рассказать и про автоматизацию тестов с помощью Ant. Но время покажет как все получится.

Комментарии

---

Android - Работа с базой данных (часть 2)

ноя 8, 21:01

Продолжаем продолжать

Итак, в последний раз мы прошли такую себе мини-лекцию по базам. Сегодня время пришло заняться практикой.

Начнем с первого пункта:

Создание базы данных

В Андроиде я насчитал 3 способа создания базы данных. Начнем с самого неправильно, и закончим самым православным.

1 способ – использовать класс SQLiteDatabase. Для начала сделаем небольшой обзор этого класса, так как в будущем он нам ой как пригодится.

Иерархия:


java.lang.Object
	android.database.sqlite.SQLiteClosable
		android.database.sqlite.SQLiteDatabase

Предков всего ничего: стандартный явовский Object и интерфейс SQLiteClosable. Последний мы вряд ли мы когда-нибудь будем использовать — это всего лишь внутренний интерфейс, которой исходя из названия содержит в себе методы, необходимые для закрытие (удаления) SQLite классов. В данном случае его наследует наш SQLiteDatabase, который следовательно представляет из себя ресурс, который можно прикрыть, если что)

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

  • void execSQL(String sql) — выполнение sql-запроса, который не является выборкой (т.е. без select). Это всякие CREATE TABLE, INSERT, DELETE и так далее.
  • Cursor query(String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy, String limit) — большой и важный метод с кучей параметров. Используем для выборки. Параметры сами за себя говорят. Подробнее обсудим позже
  • Cursor rawQuery(String sql, String[] selectionArgs) — сырой, обычный, дикий sql-запрос. Очень полезно иметь возможность обратиться к базе, используя всякие хитрые Join’ы и так далее.

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

  • insert() – возвращает id вставленной строки
  • update() – возвращает количество обновленных строк
  • replace() – не часто встречал такой метод в других API. Нужно поковыряться еще. Замена.
  • delete() – возвращает количество удаленных строк

Дальше пойдут методы, которые возвращают ответ «Да» или «Нет»:

  • isOpen() – true, если база была не закрыта
  • isReadOnly() – true, если база была открыта только для чтения

Методы для работы с транзакциями:

  • beginTransaction() – начало
  • endTransaction() – конец
  • inTransaction() – true, если открыта транзакция
  • setTransactionSuccessful() – только после вызова этого метода транзакция будет закомичена.

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

Как я и говорил, существует 3 способа это сделать. И одним из этим способов является метод openDatabase(). Вот его полное описание:


static SQLiteDatabase openDatabase(String path, SQLiteDatabase.CursorFactory factory, int flags)

где:

  • path — путь к файлу базы данных. Путь для любой базы данных имеет вид: «/data/data/<package_name>/databases. <package_name> – имя пакета, которое вы указывали при создании проекта. Что-то вроде com.google.android.maps. Ну, или com.example.mysmallapp
  • factory — хз, что такое. По умолчанию стоит null — пусть так и стоит, пока не будет трогать.
  • flags — флаги или константы, определенные внутри класса SQLiteDatabase. Их всего четыре: CREATE_IF_NECESSARY (создавать базу, если это нужно), OPEN_READONLY (открыть только для чтения), OPEN_READWRITE (читать и писать), NO_LOCALIZED_COLLATORS (открыть без поддержки локализации)

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


SQLiteDatabase mDatabase = SQLiteDatabase.openDatabase("/data/data/com.example.app/databases/my.db", null, SQLiteDatabase.CREATE_IF_NECESSARY);

Все. Таким образом, мы получили новую, чистую базу данных для дальнейшей работы. Так как флаг SQLiteDatabase.CREATE_IF_NECESSARY пишут в конце очень часто при открытии, разработчики Андроида решили предложить метод openOrCreateDatabase() для краткости:


SQLiteDatabase mDatabase = SQLiteDatabase.openOrCreateDatabase("/data/data/com.example.app/databases/my.db", null);

Так даже удобнее. Но это только первый способ.

2 способ — это метод openOrCreateDatabase() в классе Context. О классе Context особо поговорим потом. Сейчас остановимся но том, что он представляет объект для общения с ОС Андроида.

Если вам доступен объект типа Context, то очень рекомендуется использовать его метод openOrCreateDatabase(). Почему? Для начала разберем его поподробнее:


SQLiteDatabase openOrCreateDatabase(String name, int mode, SQLiteDatabase.CursorFactory factory)

где

  • name — имя базы данных, уникальное в рамках приложения
  • mode — константа, для указания режима (MODE_PRIVATE или 0 – по умолчанию, MODE_WORLD_READABLE для чтения, MODE_WORLD_WRITEABLE для записи)
  • factory — пока не трогаем, ставим null

Возвращает этот метод объект типа SQLiteDatabase.

Какие преимущества у такого способа? Оно одно — нам не нужно указывать полный путь к файлу базы данных. Достаточно только ее имени. Так как путь к приложению мы уже знаем исходя из данных, которые предоставляет Context.

Для полноты картины разберем еще несколько методов класса Context для работы с базами данных:

  • boolean deleteDatabase(String name) — удаляем базу по имени. True, если завершилось успешно.
  • File getDatabasePath(String name) — возвращает абсолютный путь по имени базы

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

3 способ — использовать SQLiteOpenHelper.

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

Иерархия:


java.lang.Object
	android.database.sqlite.SQLiteOpenHelper

Здесь все. Также все просто и с методами. Их всего 6. 2 из них — абстрактные, и их следует переопределять в потомках. Перечислим первые 4:

  • void close() – закрыть все открытые соединения
  • SQLiteDatabase getReadableDatabase() – возвращает базу для чтения
  • SQLiteDatabase getWritableDatabase() – база для чтения и записи
  • onOpen(SQLiteDatabase db) — вызывается после того, как база будет открыта

И 2 абстрактных метода:

  • onCreate(SQLiteDatabase db) — вызывается после того, как база создана в первый раз. Здесь мы создаем необходимые таблицы. И если нужно, заносим в них какие-либо начальные данные.
  • onUpgrade(SQLiteDatabase db) — вызывается, когда базе нужен апгрейд. Здесь мы удаляем таблицы, создаем новые, добавляем поля и так далее.

Ну, и конструктор:


SQLiteOpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version)

где

  • context — контекст приложения. Именно через его метод openOrCreateDatabase() и работает SQLiteOpenHelper
  • name — имя базы
  • factory — говорили уже
  • version — версия базы. Используется, когда схему базы данных нужно обновить.

Приведем пример наследования и использования SQLiteOpenHelper:


class DatabaseHelper extends SQLiteOpenHelper {
  private static final String DATABASE_NAME = "data";
  private static final String DATABASE_CREATE = "create table test_table (_id integer primary key autoincrement, some_test text not null);";
DatabaseHelper(Context context) {
  super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
  db.execSQL(DATABASE_CREATE);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
  db.execSQL("DROP TABLE IF EXISTS notes");
  onCreate(db);
}
}

Здесь мы использовали паттерн Шаблон для инкапсуляции внутренних данных. Для использования базы теперь нужно лишь вызвать getReadableDatabase() или getWriteableDatabase() метод. Т.е.:


DatabaseHelper mDbHelper = new DatabaseHelper(mCtx);
SQLiteDatabase mDb = mDbHelper.getWritableDatabase();

И мы получим нашу любимую базу. Все очень просто и лаконично смотриться.

Конец второй части

На этом пока остановимся. Дальше разберем, как же все таки работать со всеми этими методами — insert(), update(), replace(), delete() и тому прочее. Постараюсь этому как-нибудь выделить время. Пока всем.

Комментарии [2]

---

Android - Работа с базой данных (часть 1)

ноя 7, 18:22

В начале было слово

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

У меня нет никакой четкой и выверенной программы обучения. Не никакого плана: сначала это, потом это, потом то. Единственный мой план — это собственная интуиция. В общем-то, как бог на душу положит одним словом.

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

Цели

  1. Понять, как устроена работа с базой данных в самом широком смысле
  2. Какие эффективные практики следует применять при работе?
  3. Какие классы и объекты чаще всего используются? Какие методы следует знать, как свои пять пальцев?
  4. Как база данных связана со остальными частями приложения? Где создается, где вызывается?

Начало

Базы данных имеют больше значение в программировании хотя бы по тому, что каждый из нас с ними сталкивался (или столкнется). В Андроиде реализована поддержка баз данных на уровне каждого приложения. Т.е. создать и работать с базой данных внутри приложения настолько естественно в Андроиде, как дышать воздухом. По умолчанию доступен интерфейс для работы с базами данных SQLite. Поддержки никаких других СУБД мною замечено не было, значит за основу берем единственно доступный SQLite.

Ради справедливости стоит отметить, что сторонние разработчики предлагают свои продукты, как альтернативу SQLite. Это db4object и mcobject.

Но о них мы поговорим, как-нибудь потом. Сейчас для этого не время.

Небольшое введение (SQLite)

SQLite — библиотека, которая позволяет создавать база данных, который обладают рядом приятных и уникальных особенностей. Во-первых – это независимость. Под независимостью я понимаю минимальный объем сторонних библиотек и функций, которые ей нужны. Создатели говорят, что SQLite можно скомпилировать любым С компилятором и с использованием только таких библиотечных функций, как:

  • memset()
  • memcpy()
  • memcmp()
  • strcmp()
  • malloc()
  • free()
  • realloc()

Это позволяет использовать эту библиотеку в разнообразных мобильных устройствах, mp3-плеерах, да и просто в любом месте, где требуется компактная база данных.

Во-вторых, в SQLite нет никакого серверного процесса. База данных хранится в файле. И весь доступ к ней осуществляется, как к файлу. Следовательно ничего не надо устанавливать. Нет никакого процесса установки. Ты просто подключил и используешь.

В-третьих, все запросы являются транзакциями, что обеспечивает такие свойства, как Атомарность, Непротиворечивость, Изоляцию и Долговечность (ACID)

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

Конечно, свойства SQLite откладывают свой отпечаток на работу с ней. Так, например, открытие доступа к базе может потребовать создание нового файла и так далее. Но API Андроида делает большинство работы за нас. И многом мы можем и не заботиться, как убедимся позже.

Продолжение введения (паттерн Адаптер и Шаблон