AddThis Feed Button

GWT-PF, GWT Pleso Framework

GWT Pleso framework

GWT Pleso Framework - это высокоуровневый framework основанный на GWT для создания пользовательских AJAX интерфейсов фронтэндов баз данных

 

pleso odb-ui prototype

ODB-UI prototype

Прототип интерфейса пользователя к объектным базам данных


Туториал 1. Написание первого GWT-PF приложения.

В этом туториале описывается пример создания простого GWT-PF приложения без использования баз данных и RPC. Данное тестовое приложение создает простую форму-справочник (часть 1), а также формы манипуляций с данными: вставка, редактирование и удаление (часть2). В документе подразумевается, что уже установлен и настроен GWT 1.4. Вы можете загрузить готовое приложение, которое создается в обеих частях данного туториала с sourceforge (файл gwt-pf-tutorial1).

Содержание:

Часть 1. Создание простого справочника

  1. Создание проекта
  2. Общий подход
  3. Создание набора данных
  4. Создание класса-справочника
  5. Визуализация справочника

Часть 2. Создание форм манипуляции с данными: вставка, редактирование, удаление

  1. Модификация набора данных для возможности вставки, редактирования и удаления
  2. Разработка форм манипуляций с даными

Часть 1. Создание простого справочника (часть 2).

Создание проекта

Создадим GWT проект для Eclipse стандартными средствами projectCreator и applicationCreator:

 1
projectCreator -eclipse gwt-pf-tutorial1
 1
applicationCreator -eclipse gwt-pf-tutorial1 net.pleso.tutorials.tutorial1.client.gwtpftutorial1

Далее импортируем проект в Eclipse и получаем рабочий GWT проект

Теперь установим GWT Pleso Framework. Для этого необходимо загрузить последнуюю версию GWT-PF отсюда. Создадим папку lib в нашем GWT проекте (правый клик на корень проекта - New - Folder) и распакуем туда два jar-файла: gwt-pf-core.jar и gwt-pf-ui.jar. После Refresh проекта добавляем внешние библиотеки в Build Path: правый клик на jar файл - Build Path - Add to Build Path. Составные framework появятся в проекте, как подключенные, вы можете просмотреть их содержимое и исходные коды.

Добавляем GWT-PF в XML-конфигурацию GWT проекта - файл gwtpftutorial1.gwt.xml:

 1
<inherits name="net.pleso.framework.plesoframework"></inherits>

В Eclipse получаем готовый для работы проект:

Общий подход

Логика работы GWT-PF представлена моделью интерфейсов, созданных в gwt-pf-core.jar. Набор этих интерфейсов создан с целью абстрактного представления разного рода взаимосвязанных логических сущностей, таких как форма, справочник, строка, колонка, операция и др. Все интерфейсы разделены и размещены в двух слоях: BL (business logic layer) и DAL (data access layer). Интерфейсы слоя бизнес логики представляют собой сущности для описания правил отображения и манипулирования данными. Интерфейсы слоя данных предназначены для обеспечения доступа к данным и представления примитивных единиц данных. Задача разработчика при создании своего приложения реализовать соответствующие интерфейсы для предоставления нужной логики работы с данными. Такой подход позволяет создать формальную модель логики приложения, используя которую, может функционировать универсальная визуальная часть, реализованная в gwt-pf-ui.jar. Эта часть представляет собой слой UI (user interface layer), который зависит от модели интерфейсов из gwt-pf-core.jar. Библиотека gwt-pf-ui содержит набор визуальных компонентов, предоставляющих пользователю визуальные средства для управления данными. Для поддержки логики работы визуальные компоненты используют реализации интерфейсов gwt-pf-core, в которых она формально описана разработчиком приложения.

Таким образом, разработчик сначала создает реализацию модели данных (DAL), описывает логику работы с данными (BL) и предоставляет созданные сущности, приведенные к соответствующим интерфейсам, визуальному уровню (UI), уже разработанному в фреймворке. Визуальный уровень, в свою очередь, будучи готовым к работе с такой описанной моделью, предоставляет пользователю универсальные визуальные средства для работы с приложением по заданной логике.

gwt-pf-ui создан в качестве отдельной библиотеки, так как представляет собой отдельное решение визуального уровня, зависимое от модели интерфейсов. А так, как это отдельный слой данной трехуровневой архитектуры, то он вполне может быть заменен или использоваться кооперативно с другими подобными решениями.

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

Начнем с набора данных. Все интерфейсы для создания набора данных лежат в net.pleso.framework.client.dal. Все запросы на получение данных в GWT-PF изначально асинхронные, чтобы сделать возможной работу с RPC или JSON, где на серверной стороне может находиться база даных или другое хранилище.

Создадим простой JavaBean, который описывает сущность Bank. Это нам нужно для того, чтобы в итоге создать справочник банков. Через стандартные средства Eclipse создаем клас BankInfo в пространстве имен net.pleso.tutorials.tutorial1.client.dal:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package net.pleso.tutorials.tutorial1.client.dal;

import net.pleso.framework.client.dal.db.types.DBInteger;
import net.pleso.framework.client.dal.db.types.DBString;

public class BankInfo {
	
	public BankInfo() {}
	
	public BankInfo(Integer bank_id, String bank_name) {
		this.bank_id = bank_id;
		this.bank_name = bank_name;
	}

	private Integer bank_id = DBInteger.nullValue;
	private String bank_name = DBString.nullValue;

	public Integer getBank_id() {
		return bank_id;
	}

	public void setBank_id(Integer bank_id) {
		this.bank_id = bank_id;
	}

	public String getBank_name() {
		return bank_name;
	}

	public void setBank_name(String bank_name) {
		this.bank_name = bank_name;
	}

}

Поля bank_id и bank_name инициализируются по умолчанию значением Null (эта та же концепция, что и в базах данных). Все типы данных, с которыми работает GWT-PF, представлены в net.pleso.framework.client.dal.db.types и начинаются с префикса DB (database). В даном случаем мы используем DBInteger (целое число) и DBString (строка). В будущем вы можете использовать класс, подобный BankInfo, для обмена данных с сервером через RPC - следует просто добавить реализацию стандартного интерфейса IsSerializable из GWT.

Для того что-бы фреймворк мог работать с сущностью Bank, необходимо "представить" банк как интерфейс GWT-PF (через его реализацию). Для этого служат интерфейсы IDataColumn и IDataRow, которые представляют собой колонку данных и строку данных. Для удобства создадим наследника BankInfo, который реализует интерфейс IDataRow и назовем его Bank:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package net.pleso.tutorials.tutorial1.client.dal;

public class Bank extends BankInfo implements IDataRow {

	public IDBValue getCell(IDataColumn column) {
		// TODO Auto-generated method stub
		return null;
	}

	public IDataColumn getColumn(int index) {
		// TODO Auto-generated method stub
		return null;
	}

	public int getColumnCount() {
		// TODO Auto-generated method stub
		return 0;
	}

	public void setCell(IDataColumn column, IDBValue value) {
		// TODO Auto-generated method stub

	}

}

Как видим, даный класс представляет собой строку данных, которая дает доступ к своему содержимому или через соответствующие методы-аксесоры каждого из полей (как к наследнику BankInfo), или через параметризированные методы чтения/записи getCell и setCell (через реализацию интерфейса IDataRow). Последним методом будет пользоваться визуальная часть GWT-PF. Для того что бы до конца реализовать даный интерфейс, нужно познакомится еще с двумя интерфейсами: IDBValue и IDataColumn.

IDBValue представляет собой основной интерфейс, который реализируют все типы данных в GWT-PF. То есть DBInteger, DBString и другие типы являються реализациями интерфейса IDBValue. Он требует у типа данных чтобы тот поддерживал состояние Null и мог представить себя в виде текстовой строки для отображения. Также интерфейс требует чтобы каждый тип даных мог себя провалидировать с помощью метода parseValue. Тоесть у каждого типа можно спросить, является ли даный String для него правильным (валидным). К примеру: для типа DBDate будет правильным значение "01.01.2007" и не правильным - "some date".

IDataColumn представляет собой интерфейс колонки уровня DAL. Колонка имеет имя в базе данных, заголовок для пользователя, а также свойство, которое указывает, поддерживает ли она значение Null.

Познакомившись с интерфейсами, реализуем IDataRow до конца и дописываем все методы:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
public class Bank extends BankInfo implements IDataRow {
	
	public Bank() {
	}
	
	public Bank(Integer bank_id, String bank_name) {
		super(bank_id, bank_name);
	}
	
	public class bank_id_column extends Columns {
		public bank_id_column() {
			super("bank_id", "Bank ID", false);
		}

		public IDBValue getCell(Bank row) {
			return new DBInteger(row.getBank_id());
		}

		public void setCell(Bank row, IDBValue value) {
			if (value instanceof DBInteger)
				row.setBank_id(((DBInteger) value).getInteger());
		}
	}
	
	public class bank_name_column extends Columns {
		public bank_name_column() {
			super("bank_name", "Bank name", false);
		}

		public IDBValue getCell(Bank row) {
			return new DBString(row.getBank_name());
		}

		public void setCell(Bank row, IDBValue value) {
			if (value instanceof DBString)
				row.setBank_name(((DBString) value).getString());
		}
	}
	
	private static ArrayList columns = new ArrayList();
	private static final Bank instance = new Bank();
	
	public abstract static class Columns extends DataColumn {
		
		public Columns(String name, String caption, boolean allowNull) {
			super(name, caption, allowNull);
			columns.add(this);
			this.order = columns.size(); 
		}
		
		public abstract void setCell(Bank row, IDBValue value);
		public abstract IDBValue getCell(Bank row);
		
		public static final bank_id_column bank_id = instance.new bank_id_column();
		public static final bank_name_column bank_name = instance.new bank_name_column();
	}

	public IDBValue getCell(IDataColumn column) {
		Columns col = (Columns) column;
		return col.getCell(this);
	}

	public IDataColumn getColumn(int index) {
		return (IDataColumn) columns.get(index);
	}

	public int getColumnCount() {
		return columns.size();
	}

	public void setCell(IDataColumn column, IDBValue value) {
		Columns col = (Columns) column;
		col.setCell(this, value);
	}

}

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

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

Получив строку данных, можем реализовать класс, который возвратит список банков по запросу пользователя. В даном туториале мы не будем использовать базу данных, а просто будем возвращать данные из массива. Создадим клас BankDataSet, которые умеет асинхронно возвращать список банков и их количество через стандартный интерфейс AsyncCallback:

 1
 2
 3
 4
 5
 6
 7
 8
public class BankDataSet {

	public void select(SelectParams params, AsyncCallback callback) {	
	}
	
	public void selectCount(AsyncCallback callback) {
	}
}

Здесь используется класс SelectParams. Это класс-утилита, который предназначен для передачи таких параметров запроса данных как: номер страницы данных, как: количество записей на странице, строка поиска, а также информация о сортировке. Реализуем возврат данных из статического масива по параметрам:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
package net.pleso.tutorials.tutorial1.client.dal;

import java.util.Arrays;

import net.pleso.framework.client.bl.exceptions.FrameworkRuntimeException;
import net.pleso.framework.client.dal.SelectParams;

import com.google.gwt.user.client.rpc.AsyncCallback;

public class BankDataSet {
	
	private ArrayList banks = new ArrayList();
	
	private String[] bankNames = { 
			"NewYork Bank",
			"City Bank",
			"Private Bank",
			"OTP Bank",
			"Refactor Bank",
			"Aval Bank"
	};
	
	private static int bankCount = 40;
	
	private int counter = 0;
	
	public BankDataSet() {
		for(int i = 0; i < bankCount; i++) {
			banks.add(new Bank(new Integer(i), this.bankNames[i % bankNames.length]));
		}
		this.counter = bankCount;
	}
	
	private Bank[] getBanks(int offset, int limit, String orderByColumnName, boolean orderByDirection){
		if (offset > banks.size() - 1)
			offset = banks.size() - 1;
		
		if (offset + limit > banks.size() - 1)
			limit =  banks.size() - offset;
		
		Bank[] result = new Bank[limit];
		
		for (int i = 0; i < limit; i++){
			result[i] = (Bank) banks.get(i + offset);
		}
		
		Arrays.sort(result, new DataRowComparator(orderByColumnName, orderByDirection));
		
		return result;
	}
	
	public void select(SelectParams params, AsyncCallback callback) {
		try {
			callback.onSuccess(getBanks(params.getOffset(), params.getLimit(), params.getOrderByColumnName(), params.getOrderByDirection()));
		}
		catch (Exception e){
			e.printStackTrace();
			callback.onFailure(new FrameworkRuntimeException(e.getMessage(), e));			
		}  
	}
	
	public void selectCount(AsyncCallback callback) {
		callback.onSuccess(new Integer(this.banks.size()));
	}
}

В реальном приложении даный класс должен был бы вызывать серверный метод через RPC или JSON для возврата данных из базы данных. Здесь же метод getBanks получает все параметры запроса (offset, limit и т.д.), которые пришли в SelectParams. Он строит соответствующий набор данных и сортирует их, если нужно, с помошью класса DataRowComparator. По сути все это может, и должна делать база данных на сервере. Но в даном туториале мы упростили подход, чтобы показать последовательно работу фреймворка. Вот код DataRowComparator:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
package net.pleso.tutorials.tutorial1.client.dal;

import java.util.Comparator;

import net.pleso.framework.client.dal.IDataColumn;
import net.pleso.framework.client.dal.IDataRow;

public class DataRowComparator implements Comparator {
	
	private String orderByColumnName;
	private boolean orderByDirection; // true - "asc"; false - "desc"
	private IDataColumn sortColumn = null;
	private boolean firstCompare = true;
	
	public DataRowComparator(String orderByColumnName, boolean orderByDirection) {
		this.orderByColumnName = orderByColumnName;
		this.orderByDirection = orderByDirection;
	}

	public int compare(Object o1, Object o2) {
		IDataRow row1 = (IDataRow) o1;
		IDataRow row2 = (IDataRow) o2;
		
		if (row1 == null && row2 != null)
			return -1;
		if (row2 == null && row1 != null)
			return 1;
		if (row2 == null && row1 == null)
			return 0;
		
		if (this.firstCompare) {
			this.sortColumn = getSortColumnIndex(row1);
			this.firstCompare = false;
		}
		
		if (this.sortColumn == null)
			return 0;
		
		int c = 0;
		
		if (row1.getCell(sortColumn) instanceof DBInteger && row2.getCell(sortColumn) instanceof DBInteger) {
			c = ((DBInteger)row1.getCell(sortColumn)).getInteger().compareTo(((DBInteger)row2.getCell(sortColumn)).getInteger());
		} else {
			c = row1.getCell(sortColumn).getValue().compareTo(row2.getCell(sortColumn).getValue());
		}
			
		if (!orderByDirection)
			return -c;
		else
			return c;
	}
	
	public IDataColumn getSortColumnIndex(IDataRow row) {
		for(int i = 0; i < row.getColumnCount(); i++) {
			IDataColumn dataColumn = row.getColumn(i);
			if (dataColumn.getName().equalsIgnoreCase(orderByColumnName))
				return dataColumn;
		}
		return null;
	}
	
}

И так, уровень доступа к даным готов. У нас есть сущность Bank, которая представляет собой строку данных (так же как строка в таблице базы данных), а также у нас есть набор данных BankDataSet, который умеет возвращать список банков постраничным способом, а также осуществлять сортировку.

Создание класса-справочника

Теперь переходим к уровню бизнес-логики, по которому GWT-PF построит визуальную часть. Все интерфейсы для создания бизнес-логики лежат в пространстве имен net.pleso.framework.client.bl. Интерфейс справочника называется IRB (interface of reference book). Создадим его реализацию в пространстве имен net.pleso.tutorials.tutorial1.client.bl:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package net.pleso.tutorials.tutorial1.client.bl;

import net.pleso.framework.client.bl.IAuthDataSource;
import net.pleso.framework.client.bl.rb.IRB;
import net.pleso.framework.client.bl.rb.columns.IRBColumn;

public class BankRB implements IRB {

	public String getCaption() {
		// TODO Auto-generated method stub
		return null;
	}

	public IRBColumn[] getColumns() {
		// TODO Auto-generated method stub
		return null;
	}

	public IAuthDataSource getDataSource() {
		// TODO Auto-generated method stub
		return null;
	}

}

Как видим из кода, для того, что бы фреймворк построил справочник, ему необходимы: заголовок справочника (getCaption), масив колонок (getColumns) и источник даных (getDataSource). Заголовок добавляем сразу:

 1
 2
 3
 4
 5
public class BankRB implements IRB {

	public String getCaption() {
		return "Banks";
	}

Теперь познакомимся с интерфейсом колонок IRBColumn. Это такая же колонка, но уже на уровне бизнес-логики, а не на уровне доступа к даным. Этот интерфейс представляет абстрактную колонку, в нем есть лишь заголовок колонки (getCaption) и ее пропорциональная ширина (getWidth). Пропорциональная ширина обозначает, что вы можете указать любое целое число, как ширину, при этом ширина каждой следующей колонки рассчитывается, как пропорция относительно других (например: значения 50 и 50 дадут две колонки с одинаковой шириной). Все колонки разных типов в GWT-PF должны реализировать даный абстрактный интерфейс.

Первая самая простая колонка это - IRBDataColumn. Она уже имеет привязку к данным, а именно к знакомой нам IDataColumn - колонке с уровня доступа к данным. То есть можно сказать, что IRBDataColumn - это один из способов представления IDataColumn на уровне бизнес-логики. Вы можете задать вопрос: зачем разделять ту же сущность на два интерфейса? Ответ: дело в том, что именно колонка задает способ отображения своих даных. Колонку уровня даных можна представить в бизнес-логике многими способами: в виде поля редактирования, в виде текста только для просмотра, в форме ссылки на внешний справочник. Уровень данных не заботится о том, что вы будете делать с колонкой дальше, каким образом манипулировать ее данными пользователь. Этим занимается бизнес-логика. Именно поэтому одна IDataColumn может быть представлена разными реализациями IRBColumn.

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

 1
 2
 3
 4
 5
 6
 7
private static RBColumn[] rbColumns = new RBColumn[] {
		new RBColumn(Bank.Columns.bank_id, 30),
		new RBColumn(Bank.Columns.bank_name, 70) };

public IRBColumn[] getColumns() {
	return rbColumns;
}

Каждой колонке справочника даем соответствующую колонку уровня данных из класса Bank.

Теперь привяжем источник данных. Интерфейс IRB ждет от нас реализации специального интерфейса IAuthDataSource. Создадим внутренний класс, в котором вызовем соответствующие методы нашего класса из уровня DAL:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
private static BankDataSet dataSet = new BankDataSet();
	
public static BankDataSet getBankDataSet() {
	return dataSet;
}

private class BankRBDataSource implements IAuthDataSource {
	
	public void select(SelectParams params, AsyncCallback callback) {
		dataSet.select(params, callback);
	}

	public void selectCount(SelectParams params, AsyncCallback callback) {
		dataSet.selectCount(callback);
	}
}

Уровень бизнес-логики готов. Даных классов достаточно, чтобы отобразить Grid с колонками, наполнить его данными и дать возможность просматривать их постранично и сортировать. Именно это и сделает для нас GWT-PF.

Визуализация справочника

Осталось визуализировать справочник. Для этого используем форму для построения справочников - CustomRBWindow. Сначала поправим сгенерированный GWT файл gwtpftutorial1.html. В body оставьте только:

 1
 2
<h1>GWT-PF Tutorial 1</h1>
<table width="100%"> <tbody><tr> <td id="slot1">&nbsp;</td> </tr> </tbody></table>

Для создания окна справочника используем класс CustomRBWindow, одна из перегрузок конструктора которого принимает экземпляр бизнес-объекта отображаемого справочника. В даном случае это наш BankRB. Все визуальные формы в GWT-PF являются окнами и управляются единым менеджером окон. Отображение окна осуществляется вызовом метода show(). Текущая версия GWT-PF обеспечивает управление окнами через оконный менеджер TabbedSliderManager (менеджер, построенный на закладках и слайдерах). Он представляет собой контейнер для многооконной поддержки. Все отображаемые окна так или иначе будут дочерними виджетами статического экземпляра TabbedSliderManager, создаваемого самим GWT-PF. Таким образом, для того, чтобы увидеть созданное окно нужно разместить менеджер окон на странице. Интерфейс вашего приложения может кардинально отличаться от подхода со слайдерами и закладками. Вы можете написать при надобности собственный менеджер окон.

Описываем построение и визуализацию окна справочника в методе onModuleLoad():

 1
 2
 3
 4
 5
 6
 7
 8
 9
public class gwtpftutorial1 implements EntryPoint {

	public void onModuleLoad() {
		RootPanel.get("slot1").add(TabbedSliderManager.getInstance());

		BankRB rb = new BankRB();
		new CustomRBWindow(rb).show();
	}
}

Запускаем проект и видим рабочий справочник банков с работающей сортировкой и постраничным выводом:

Далее часть 2

Комментарии: 9
IgorG  - 24.03.2009, 21:35:

V sledyyuschej implementatsii kasting proizvoditsya na halyavy' - kakie garantii chto y implementora IDataColumn dolzhnu but' metodu 'getCell'???

public IDBValue getCell(IDataColumn column) { Columns col = (Columns) column; return col.getCell(this); }

Igor G  - 24.03.2009, 21:41:

Dolzhno but' tak:

Columns col = (Column)columns.get(column.getOrder());

Назар Леуш (http://gwt.org.ua)  - 24.03.2009, 21:58:

В данном случае уровень абстракции интерфейсов не позволяет типизировано контролировать наличие такой колонки в записи.

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

В текущей реализации возможная причина ошибки - программист что-то перепутал, и во время исполнения при приведении типа колонки возникнет ошибка выполнения, которая точно укажет на место конфликта

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

Igor G  - 25.03.2009, 21:00:

v chem ideya nasledovaniya IDataColumn v Columns? Kakova sobstveno tsennost' stochki zreniya OOP? Elegantnost' ispol'zovaniya vnytrennih klassov imeet svoi preeimyschestva - no takoj kasting protivorechit printsupam objectnogo programirovaniya

Назар Леуш (http://gwt.org.ua)  - 25.03.2009, 21:32:

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

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

Для нас такая схема оказалась оправданной. Никаких подводных камней она нам не дала.

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

IgorG  - 25.03.2009, 22:35:

Nazar, mne vashe reshenie implementatsii ochen' ponravilos' imenno potomy i napisal. No problema ostaetsya i ona imenno vurazhaetsya v tom chto vashe ytverzhdenie ne spravedlivo: во время исполнения кастинг даст знать, если где-то что-то пошло не туда.

Tak kak custing v storony naslednikov po ierarhii predpolagaet chto vu imeete informatsiyu pro implementora a eto opasnoe zablyzhdenie.

Kstati image 'Capthca' pokazuvaetsya ne polnostyu v FireFoxe pochemyto

You know an upcast is always safe; the base class cannot have a bigger interface than the derived class, therefore every message you send through the base class interface is guaranteed to be accepted. But with a downcast, you don’t really know that a shape (for example) is actually a circle. It could instead be a triangle or square or some other type.

Poetomy, v vashem slychae - order of columns is set by constructor and is garanteed to be true. So, by introducing method getOrder in IDataColumn vu elegantno yhodite ot dowcusting i ostavlyaete svoe original'noe reshenie: Columns col = (Columns)columns.get(column.getOrder()); A ostal'noj kod bez kakih libo izmenenij.

IgorG  - 25.03.2009, 22:37:

Sorry, vu pravu - vo vremya ispolneniya imenno vse i svalitsya... A mozhet i ne padat' - ibo est' vse osnovaniya izbegat' custinga...

Назар Леуш (http://gwt.org.ua)  - 25.03.2009, 23:27:

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

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

IgorG  - 27.03.2009, 15:59:

Netraditsionno, no priznayus', logica v etom tozhe est', ybedil :)