Путівник по db4o для Java-розробника: cтруктурірованние об’єкти і колекції, Інші СУБД, Бази даних, статті

 


До цього моменту я весь час використовував клас Person для показу всіх основних принципів роботи з db4o. Ви навчилися створювати графи об’єктів типу Person, Запитувати і отримувати графи з вельми високим ступенем гнучкості (використовуючи запити db4o для накладення умов на запитувані графи), а також їх модифікувати і видаляти (правда, з деякими обмеженнями). Фактично єдине, що залишилося неохопленим в попередніх випусках – це спадкування.


До сих пір я розробляв тільки клас Person незважаючи на те що кінцевою метою моїх прикладів було створення системи зберігання даних про співробітників. Ймовірно, система повинна надавати можливості зберігання інформації не тільки про співробітників, але також про членів їх сімей, але поки що все це просто об’єкти типу Person. Висловлюючись технічно, кожен об’єкт типу Person також є співробітником (об’єктом типу Employee), Але не навпаки. До того ж, клас Employee може надавати більш специфічні функції, ніж Person. Таким чином, з точки зору розробки об’єктно-орієнтованої архітектури використання спадкування є ключовою можливістю при проектуванні системи типів.

Звичайно, можна розрізняти співробітників і всіх інших, просто використовуючи додаткове поле класу Person, Але цей реляційний підхід погано вписується в об’єктно-орієнтовану систему. На щастя, як і в багатьох ООСУБД, спадкування є вбудованою можливістю db4o. Завдяки цьому можна з дивовижною легкістю вносити зміни в існуючі програми, використовуючи всі переваги спадкування при проектуванні архітектури, але уникаючи при цьому багатьох труднощів при написанні запитів. Як буде показано нижче, спрощення запитів для вибірки об’єктів певного типу є одним з переваг успадкування.


Останній варіант класу Person


Лістинг 1 може бути корисний тим з вас, хто почав читати серію тільки з цієї статті. На ньому наведена остання версія класу Person, Що розробляється з самого початку серії.


Лістинг 1. На чому ми зупинилися …





package com.tedneward.model;
import java.util.List;
import java.util.ArrayList;
import java.util.Iterator;
public class Person
{
public Person()
{ }
public Person(String firstName, String lastName, Gender gender, int age, Mood mood)
{
this.firstName = firstName;
this.lastName = lastName;
this.gender = gender;
this.age = age;
this.mood = mood;
}

public String getFirstName() { return firstName; }
public void setFirstName(String value) { firstName = value; }

public String getLastName() { return lastName; }
public void setLastName(String value) { lastName = value; }
public Gender getGender() { return gender; }

public int getAge() { return age; }
public void setAge(int value) { age = value; }

public Mood getMood() { return mood; }
public void setMood(Mood value) { mood = value; }
public Person getSpouse() { return spouse; }
public void setSpouse(Person value) {
// A few business rules
if (spouse != null)
throw new IllegalArgumentException(“Already married!”);

if (value.getSpouse() != null && value.getSpouse() != this)
throw new IllegalArgumentException(“Already married!”);

spouse = value;

// Highly sexist business rule
if (gender == Gender.FEMALE)
this.setLastName(value.getLastName());
// Make marriage reflexive, if it”s not already set that way
if (value.getSpouse() != this)
value.setSpouse(this);
}
public Address getHomeAddress() { return addresses[0]; }
public void setHomeAddress(Address value) { addresses[0] = value; }
public Address getWorkAddress() { return addresses[1]; }
public void setWorkAddress(Address value) { addresses[1] = value; }
public Address getVacationAddress() { return addresses[2]; }
public void setVacationAddress(Address value) { addresses[2] = value; }
public Iterator<Person> getChildren() { return children.iterator(); }
public Person haveBaby(String name, Gender gender) {
// Business rule
if (this.gender.equals(Gender.MALE))
throw new UnsupportedOperationException(“Biological impossibility!”);

// Another highly objectionable business rule
if (getSpouse() == null)
throw new UnsupportedOperationException(“Ethical impossibility!”);
// Welcome to the world, little one!
Person child = new Person(name, this.lastName, gender, 0, Mood.CRANKY);
// Well, wouldn”t YOU be cranky if you”d just been pushed out of
// a nice warm place?!?
// These are your parents…
child.father = this.getSpouse();
child.mother = this;

// … and you”re their new baby.
// (Everybody say “Awwww….”)
children.add(child);
this.getSpouse().children.add(child);
return child;
}

public Person getFather() { return this.father; }
public Person getMother() { return this.mother; }

public String toString()
{
return
“[Person: ” +
“firstName = ” + firstName + ” ” +
“lastName = ” + lastName + ” ” +
“gender = ” + gender + ” ” +
“age = ” + age + ” ” +
“mood = ” + mood + ” ” +
(spouse != null ? “spouse = ” + spouse.getFirstName() + ” ” : “”) +
“]”;
}

public boolean equals(Object rhs)
{
if (rhs == this)
return true;

if (!(rhs instanceof Person))
return false;

Person other = (Person)rhs;
return (this.firstName.equals(other.firstName) &&
this.lastName.equals(other.lastName) &&
this.gender.equals(other.gender) &&
this.age == other.age);
}

private String firstName;
private String lastName;
private Gender gender;
private int age;
private Mood mood;
private Person spouse;
private Address[] addresses = new Address[3];
private List<Person> children = new ArrayList<Person>();
private Person mother;
private Person father;
}


Як і раніше, я не збираюся повторювати код класу PersonPerson цілком після кожної зміни; цілком достатньо показати лише модифікований фрагмент. Хоча в даному випадку я взагалі не буду змінювати клас Person. Замість цього ми його розширимо.


Співробітник ти чи ні?


Наша система управління співробітниками перш за все повинна вміти відрізняти звичайні об’єкти типу Person (Наприклад, подружжя і дітей співробітників) від безпосередніх співробітників (об’єктів типу Employee). З точки зору проектування це дуже просто: достатньо додати новий клас-спадкоємець Person і помістити його в той же пакет, що й інші класи. Новий клас – назвемо його, як і слід було очікувати, Employee – Показаний на лістингу 2.


Лістинг 2. Employee – спадкоємець Person





package com.tedneward.model;
public class Employee extends Person
{
public Employee()
{ }
public Employee(String firstName, String lastName, String title,
Gender gender, int age, Mood mood)
{
super(firstName, lastName, gender, age, mood);

this.title = title;
}
public String getTitle() { return title; }
public void setTitle(String value) { title = value; }

public String toString()
{
return “[Employee: ” + getFirstName() + ” ” + getLastName() + ” ” +
“(” + getTitle() + “)]”;
}

private String title;
}


Клас Employee не містить нічого особливого крім показаного на лістингу 2. З точки зору ООСУБД всі додаткові методи класу не мають ніякого значення. Все, що вам треба пам’ятати – це те, що Employee є спадкоємцем Person. Зрозуміло, якщо вас дуже цікавить можливе подальше проектування Employee, То можете уявити методи типу promote(), demote(), getSalary() і setSalary(), А також workLikeADog().)


Тестування нової моделі даних


Перевірити працездатність нової моделі досить просто. Я створив JUnit-тест під назвою InheritanceTest, А також трохи заплутаний набір об’єктів для початкового заповнення об’єктної БД. Щоб спростити розуміння тестового висновку (він буде показаний на лістингу 6), лістинг 3 розкриває деталі підготовки БД при виконанні методу prepareDatabase()з анотацією @Before.


Лістинг 3. Ласкаво просимо в компанію (тепер ви – моя власність)





@Before public void prepareDatabase()
{
db = Db4o.openFile(“persons.data”);
// The Newards
Employee ted = new Employee(“Ted”, “Neward”, “President and CEO”,
Gender.MALE, 36, Mood.HAPPY);
Person charlotte = new Person(“Charlotte”, “Neward”,
Gender.FEMALE, 35, Mood.HAPPY);
ted.setSpouse(charlotte);
Person michael = charlotte.haveBaby(“Michael”, Gender.MALE);
michael.setAge(14);
Person matthew = charlotte.haveBaby(“Matthew”, Gender.MALE);
matthew.setAge(8);
Address tedsHomeOffice =
new Address(“12 Redmond Rd”, “Redmond”, “WA”, “98053”);
ted.setHomeAddress(tedsHomeOffice);
ted.setWorkAddress(tedsHomeOffice);
ted.setVacationAddress(
new Address(“10 Wannahokalugi Way”, “Oahu”, “HA”, “11223”));
db.set(ted);

// The Tates
Employee bruce = new Employee(“Bruce”, “Tate”, “Chief Technical Officer”,
Gender.MALE, 29, Mood.HAPPY);
Person maggie = new Person(“Maggie”, “Tate”,
Gender.FEMALE, 29, Mood.HAPPY);
bruce.setSpouse(maggie);
Person kayla = maggie.haveBaby(“Kayla”, Gender.FEMALE);
Person julia = maggie.haveBaby(“Julia”, Gender.FEMALE);
bruce.setHomeAddress(
new Address(“5 Maple Drive”, “Austin”,
“TX”, “12345”));
bruce.setWorkAddress(
new Address(“5701 Downtown St”, “Austin”,
“TX”, “12345”));
// Ted and Bruce both use the same timeshare, apparently
bruce.setVacationAddress(
new Address(“10 Wanahokalugi Way”, “Oahu”,
“HA”, “11223”));
db.set(bruce);

// The Fords
Employee neal = new Employee(“Neal”, “Ford”, “Meme Wrangler”,
Gender.MALE, 29, Mood.HAPPY);
Person candi = new Person(“Candi”, “Ford”,
Gender.FEMALE, 29, Mood.HAPPY);
neal.setSpouse(candi);
neal.setHomeAddress(
new Address(“22 Gritsngravy Way”, “Atlanta”, “GA”, “32145”));
// Neal is the roving architect
neal.setWorkAddress(null);
db.set(neal);

// The Slettens
Employee brians = new Employee(“Brian”, “Sletten”, “Bosatsu Master”,
Gender.MALE, 29, Mood.HAPPY);
Person kristen = new Person(“Kristen”, “Sletten”,
Gender.FEMALE, 29, Mood.HAPPY);
brians.setSpouse(kristen);
brians.setHomeAddress(
new Address(“57 Classified Drive”, “Fairfax”, “VA”, “55555”));
brians.setWorkAddress(
new Address(“1 CIAWasNeverHere Street”, “Fairfax”, “VA”, “55555”));
db.set(brians);

// The Galbraiths
Employee ben = new Employee(“Ben”, “Galbraith”, “Chief UI Director”,
Gender.MALE, 29, Mood.HAPPY);
Person jessica = new Person(“Jessica”, “Galbraith”,
Gender.FEMALE, 29, Mood.HAPPY);
ben.setSpouse(jessica);
ben.setHomeAddress(
new Address(
“5500 North 2700 East Rd”, “Salt Lake City”,
“UT”, “12121”));
ben.setWorkAddress(
new Address(
“5600 North 2700 East Rd”, “Salt Lake City”,
“UT”, “12121”));
ben.setVacationAddress(
new Address(
“2700 East 5500 North Rd”, “Salt Lake City”,
“UT”, “12121”));
// Ben really needs to get out more
db.set(ben);

db.commit();
}


Як і раніше, я використовую метод deleteDatabase() з анотацією @After для очищення БД після кожного запуску, щоб тести залишалися повністю незалежними один від одного.


Запустимо пару запитів


Для початку перевіримо, які наслідки спричинило додавання в систему класу Employee. Система повинна надавати можливість вибірки всіх співробітників, наприклад, для того щоб відразу їх всіх звільнити в разі банкрутства (так, звучить, звичайно, жорстоко, але в моїй пам’яті все ще спливає відомий криза доткомів у 2001 р.). Перевірка виглядає вельми просто, як ви можете судити з лістингу 4.


Лістинг 4. “Ви звільнені!”, Заявляє Тед





@Test public void testSimpleInheritanceQueries()
{
ObjectSet employees = db.get(Employee.class);
while (employees.hasNext())
System.out.println(“Found ” + employees.next());
}

При запуску відразу ж виявляється цікавий момент: тільки об’єкти класу Employee(Я, Бен, Ніл, Брайан і Брюс) повертаються в якості результату вибірки. Це означає, що ООСУБД якимось чином розпізнала умова на приналежність підтипу Employee і вибрала тільки ті об’єкти, які йому задовольняли. Так як ніхто з членів сімей співробітників не був об’єктом типу Employee, Вони не задовольняли цій умові і, як наслідок, не були включені в результат вибірки.


Ще цікавіше запустити запит на вибірку всіх об’єктів типу Person, Наприклад, так (лістинг 5).


Лістинг 5. Виведи всіх людей





@Test public void testSimpleNonEmployeeQuery()
{
ObjectSet persons = db.get(Person.class);
while (persons.hasNext())
System.out.println(“Found ” + persons.next());
}

При цьому вибираються абсолютно всі об’єкти в системі, включаючи вибрані раніше об’єкти типу Employee. В цілому це абсолютно логічно, тому що кожен об’єкт типу Employee є також об’єктом типу Person (Employee is-a Person) В силу використання успадкування при оголошенні Employee в Java. Таким чином, кожен об’єкт задовольняє умові запиту.


Як бачите, немає нічого складного у використанні спадкування (а в подальшому і поліморфізму) в db4o. Не потрібні ні розширення мови запитів, ні додаткові операції над типами понад використовуваних в Java. Все що потрібно – це вказати потрібний тип в запиті. Все працює аналогічно зв’язуванню таблиць в запитах SQL, в яких вказуються таблиці, з яких необхідно вибирати дані. Додатковою перевагою є неявне включення об’єктів батьківських типів в результати запиту. На лістингу 6 показаний результат виконання тесту, наведеного раніше в лістингу 3.


Лістинг 6. Поліморфізм в справі





.Found [Employee: Ted Neward (President and CEO)]
Found [Person: firstName = Charlotte lastName = Neward gender = FEMALE age = 35
mood = HAPPY spouse = Ted ]
Found [Person: firstName = Michael lastName = Neward gender = MALE age = 14 mood
= CRANKY ]
Found [Person: firstName = Matthew lastName = Neward gender = MALE age = 8 mood
= CRANKY ]
Found [Employee: Bruce Tate (Chief Technical Officer)]
Found [Person: firstName = Maggie lastName = Tate gender = FEMALE age = 29 mood
= HAPPY spouse = Bruce ]
Found [Person: firstName = Kayla lastName = Tate gender = FEMALE age = 0 mood =
CRANKY ]
Found [Person: firstName = Julia lastName = Tate gender = FEMALE age = 0 mood =
CRANKY ]
Found [Employee: Neal Ford (Meme Wrangler)]
Found [Person: firstName = Candi lastName = Ford gender = FEMALE age = 29 mood =
HAPPY spouse = Neal ]
Found [Employee: Brian Sletten (Bosatsu Master)]
Found [Person: firstName = Kristen lastName = Sletten gender = FEMALE age = 29 m
ood = HAPPY spouse = Brian ]
Found [Employee: Ben Galbraith (Chief UI Director)]
Found [Person: firstName = Jessica lastName = Galbraith gender = FEMALE age = 29
mood = HAPPY spouse = Ben ]

Може здатися дивним, що всі повернуті об’єкти як і раніше ставляться до свого підтипу незалежно від того, як вони були обрані. Наприклад, якщо викликати метод toString() для кожного об’єкта з попереднього запиту, то для кожного об’єкта типу Person буде викликаний Person.toString(), Як і слід було очікувати. Але так як клас Employee перевантажує toString(), То динамічне зв’язування відпрацює як звичайно, і для об’єктів типу Employee буде викликаний Employee.toString(). Іншими словами, додаткова інформація про співробітників, що міститься в полях класу Employee, Не буде “відсічена” запитом. Подібна проблема часто зустрічається при використанні SQL, якщо при зберіганні об’єктів кожного підтипу в окремій дочірньої таблиці забути зв’язати якусь із них з батьківської.


Спадкування в природних запитах


Зрозуміло, умови успадкування можна використовувати в природних запитах (native queries) в тій же мірі, що і в об’єктних запитах в попередніх прикладах. Синтаксис запитів трохи ускладнюється, але, як показано на лістингу 7, не зазнає ніяких серйозних змін.


Listing 7. А ви не замужем чи за нашим співробітником?





@Test public void testNativeQuery()
{
List<Person> spouses =
db.query(new Predicate<Person>() {
public boolean match(Person candidate) {
return (candidate.getSpouse() instanceof Employee);
}
});
for (Person spouse : spouses)
System.out.println(spouse);
}

Як бачите, запит дуже схожий на написаний вище з тією лише різницею, що тепер ми запитуємо тільки ті об’єкти типу Person, Чиї дружини є об’єктами типу Employee. Це робиться за допомогою Java-оператора instanceof, Який перевіряє тип об’єкта, що повертається методом getSpouse() класу Person. Пам’ятайте, що метод match() повинен повертати тільки true або false, вказуючи таким чином, чи треба включати даний об’єкт в результат виконання запиту.


На лістингу 8 показано, як зміна типу-параметра класу Predicate, Що передається в метод query(), Може змінити неявно заданий умова на тип обираних об’єктів:


Лістинг 8. Скандал! Службовий роман!





@Test public void testEmployeeNativeQuery()
{
List<Employee> spouses =
db.query(new Predicate<Person>() {
public boolean match(Person candidate) {
return (candidate.getSpouse() instanceof Employee);
}
});
for (Person spouse : spouses)
System.out.println(spouse);
}

Запит повертає порожній набір даних, тому що за умовою повинні бути обрані лише ті співробітники, чиї дружини також є об’єктами типу Employee. На даний момент таких об’єктів в БД немає, але варто компанії взяти на роботу Шарлотту (Charlotte), як запит відразу поверне два об’єкти, а саме Теда (Ted) і Шарлотту (і при цьому хтось ще має щось проти службових романів!)


Ось, загалом і в цілому, як працює спадкування в запитах. У нього немає ніяких побічних ефектів при роботі із запитами на зміну і видалення, а також при обмеженні глибини вибірки даних. Але, як ви пам’ятаєте, в Java існує два варіанти спадкування: спадкування реалізації (конструкція extends) і спадкування інтерфейсів (конструкція implements). Оскільки db4o підтримує перший варіант, він також зобов’язаний підтримувати і спадкування інтерфейсів, що є досить потужною можливістю, як буде показано нижче.


Інтерфейси – це головне


Напевно, один з перших уроків початківця програміста на Java або C # – це роль інтерфейсів при проектуванні додатків. Інтерфейси надають потужні можливості для відділення типів від їх реалізацій. Наприклад, використовуючи інтерфейси, можна оголосити тип як Comparable або Serializable, Або ж, як у нашому випадку – Employable (Лістинг 9). Можливо, останній – це деякий перегин, але цілком підходить для даної навчальної мети.


Лістинг 9. Гей, 2001-й залишився в минулому, пора працювати!





package com.tedneward.model;
public interface Employable
{
public boolean willYouWorkForUs();
}





 



Об’єкти і ролі

З точки зору певних підходів до проектування моє використання інтерфейсів і наслідування для моделювання об’єктів і їх ролей може виглядати неправомірним. Наприклад, припустимо, що родич деякого співробітника був найнятий на роботу в компанії. Чи означає це, що нам необхідно видалити його зі списку об’єктів типу Person і додати вже як об’єкт типу Employee? У той час як один об’єкт може грати різні ролі в різні моменти часу, ні його реалізація, ні список інтерфейсів не повинні змінюватися по ходу виконання програми.


Це твердження цілком справедливо, і мій підхід до моделювання порушує його принципи. У той же час інтерфейси і спадкування використовуються в даній статті виключно в педагогічних цілях, для демонстрації можливостей db4o.


Щоб продемонструвати інтерфейси у справі, мені знадобиться деяка реалізація Employable. Як ви, напевно, вже здогадалися, нею стане клас EmployablePerson, Який також є спадкоємцем Person. Я не буду показувати деталі реалізації, так як там все одно немає нічого цікавого крім додавання рядка ** EMPLOYABLE ** в кінець методу EmployablePerson.toString(), Перегружаюшего Person.toString(). Я тільки зміню метод prepareDatabase(), Зробивши Шарлотту об’єктом EmployablePerson, А не просто Person.


Тепер я можу написати запит, перебирающий родичів наших співробітників в пошуку тих, кого також можна найняти на роботу. Код показаний в лістингу 10.


Лістинг 10. Бачите, ця позиція все ще вакантна …





@Test public void testEmployableQuery()
{
List<Employable> potentialEmployees =
db.query(new Predicate<Employable>() {
public boolean match(Employable candidate) {
return (candidate.willYouWorkForUs());
}
});
for (Employable e : potentialEmployees)
System.out.println(“Eureka! ” + e + ” has said they”ll work for us!”);
}

Як і очікувалося, Шарлотта була включена в результат запиту. Це означає, що будь-інтерфейс, який мені прийде в голову створити, може бути використаний для накладення умов на вибірку даних. При цьому немає ніякої необхідності додавати штучні атрибути: та ж Шарлотта задовольняє умові тільки завдяки реалізації інтерфейсу, що відрізняє її від всіх інших родичів на поточний момент часу.


Висновок


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

Схожі статті:


Сподобалася стаття? Ви можете залишити відгук або підписатися на RSS , щоб автоматично отримувати інформацію про нові статтях.

Коментарів поки що немає.

Ваш отзыв

Поділ на параграфи відбувається автоматично, адреса електронної пошти ніколи не буде опублікований, допустимий HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

*