如何为Vaadin两个版本的Grid实现可复用的同步扩展
Your goal of reusing synchronization logic across two incompatible Vaadin Grid versions is a classic case where adapter + delegation shines—this avoids the pitfalls of your three previous attempts while keeping your core logic DRY (Don’t Repeat Yourself).
Here's a step-by-step breakdown of the architecture:
1. Core Sync Library (SG)
First, extract all version-agnostic synchronization logic into a standalone core library. This library has no dependencies on any Vaadin version.
Key Components in SG:
SyncGridAdapter<T>: An interface that defines the "contract" between your core sync logic and the Vaadin Grid implementations. It abstracts away all Vaadin-specific operations the core needs:public interface SyncGridAdapter<T extends Selectable> { // Access the UI thread safely void accessUI(Runnable task); // Refresh all items in the grid void refreshAllItems(); // Register a listener for item double-clicks void onItemDoubleClick(Consumer<T> handler); // Get the class type of items in the grid Class<T> getItemClass(); // Get the static list of values for this item type List<T> getStaticItemList(); }SynchronizationManager<T>: The heart of your sync logic. It handles observer registration, cross-grid notifications, and data state management—all using the adapter to interact with the underlying Grid:public class SynchronizationManager<T extends Selectable> { private final SyncGridAdapter<T> adapter; public SynchronizationManager(SyncGridAdapter<T> adapter) { this.adapter = adapter; // Initialize data provider filters via the adapter setupDataProviderFilter(); // Register double-click handler via the adapter adapter.onItemDoubleClick(this::handleItemSelection); } private void setupDataProviderFilter() { // Your filter logic here (uses adapter to get static list) // Example: dataProvider.addFilter(bean -> !bean.isSelected()); } public void registerAsObserver() { ObserverStore.getInstance().get(adapter.getItemClass()).add(adapter); } public void unregisterAsObserver() { ObserverStore.getInstance().get(adapter.getItemClass()).remove(adapter); } private void handleItemSelection(T item) { item.setSelected(true); // Notify all observers to refresh ObserverStore.getInstance().get(adapter.getItemClass()) .forEach(obs -> obs.accessUI(() -> obs.refreshAllItems())); } public void reload() { adapter.accessUI(adapter::refreshAllItems); } }
2. SG8 Library (Vaadin 8 Implementation)
Your SynchronizedGrid8 inherits Vaadin 8's Grid<T> and implements the SyncGridAdapter<T> interface. It delegates all sync-related work to the SynchronizationManager:
public class SynchronizedGrid8<T extends Selectable> extends Grid<T> implements SyncGridAdapter<T> { private final SynchronizationManager<T> syncManager; private final Class<T> itemClass; public SynchronizedGrid8(Class<T> itemClass) { super(itemClass); this.itemClass = itemClass; // Initialize core sync logic with this adapter this.syncManager = new SynchronizationManager<>(this); // Vaadin 8-specific setup (data provider) ListDataProvider<T> dataProvider = new ListDataProvider(getStaticItemList()); setDataProvider(dataProvider); dataProvider.addFilter(bean -> !bean.isSelected()); } @Override public void attach() { super.attach(); syncManager.registerAsObserver(); } @Override public void detach() { super.detach(); syncManager.unregisterAsObserver(); } // Implement SyncGridAdapter methods for Vaadin 8 @Override public void accessUI(Runnable task) { getUI().access(task); } @Override public void refreshAllItems() { getDataProvider().refreshAll(); } @Override public void onItemDoubleClick(Consumer<T> handler) { addItemClickListener(clickEvent -> { if (clickEvent.getMouseEventDetails().isDoubleClick()) { handler.accept(clickEvent.getItem()); } }); } @Override public Class<T> getItemClass() { return itemClass; } @Override public List<T> getStaticItemList() { return StaticListStore.getInstance().get(itemClass); } }
3. SG10 Library (Vaadin Flow Implementation)
Similarly, SynchronizedGrid10 inherits Vaadin Flow's Grid<T> and implements SyncGridAdapter<T>. The only differences are the Vaadin-specific API calls—all core sync logic stays in the SynchronizationManager:
public class SynchronizedGrid10<T extends Selectable> extends Grid<T> implements SyncGridAdapter<T> { private final SynchronizationManager<T> syncManager; private final Class<T> itemClass; public SynchronizedGrid10(Class<T> itemClass) { super(itemClass); this.itemClass = itemClass; this.syncManager = new SynchronizationManager<>(this); // Vaadin Flow-specific data provider setup ListDataProvider<T> dataProvider = new ListDataProvider<>(getStaticItemList()); setDataProvider(dataProvider); dataProvider.addFilter(bean -> !bean.isSelected()); } @Override public void onAttach(AttachEvent event) { super.onAttach(event); syncManager.registerAsObserver(); } @Override public void onDetach(DetachEvent event) { super.onDetach(event); syncManager.unregisterAsObserver(); } // Implement SyncGridAdapter methods for Vaadin Flow @Override public void accessUI(Runnable task) { getUI().ifPresent(ui -> ui.access(task)); } @Override public void refreshAllItems() { getDataProvider().refreshAll(); } @Override public void onItemDoubleClick(Consumer<T> handler) { // Vaadin Flow uses a dedicated double-click listener addItemDoubleClickListener(event -> handler.accept(event.getItem())); } @Override public Class<T> getItemClass() { return itemClass; } @Override public List<T> getStaticItemList() { return StaticListStore.getInstance().get(itemClass); } }
Why This Works Better Than Your Previous Attempts:
- No multiple inheritance: We use interface implementation + delegation instead, which is fully Java-compliant.
- Clean separation of concerns: Core sync logic lives entirely in SG, while SG8/SG10 only handle Vaadin-specific adaptations (no business logic here).
- Minimal boilerplate: The adapter interface standardizes the operations the core needs, so you only write Vaadin-specific mappings once per version.
- Scalable: If you add a Vaadin 24 version later, you just create a new
SynchronizedGrid24class that implementsSyncGridAdapter—no changes needed to the core SG library.
内容的提问来源于stack exchange,提问作者keanni

