Вкладений тип Person.PhoneNumber згенерований, як і очікувалось, як вкладений в Person клас.
Конструктори на противагу Повідомленням
Всі повідомлення класів, згенеровані за допомогою protocol buffer компілятора, є сталими (immutable). Як тільки створений об'єкт повідомлення, то він не може бути зміненим, так само, як і Java String тип. Для побудови повідомлення спочатку потрібно створити конструктор, задати певним полям необхідними значеннями, а потім викликати метод конструктора build().
Можна помітити, як кожен метод конструктора, що модифікує повідомлення, вертає інший конструктор. В дійсності, повернений об'єкт є тим самим конструктором, з якого викликається метод. Для зручності він вертається так, щоб можна було послідовно зв'язати між собою декілька сеттерів в просту стрічку коду.
Ось приклад створення екземпляру Person:
Person john =
Person.newBuilder()
.setId(1234)
.setName("John Doe")
.setEmail("jdoe@example.com")
.addPhone(
Person.PhoneNumber.newBuilder()
.setNumber("555-4321")
.setType(Person.PhoneType.HOME))
.build();
Стандартні Методи Повідомлень
Кожне повідомлення та конструктор класу містять також ряд інших методів, які дозволяють контролювати або маніпулювати цілим повідомленням, включаючи:
isInitialized(): перевіряє чи задані всі обов'язкові поля.
toString(): вертає повідомлення в представленні, яке є доступним людському розумінню, зокрема корисне при відлагодженні.
mergeFrom(Message other): (тільки конструктор) з'єднує вміст інших в поточне повідомлення, перезаписує окремі поля та зчеплює ті, котрі повторюються.
clear(): (тільки конструктор) встановлює всі поля назад в порожній стан.
Ці методи реалізувують Message і Message.Builder інтерфейси спільно із всіма Java повідомленнями та конструкторами. Для отримання додаткової інформації див. .
Аналіз та Серіалізація (Serialization)
Нарешті кожен клас protocol buffer-а має методи для написання і читання повідомлень з вибраного типу, використовуючи protocol buffer-у. До них відносяться:
byte[] toByteArray();: серіалізує повідомлення та вертає масив байтів, що містить рядок байтів.
static Person parseFrom(byte[] data);: аналізує повідомлення з даного масиву байтів.
void writeTo(OutputStream output);: серіалізує повідомлення і записує його в OutputStream.
static Person parseFrom(InputStream input);: читає та аналізує повідомлення з InputStream.
Ось лише кілька з варіантів, передбачених для аналізує та серіалізаціі. Знову ж таки, див. для повного списку.
Protocol Buffers та O-O ескіз В основному класи protocol buffer-а скидають дані holder-ам (подібно до structs в C++), проте вони не дуже дружать з об'єктною моделлю. Найкращий спосіб поведінки із згенерованими класами полягає в тому, щоб згорнути згенерований клас protocol buffer-а у конкретний клас програми. Згортання protocol buffer-ів також є хорошою ідеєю при відсутньому керуванні над ескізом .proto файлу (якщо, скажімо, повторно використовувати певний з іншого проекту). У цьому випадку можна використовувати обгортку класу (wrapper class) до розробки інтерфейсу більш належного для особливого середовища даної програми: приховування деяких даних і методів, показ придатних функцій і т. д. Не можна додавати поведінку до згенерованих класів за їхнім наслідуванням. Це дозволить розірвати внутрішні механізми і взагалі не є приємливою об'єктно-орієнтованою практикою.
Написання Повідомлення
Тепер слід спробувати використати дані класи protocol buffer-у. Спочатку необхідно, щоб програма адресна книжка могла записувати персональні подробиці у файл адресної книжки. Для цього потрібно створити і заповнити екземпляри даних класів protocol buffer-у, а потім записати їх у вихідний потік.
Ось програма, яка зчитує AddressBook з файлу, додає до нього новий Person, що базований на даних, введенних користувачем, а також знову записує новий AddressBook назад у файл. Виділені ті частини, які наряму викликають або посилають код, згенерований protocol компілятором.
import com.example.tutorial.AddressBookProtos.AddressBook;
import com.example.tutorial.AddressBookProtos.Person;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.InputStreamReader;
import java.io.IOException;
import java.io.PrintStream;
class AddPerson {
// This function fills in a Person message based on user input.
static Person PromptForAddress(BufferedReader stdin,
PrintStream stdout) throws IOException {
Person.Builder person = Person.newBuilder();
stdout.print("Enter person ID: ");
person.setId(Integer.valueOf(stdin.readLine()));
stdout.print("Enter name: ");
person.setName(stdin.readLine());
stdout.print("Enter email address (blank for none): ");
String email = stdin.readLine();
if (email.length() > 0) {
person.setEmail(email);
}
while (true) {
stdout.print("Enter a phone number (or leave blank to finish): ");
String number = stdin.readLine();
if (number.length() == 0) {
break;
}
Person.PhoneNumber.Builder phoneNumber =
Person.PhoneNumber.newBuilder().setNumber(number);
stdout.print("Is this a mobile, home, or work phone? ");
String type = stdin.readLine();
if (type.equals("mobile")) {
phoneNumber.setType(Person.PhoneType.MOBILE);
} else if (type.equals("home")) {
phoneNumber.setType(Person.PhoneType.HOME);
} else if (type.equals("work")) {
phoneNumber.setType(Person.PhoneType.WORK);
} else {
stdout.println("Unknown phone type. Using default.");
}
person.addPhone(phoneNumber);
}
return person.build();
}
// Main function: Reads the entire address book from a file,
// adds one person based on user input, then writes it back out to the same
// file.
public static void main(String[] args) throws Exception {
if (args.length != 1) {
System.err.println("Usage: AddPerson ADDRESS_BOOK_FILE");
System.exit(-1);
}
AddressBook.Builder addressBook = AddressBook.newBuilder();
// Read the existing address book.
try {
addressBook.mergeFrom(new FileInputStream(args[0]));
} catch (FileNotFoundException e) {
System.out.println(args[0] + ": File not found. Creating a new file.");
}
// Add an address.
addressBook.addPerson(
PromptForAddress(new BufferedReader(new InputStreamReader(System.in)),
System.out));
// Write the new address book back to disk.
FileOutputStream output = new FileOutputStream(args[0]);
addressBook.build().writeTo(output);
output.close();
}
}
Читання Повідомлення
Звісно, що адресна книга не буде приносити великої користі, якщо з неї не можна буде отримати жодної інформації! Цей приклад читає файл, створений на основі вище описаного прикладу та виводить всю інформацію в ньому.
import com.example.tutorial.AddressBookProtos.AddressBook;
import com.example.tutorial.AddressBookProtos.Person;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.PrintStream;
class ListPeople {
// Iterates though all people in the AddressBook and prints info about them.
static void Print(AddressBook addressBook) {
for (Person person: addressBook.getPersonList()) {
System.out.println("Person ID: " + person.getId());
System.out.println(" Name: " + person.getName());
if (person.hasEmail()) {
System.out.println(" E-mail address: " + person.getEmail());
}
for (Person.PhoneNumber phoneNumber : person.getPhoneList()) {
switch (phoneNumber.getType()) {
case MOBILE:
System.out.print(" Mobile phone #: ");
break;
case HOME:
System.out.print(" Home phone #: ");
break;
case WORK:
System.out.print(" Work phone #: ");
break;
}
System.out.println(phoneNumber.getNumber());
}
}
}
// Main function: Reads the entire address book from a file and prints all
// the information inside.
public static void main(String[] args) throws Exception {
if (args.length != 1) {
System.err.println("Usage: ListPeople ADDRESS_BOOK_FILE");
System.exit(-1);
}
// Read the existing address book.
AddressBook addressBook =
AddressBook.parseFrom(new FileInputStream(args[0]));
Print(addressBook);
}
}
Розширення Protocol Buffer-у
Рано чи пізно, після запуску коду, що використовує даний protocol buffer, безсумніву потрібно буде «покращити» визначення protocol buffer-у. Якщо необхідно, щоб нові buffer-и були сумісними з попередніми версіями, а попередні buffer-и бути сумісні з наступними версіями — виглядає, як майже необхідне — то існують деякі правила, які потрібно дотримуватися. У новій версії protocol buffer-у:
слід не змінювати номери теґів всіх присутніх полів.
слід не створювати і не стирати жодні з обов'язкових полів.
можна стирати необов'язкові поля та поля, що повторюються.
можна створювати необов'язкові поля та поля, що повторюються, проте потрібно використовувати свіжі номери теґів (тобто, номери теґів, які ніколи не використовувалися в protocol buffer-і, навіть в стертих полях).
(Ось до правил, проте їх рідко використовують.)
Якщо дотримуватися цих правил, старий код буде радісно читати нові повідомлення і просто ігнорувати всі нові поля. Для старого коду, необов'язкові поля, які стерли, просто матимуть свої значення за замовчуванням, а стерті поля, які повторювались, будуть порожніми. Новий код також буде відкрито зчитувати старі повідомлення. Однак, майте на увазі, що нові необов'язкові поля не будуть присутні у старих повідомленнях, так що вам доведеться або прямо перевірити чи у них встановлено has_ або забезпечити допустимим по замовчуванню значенням у даному .proto файлі з [default = value] після номеру теґу. Якщо значення за замовчуванням не є визначеним для необов'язкового елемента, то по замовчуванню замість нього використовується значення конкретного типу: для стрічок (string) значенням за замовчуванням є порожня стрічка, для булівських (boolean) — false, для числових (numeric) типів — нуль. Слід також зауважити, що якщо додано нове поле, яке повторюється, то даний новий код не зможе повідомити чи воно залишилось порожнім (для нового коду) чи взагалі коли-небудь було встановлене (для старого коду), оскільки немає has_ прапорця (flag) для нього.
Вдосконалене Застосування
Можливе використовування Protocol buffer-ів виходить за рамки простих аксессорів та серіалізаціі. Для впевненості можна досліджувати , щоб дізнатися, що ще можна робити з ними.
Однією з ключових можливостей, що надаються класами протоколу повідомлення є відображення (reflection). Можна ітерувати над полями повідомлення та маніпулювати їхніми значеннями без написання коду порівняно з будь-яким конкретним типом повідомлення. Одним з найкорисніших способів використання відображення є для перетворення протоколу повідомлень в інші кодування та з них, таких як XML або JSON. А більш передове застосування відображення надає можливість знаходити відмінності між двома повідомлення одного й того ж типу, або розробляти свого роду «регулярні вирази для протокольних повідомлень», в яких можна записувати вирази, що відповідають певному змістові повідомлення. Якщо використати фантазію, то можна застосовувати Protocol Buffer-и в набагато ширшому колі проблем, ніж можна було очікувати на початку!
Відображення постачається як складова та інтерфейсів.
Коментарі (0)
RSS згорнути / розгорнутиТільки зареєстровані й авторизовані користувачі можуть залишати коментарі.