В крупных проектах по разработке программного обеспечения сервис-ориентированная архитектура очень распространена, поскольку она предоставляет функциональный интерфейс, который может использоваться различными группами или отделами. Те же принципы должны применяться при создании пользовательских интерфейсов.
В случае крупной компании, в которой, помимо прочего, есть отдел выставления счетов и отдел управления клиентами, организационная структура может выглядеть следующим образом:
Если отдел биллинга хочет разработать новый диалог для создания счетов, он может выглядеть так:
Как видите, на приведенном выше экране указана ссылка на клиента в верхней части. Нажав кнопку «..» прямо за текстовым полем короткого имени, откроется диалоговое окно, приведенное ниже, которое позволяет пользователю выбрать клиента:
После нажатия «Выбрать» данные клиента отображаются в форме счета.
Также можно выбрать клиента, просто введя номер клиента или введя короткое имя в текстовые поля на экране счета. Если введено уникальное короткое имя, диалог выбора вообще не появляется. Вместо этого данные клиента отображаются напрямую. Только двусмысленное короткое имя приводит к открытию экрана выбора клиента.
Функциональность клиента будет обеспечиваться разработчиками, которые входят в команду управления клиентами. Типичный подход заключается в том, что команда разработчиков управления клиентами предоставляет некоторые услуги, а разработчики биллингового отдела создают пользовательский интерфейс и вызывают эти службы.
Однако этот подход предполагает более тесную связь между этими двумя различными отделами, чем на самом деле необходимо. Счет-фактура требует только уникальный идентификатор для ссылки на данные клиента. Разработчики, создающие диалоговое окно счета-фактуры, на самом деле не хотят знать, как запрашиваются данные клиента или какие услуги используются в фоновом режиме для получения этой информации.
Разработчики по управлению клиентами должны предоставить полную часть пользовательского интерфейса, которая отображает идентификатор клиента и обрабатывает выбор клиента:
Используя JSF 2, этого легко достичь с помощью составных компонентов. Логический интерфейс между отделом управления клиентами и отделом биллинга состоит из трех частей:
- Композитный компонент (XHTML)
- Подложка для составного компонента
- Интерфейс слушателя для обработки результатов выбора
Провайдер (отдел управления клиентами)
Композитный компонент:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:composite="http://java.sun.com/jsf/composite"
xmlns:ice="http://www.icesoft.com/icefaces/component"
xmlns:ace="http://www.icefaces.org/icefaces/components"
xmlns:icecore="http://www.icefaces.org/icefaces/core">
<ui:composition>
<composite:interface name="customerSelectionPanel" displayName="Customer Selection Panel"
shortDescription="Select a customer using it's number or short name">
<composite:attribute name="model" type="org.fuin.examples.soui.view.CustomerSelectionBean" required="true" />
</composite:interface>
<composite:implementation>
<ui:param name="model" value="#{cc.attrs.model}"/>
<ice:form id="customerSelectionForm">
<icecore:singleSubmit submitOnBlur="true" />
<h:panelGroup id="table" layout="block">
<table>
<tr>
<td><h:outputLabel for="customerNumber"
value="#{messages.customerNumber}" /></td>
<td><h:inputText id="customerNumber"
value="#{model.id}" required="false" /></td>
<td> </td>
<td><h:outputLabel for="customerShortName"
value="#{messages.customerShortName}" /></td>
<td><h:inputText id="customerShortName"
value="#{model.shortName}" required="false" /></td>
<td><h:commandButton action="#{model.select}"
value="#{messages.select}" /></td>
</tr>
<tr>
<td><h:outputLabel for="customerName"
value="#{messages.customerName}" /></td>
<td colspan="5"><h:inputText id="customerName"
value="#{model.name}" readonly="true" /></td>
</tr>
</table>
</h:panelGroup>
</ice:form>
</composite:implementation>
</ui:composition>
</html>
Подложка для составного компонента:
package org.fuin.examples.soui.view;
import java.io.Serializable;
import javax.enterprise.context.Dependent;
import javax.inject.Inject;
import javax.inject.Named;
import org.apache.commons.lang.ObjectUtils;
import org.fuin.examples.soui.model.Customer;
import org.fuin.examples.soui.services.CustomerService;
import org.fuin.examples.soui.services.CustomerShortNameNotUniqueException;
import org.fuin.examples.soui.services.UnknownCustomerException;
@Named
@Dependent
public class CustomerSelectionBean implements Serializable {
private static final long serialVersionUID = 1L;
private Long id;
private String shortName;
private String name;
private CustomerSelectionListener listener;
@Inject
private CustomerService service;
public CustomerSelectionBean() {
super();
listener = new DefaultCustomerSelectionListener();
}
public Long getId() {
return id;
}
public void setId(final Long id) {
if (ObjectUtils.equals(this.id, id)) {
return;
}
if (id == null) {
clear();
} else {
clear();
this.id = id;
try {
final Customer customer = service.findById(this.id);
changed(customer);
} catch (final UnknownCustomerException ex) {
FacesUtils.addErrorMessage(ex.getMessage());
}
}
}
public String getShortName() {
return shortName;
}
public void setShortName(final String shortNameX) {
final String shortName = (shortNameX == "") ? null : shortNameX;
if (ObjectUtils.equals(this.shortName, shortName)) {
return;
}
if (shortName == null) {
clear();
} else {
if (this.id != null) {
clear();
}
this.shortName = shortName;
try {
final Customer customer = service
.findByShortName(this.shortName);
changed(customer);
} catch (final CustomerShortNameNotUniqueException ex) {
select();
} catch (final UnknownCustomerException ex) {
FacesUtils.addErrorMessage(ex.getMessage());
}
}
}
public String getName() {
return name;
}
public CustomerSelectionListener getConnector() {
return listener;
}
public void select() {
// TODO Implement...
}
public void clear() {
changed(null);
}
private void changed(final Customer customer) {
if (customer == null) {
this.id = null;
this.shortName = null;
this.name = null;
listener.customerChanged(null, null);
} else {
this.id = customer.getId();
this.shortName = customer.getShortName();
this.name = customer.getName();
listener.customerChanged(this.id, this.name);
}
}
public void setListener(final CustomerSelectionListener listener) {
if (listener == null) {
this.listener = new DefaultCustomerSelectionListener();
} else {
this.listener = listener;
}
}
public void setCustomerId(final Long id) throws UnknownCustomerException {
clear();
if (id != null) {
clear();
this.id = id;
changed(service.findById(this.id));
}
}
private static final class DefaultCustomerSelectionListener implements
CustomerSelectionListener {
@Override
public final void customerChanged(final Long id, final String name) {
// Do nothing...
}
}
}
Интерфейс слушателя для обработки результатов:
package org.fuin.examples.soui.view;
/**
* Gets informed if customer selection changed.
*/
public interface CustomerSelectionListener {
/**
* Customer selection changed.
*
* @param id New unique customer identifier - May be NULL.
* @param name New customer name - May be NULL.
*/
public void customerChanged(Long id, String name);
}
Пользователь (отдел биллинга)
Bean-объект invoice просто использует bean-компонент выбора клиента, внедряя его, и подключается к нему с помощью интерфейса слушателя:
package org.fuin.examples.soui.view;
import java.io.Serializable;
import javax.annotation.PostConstruct;
import javax.enterprise.context.SessionScoped;
import javax.enterprise.inject.New;
import javax.inject.Inject;
import javax.inject.Named;
@Named("invoiceBean")
@SessionScoped
public class InvoiceBean implements Serializable {
private static final long serialVersionUID = 1L;
@Inject @New
private CustomerSelectionBean customerSelectionBean;
private Long customerId;
private String customerName;
@PostConstruct
public void init() {
customerSelectionBean.setListener(new CustomerSelectionListener() {
@Override
public final void customerChanged(final Long id, final String name) {
customerId = id;
customerName = name;
}
});
}
public CustomerSelectionBean getCustomerSelectionBean() {
return customerSelectionBean;
}
public String getCustomerName() {
return customerName;
}
}
Наконец, в счете-фактуре XHTML используется составной компонент, связанный с внедренным компонентом поддержки:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:fuin="http://fuin.org/examples/soui/facelets"
xmlns:customer="http://java.sun.com/jsf/composite/customer">
<ui:composition template="/WEB-INF/templates/template.xhtml">
<ui:param name="title" value="#{messages.invoiceTitle}" />
<ui:define name="header"></ui:define>
<ui:define name="content">
<customer:selection-panel model="#{invoiceBean.customerSelectionBean}" />
</ui:define>
<ui:define name="footer"></ui:define>
</ui:composition>
</html>
Резюме
В заключение, за части пользовательского интерфейса, которые ссылаются на данные из других отделов, должен отвечать отдел, который доставляет данные. Любые изменения в коде обеспечения могут быть легко сделаны без каких-либо изменений в коде использования. Еще одним важным преимуществом этого метода является гармонизация пользовательского интерфейса приложения. Элементы управления и панели, отображающие одни и те же данные, всегда выглядят одинаково. Каждый отдел может также создать репозиторий своих предоставленных компонентов пользовательского интерфейса, что делает процесс разработки нового диалога столь же простым, как сбор правильных компонентов.



