Sidst opdateret: 1. juni 2004
Dette dokument forudsætter kendskab til grundlæggende begreber i EMF (Eclipse Modeling Framework). Der er flere oplysninger om EMF i Oversigt over EMF.
Introduktion
Vis EMF-objekter i JFace-fremvisere
Implementeringsklasser for elementudbydere
Revidér EMF-modeller vha. kommandoer
Brug kodegeneratoren EMF.Edit
Hvis du har en model baseret på EMF, som du har genereret kode til ved hjælp af EMF-generatoren, og du er klar til at tilføje brugergrænseflader og kode til modellen, kan du skyde genvej ved at bruge EMF.Edit-strukturen.
EMF.Edit er en Eclipse-struktur, som indeholder generiske klasser, der kan genbruges, til at opbygge editorer til EMF-modeller. Den indeholder:
Dette dokument indeholder en oversigt over de grundlæggende begreber i forbindelse med strukturen og generatorværktøjet i EMF.Edit. Du kan finde mere detaljerede oplysninger i dokumentationen til strukturklasserne, som beskriver deres funktionsmåde og funktionalitet i detaljer.
Strukturen til brugergrænsefladen i Eclipse (JFace) indeholder et sæt fremviserklasser (f.eks. TreeViewer, TableViewer), der kan genbruges, og som viser strukturerede modeller. I stedet for at kræve, at de viste modelobjekter overholder en bestemt protokol (dvs. implementerer en bestemt grænseflade), arbejder JFace-fremvisere med et hvilket som helst slags objekt (dvs. en hvilken som helst java.lang.Object-underklasse). Det er muligt, fordi fremviserne opnår adgang til modelobjekterne via et adapterobjekt, der kaldes en indholdsudbyder, i stedet for at navigere direkte til modelobjekterne.
Hver fremviserklasse bruger en indholdsudbyder, der implementerer en bestemt udbydergrænseflade. En TreeViewer-fremviser bruger f.eks. en indholdsudbyder, der implementerer følgende grænseflade:
public interface ITreeContentProvider ... { public Object[] getChildren(Object object); public Object getParent(Object object); ... }
Den grundlæggende struktur vises i følgende diagram:
Set fra TreeViewer-fremviserens side fremviser den et hierarki med objekter (som den kalder elementer) på skærmen. Den får leveret alle objekterne undtagen inputobjektet (rodobjektet) ved at kalde getChildren() på dens indholdsudbyder.
Andre typer fremvisere håndteres på samme måde, dog sådan at indholdsudbyderen skal implementere den specifikke grænseflade til hver enkelt fremviser. Selvom alle fremvisergrænsefladerne er forskellige, kan en indholdsudbyder ofte implementere flere af dem samtidig, så den samme indholdsudbyderklasse bruges til mange slags fremvisere.
EMF.Edit-strukturen stiller en generisk implementeringsklasse for indholdsudbydere til rådighed, som kan bruges til at levere indhold til EMF-modeller. Klassen AdapterFactoryContentProvider implementerer indholdsudbydernes grænseflader ved at delegere til EMF-adapterer, som ved, hvordan der navigeres i modelobjekterne (elementerne) for fremviserne. For eksempel vil den EMF-adapterklasse, der bruges til en træstrukturfremviser, implementere følgende EMF.Edit-grænseflade:
public interface ITreeItemProvider { public Collection getChildren(Object object); public Object getParent(Object object); ... }
Bemærk ligheden mellem denne grænseflade og indholdsudbyderens ITreeContentProvider-grænseflade, der er beskrevet ovenfor. AdapterFactoryContentProvider implementerer indholdsudbyderens grænseflade ved at finde og derefter delegere til en adapter (der implementerer elementudbydergrænsefladen) for det krævede element. Skiftet i terminologi fra objekter til elementer er bevidst: Fra fremviserens synspunkt er de elementer, ikke objekter.
EMF-billedet ser sådan ud:
Bemærk: Du kan automatisk generere en ItemProviderAdapterFactory og ItemProvider-klasser for en given EMF-model ved at bruge den generator, der leveres med EMF.Edit-strukturen. Det beskrives nærmere senere.
AdapterFactoryContentProvider er konstrueret med en adapterfabrik, som i lighed med en hvilken som helst anden EMF-adapterfabrik tjener til at oprette og finde adaptere med en bestemt type (i dette tilfælde elementudbydere (ItemProviders). Indholdsudbyderen behandler en anmodning som f.eks. getChildren() ved at kalde adapt(element) på ItemProviderAdapterFactory, hvilket vil oprette eller returnere adapteren (ItemProvider'en) for det angivne element. Derefter delegerer den blot til metoden getChildren() for den krævede grænseflade (i dette tilfælde ITreeItemProvider).
Metoden getChildren() i AdapterFactoryContentProvider ser omtrent sådan ud:
public Object[] getChildren(Object object) { ITreeItemContentProvider adapter = (ITreeItemContentProvider) adapterFactory.adapt(object, ITreeItemContentProvider.class); return adapter.getChildren(object).toArray(); }
Samme mønster bruges til alle indholdsudbydermetoderne. Som nævnt tidligere gør AdapterFactoryContentProvider ikke andet end blot at delegere indholdsudbydermetoder til den specifikke elementudbyder (adapter), som ved, hvordan anmodningen skal behandles.
I metoden getChildren() ovenfor overføres et simpelt java.lang.Object-objekt til adapterFactory.adapt() (ikke et org.eclipse.emf.ecore.EObject-objekt). Det er et vigtigt træk ved EMF.Edit-strukturen. Strukturen er omhyggeligt designet, så en giver plads til fremvisninger af EMF-modeller, der måske er forskellige fra modellen selv (f.eks. fremvisninger, der undertrykker objekter eller inkluderer fantomobjekter). For at give mulighed for denne blanding af EMF- og ikke-EMF-objekter indeholder strukturens basisklasse til adapterfabrikker en implementering af adapt(), fungerer omtrent sådan her:
public Object adapt(Object object, Object type) { if (object instanceof Notifier) return this.adapt((Notifier)object, type); else return object; }
Hvis det angivne objekt ikke er et EMF Notifier-objekt[1] , returneres objektet selv. Med dette design kan en elementudbyder, som vil tilføje ikke-EMF-elementer til en fremvisning, blot returnere et hvilket som helst ønsket ikke-EMF-objekt (f.eks. fra sin getChildren()-metode). Så længde det returnerede objekt implementerer fremviserens krævede elementudbydergrænseflade (f.eks. ITreeItemProvider), vil det blive behandlet som alle de andre EMF-elementer.
Dette aspekt ved designet viser, hvorfor vi foretrækker at kalde udbyder/adapterklasserne for elementudbydere i stedet for adaptere. I mindre basale programmer vil fremvisningsmodellen (dvs. den, der leveres af fremviserens indholdsudbyder) ofte være en blanding af "ægte" (EMF) modelobjekter, hvis elementudbydere tilfældigvis også er (EMF) adaptere, og "fantomobjekter", hvis elementudbydere er objekterne selv. Således er alle adapterne også elementudbydere, men det modsatte er ikke nødvendigvis tilfældet.
I de foregående afsnit beskrev vi, hvordan JFace-fremvisere bruger en indholdsudbyder til at hente deres indholdselementer. En lignende fremgangsmåde bruges til at hente etiketbilledet og teksten til de elementer, der vises af en fremviser. I stedet for at anmode elementer selv om deres etiket bruger en fremviser et andet objekt kaldet en etiketudbyder (i lighed med en indholdsudbyder). F.eks. delegerer en TreeViewer-forekomst til et objekt, som implementerer grænsefladen ILabelProvider, for at hente etiketterne til elementerne i træstrukturen.
EMF.Edit-strukturen bruger samme mekanisme til at implementere etiketudbydere for EMF-modeller, som den bruger til at levere indholdet. En generisk implementeringsklasse for elementudbydere, AdapterFactoryLabelProvider (der fungerer præcis som AdapterFactoryContentProvider), delegerer ILabelProvider-grænsefladen til elementudbyderne for modellen (de samme elementudbydere, der leverer indholdet). Det udvidede billede ser sådan ud:
Indholds- og etiketudbyderen kan (og vil normalt) delegere til samme adapterfabrik og som følge heraf til de samme elementudbydere. Som det gælder for indholdsudbyderen, er det elementudbyderne, der faktisk udfører arbejdet.
Med EMF.Edit-udbyderklasserne kan en bruger f.eks. konstruere en TreeViewer-forekomst for en EMF-model på denne måde:
myAdapterFactory = ... treeViewer = new TreeViewer(); treeViewer.setContentProvider(new AdapterFactoryContentProvider(myAdapterFactory)); treeViewer.setLabelProvider(new AdapterFactoryLabelProvider(myAdapterFactory));
Denne TreeViewer-forekomst kan derefter vises i et editorvindue på den sædvanlige måde (fastlagt af JFace).
Du tænker måske, at alt dette lyder ret banalt, men det er fordi, vi kun har vist, hvordan der delegeres til noget andet (dvs. adapterfabrikken). Vi har ikke implementeret nogen af metoderne endnu, vi har kun uddelegeret dem. Implementeringen af metoder understøttes imidlertid af EMF.Edit og inkluderer en kodegenerator, som genererer det meste af koden til elementudbyderen og fabrikken for dig. Før vi går i dybden med det, vil vi se på, hvordan elementudbydere udfører deres opgave.
Som vist i det foregående afsnit udføres det egentlige arbejde med at levere indhold til EMF-modeller af de elementudbyderadaptere, der er tilknyttet til modellen. Antallet og typen af ItemProvider-adaptere i det foregående diagram var med vilje ikke præciseret. Det skyldes, at EMF.Edit-strukturen understøtter to forskellige mønstre for elementudbyderadapterne:
Elementudbyderne til en given model kan implementeres med et af disse mønstre eller med en blanding af dem begge.
Det første mønster giver en til en-overensstemmelse mellem hvert objekt i modellen og dets adapter. Hver adapter har en pointer (kaldet et mål) for det og kun det objekt, som det "adapterer".
Billedet ser sådan ud:
Som det ses, fordobler dette mønster antallet af objekter i programmet og giver derfor kun mening i programmer, hvor der er brug for, at forekomsterne kan antage yderligere tilstande. Det er derfor, det kaldes et Stateful-mønster (mønster med tilstande).
En bedre fremgangsmåde, hvor de fleste af de ekstra objekter undgås, er singleton-mønsteret. Med dette mønster bruges en enkelt elementudbyderadapter til alle objekterne med samme type. Det ser sådan ud:
På dette billede har objekterne adapterpointere som sædvanlig, men elementudbyderne (som deles) har ingen pointer tilbage til objekterne. Hvis du kan huske den grænseflade for træstrukturelementudbydere, som vi så på i afsnittet om indholdsudbydere, har du måske bemærket, at alle metoderne skulle have et ekstra argument (et objekt):
public interface ITreeItemProvider { public Collection getChildren(Object object); public Object getParent(Object object); ... }
Objektargumentet blev tilføjet til hver elementudbydergrænseflade netop for at understøtte dette mønster. I Stateful-tilfældet vil dette objekt altid være det samme som adapterens mål.
Du undrer dig måske over, at vi ikke understøtter et "ægte" Singleton-adaptermønster - dvs. kun én adapter til alle objekterne? Svaret er, at det er et muligt mønster, der er kompatibelt med EMF.Edit-strukturen[2] , men det frarådes, fordi en fuldstændig dynamisk implementering er svær at tilpasse (uden en masse rodede instanceof()-kontroller), selvom den er simpel. Alternativet med at have elementudbyderklasser med type, hvis overtagelseshierarki afspejler modellens, giver et praktisk afsendelsespunkt for implementering af ren, objektorienteret fremvisningskode til en model.
Indtil nu har vi kun vist, hvordan man får vist EMF-modeller ved hjælp af indholds- og etiketudbydere. En anden egenskab ved EMF.Edit-strukturen er dens mulighed for kommandobaseret redigering af en model. Her bruges begrebet "redigering" for ændringer, der ikke kan fortrydes, modsat simpel "skrivning" af en model.
EMF.Edit-grænsefladen EditingDomain bruges til at give redigeringsadgang til en EMF-model. En anden EMF.Edit-implementeringsklasse, AdapterFactoryEditingDomain, fungerer i lighed med indholds- og etiketudbyderne ved, at den delegerer sin implementering til elementudbyderne (via ItemProviderAdapterFactory):
Som vist giver det også adgang til en kommandostak, som alle ændringer af modellen vil blive foretaget igennem. Redigeringsdomænet stiller to grundlæggende services til rådighed:
Et redigeringsdomæne kan opfattes som modellens udbyder til ændringer/skrivning, mens indholds- og etiketudbyderne er udbyderne til fremvisning/læsning. Her er hele billedet:
Vi vil nu se på et simpelt eksempel på ændring af en model.
Antag, at klassen Company har en en til mange-reference ved navn departments til klassen Department. Vi kunne skrive følgende kode for at fjerne en forekomst af departments fra klassen Company (f.eks. for at implementere en slettehandling i editoren):
Department d = ... Company c = ... c.getDepartments().remove(d);
Denne simple kode foretager blot ændringen.
Hvis vi i stedet bruger EMF.Edit-kommandoen remove (org.eclipse.emf.edit.command.RemoveCommand) til at fjerne forekomsten af departments, vil vi skrive følgende:
Department d = ... Company c = ... EditingDomain ed = ... RemoveCommand cmd = new RemoveCommand(ed, c, CompanyPackage.eINSTANCE.getCompany_Departments(), d); ed.getCommandStack().execute(cmd);
Der er flere fordele ved at slette departments-forekomsten på denne måde:
Når kommandoer bruges konsekvent på denne måde, aktiveres alle de funktioner, som EMF.Edit-strukturen tilbyder.
I det foregående eksempel oprettede vi en Fjern-kommando (RemoveCommand) ved hjælp af et simpelt nyt kald. Det fungerede fint, men er ikke let at genbruge, da kodefragmentet udfører en helt bestemt ting, nemlig fjernelse af en afdeling fra et firma. Hvis vi i stedet vil skrive en Slet-handling, der kan genbruges og slette et hvilket som helst objekt, kan vi gøre det ved at bruge redigeringsdomænet til at hjælpe med at udføre opgaven.
Grænsefladen EditingDomain indeholder bl.a. en kommandofabriksmetode, createCommand(), der kan bruges i stedet for new til at oprette kommandoer:
public interface EditingDomain { ... Command createCommand(Class commandClass, CommandParameter commandParameter); ... }
Når du vil bruge denne metode til at oprette en kommando, skal du først oprette et CommandParameter-objekt, angive kommandoparametre for objektet og derefter kalde oprettelsesmetoden med den ønskede kommandoklasse (f.eks. RemoveCommand.class) og parametrene.
I stedet for at få klienter til at udføre alt dette bruger vi en konvention, hvor der leveres statiske create()-bekvemmelighedsmetoder i hver kommandoklasse. Med den statiske create()-metode kan du oprette og udføre en Fjern-kommando på denne måde:
Department d = ... EditingDomain ed = ... Command cmd = RemoveCommand.create(ed, d); ed.getCommandStack().execute(cmd);
Som det ses, er det blot en lille syntaksændring (RemoveCommand.create() i stedet for new RemoveCommand). Men der er nogle grundlæggende forskelle. Bortset fra redigeringsdomænet har vi kun overført ét argument (det objekt, der skal fjernes) i stedet for tre argumenter tidligere. Bemærk, hvordan dette stykke kode nu kan bruges til at fjerne en hvilket som helst type objekt. Ved at delegere oprettelsen af kommandoen til redigeringsdomænet lader vi det udfylde de manglende argumenter.
Vi fortsætter med kaldet RemoveCommand.create() for at vise, hvordan det hele fungerer. Som nævnt tidligere er den statiske create()-metode blot en bekvemmelighedsmetode, der delegerer noget i denne stil til redigeringsdomænet:
public static Command create(EditingDomain domain, Object value) { return domain.createCommand( RemoveCommand.class, new CommandParameter(null, null, Collections.singleton(value))); }
AdapterFactoryEditingDomain modtager derefter anmodningen og sender den videre til en elementudbyder ved hjælp af standarddelegeringsmønsteret (som AdapterFactorContentProvider delegerede getChildren() tidligere):
public Command createCommand(Class commandClass, CommandParameter commandParameter) { Object owner = ... // get the owner object for the command IEditingDomainItemProvider adapter = (IEditingDomainItemProvider) adapterFactory.adapt(owner, IEditingDomainItemProvider.class); return adapter.createCommand(owner, this, commandClass, commandParameter); }
Bemærk: Hvis du ser på den egentlige createCommand()-metode, vil du bemærke, at den faktisk er betydeligt mere kompliceret. Det skyldes, at den er designet til også at håndtere sletning af samlinger af objekter på samme tid og andre ting. Overordnet er det så også alt, hvad den gør.
Metoden createCommand() bruger et ejerobjekt til at få adgang til den elementudbyder, der skal delegeres til (dvs., at ejeren bruges i adapterFactory.adapt()-kaldet). I vores eksempel er ejeren firmaobjektet (company), dvs. den overordnede for den afdeling (department), der skal fjernes. Redigeringsdomænet finder ejeren ved at kalde metoden getParent() på elementudbyderen til det objekt, der skal slettes.
Resultatet af alt dette er, at metoden createCommand() til sidst kaldes på elementudbyderen til den overordnede for det objekt, der skal fjernes (dvs. CompanyItemProvider til company c i det oprindelige kodefragment). CompanyItemProvider kan så implementere createCommand() på følgende måde:
public class CompanyItemProvider ... { ... public Command createCommand(final Object object, ..., Class commandClass, ...) { if (commandClass == RemoveCommand.class) { return new RemoveCommand(object, CompanyPackage.eINSTANCE.getCompany_Departments(), commandParameter.getCollection()); } ... } }
Det ville give det ønskede resultat, men der er en bedre måde.
Hver elementudbyderklasse (som også er en EMF-adapter) er en udvidelse af en EMF.Edit-bekvemmelighedsbasisklasse, ItemProviderAdapter, som bl.a. leverer en standardimplementering af createCommand(). Den implementerer createCommand() for alle de standardkommandoer, som stilles til rådighed af EMF.Edit-strukturen, ved at kalde et par simple metoder (som bruges til mere end dette formål), der er implementeret i elementudbyderunderklasserne. Det er et eksempel på et designmønster ved brug af skabelonmetoden.
For at få vores RemoveCommand-eksempel til at fungere behøver CompanyItemProvider blot at implementere følgende metode:
public Collection getChildrenFeatures(Object object) { return Collections.singleton(CompanyPackage.eINSTANCE.getCompany_Departments()); }
Som vist returnerer denne metode en eller flere egenskaber (i dette eksempel kun departments-referencen), som bruges til at refererer til objektets underordnede. Når metoden er kaldt, vil standardimplementeringen af createCommand() afgøre, hvilken egenskab der skal bruges (hvis der returneres mere end én), og oprette RemoveCommand med den rigtige.
En anden fordel ved at oprette kommandoer via et redigeringsdomæne er, at det giver os mulighed for at tilkoble forskellige underklasser eller helt andre implementeringer af standardkommandoer og få standardeditorer til umiddelbart at opfange dem. Antag f.eks., at vi vil foretage noget ekstra oprydning, hver gang vi fjerner en afdeling fra et firma. Den enkleste måde at gøre det kunne være at oprette en underklasse for RemoveCommand ved navn RemoveDepartmentCommand på denne måde:
public class RemoveDepartmentCommand extends RemoveCommand { public void execute() { super.execute(); // do extra stuff ... } }
Den del er let nok.
Hvis vores editor bruger den statiske RemoveCommand.create()-metode (som kalder editingDomain.createCommand()) i stedet for new RemoveCommand(), så kan vi let erstatte RemoveCommand-standarden med vores RemoveDepartmentCommand ved at tilsidesætte createCommand() i elementudbyderen på denne måde:
public class CompanyItemProvider ... { ... public Command createCommand(final Object object, ...) { if (commandClass == RemoveCommand.class) { return new RemoveDepartmentCommand(...); } return super.createCommand(...); } }
Faktisk er erstatningen endnu lettere, hvis den kommando, vi vil specialisere, er en af de foruddefinerede kommandoer (såsom RemoveCommand), fordi standardimplementeringen af createCommand() afsender oprettelsen af hver kommando til kommandospecifikke bekvemmelighedsmetoder på en måde, der ligner denne:
public Command createCommand(final Object object, ... { ... if (commandClass == RemoveCommand.class) return createRemoveCommand(...); else if (commandClass == AddCommand.class) return createAddCommand(...); else ... }
Vi kunne derfor have oprettet vores RemoveDepartmentCommand på en enklere måde ved blot at tilsidesætte createRemoveCommand() i stedet for createCommand()-metoden selv:
protected Command createRemoveCommand(...) { return new RemoveDepartmentCommand(...); }
Sammenfattende gælder, at redigeringsdomænet er vores middel til tilpasning af kommandoparametre, herunder kommandoklassen selv, så vi let kan styre en hvilken som helst redigeringskommandos funktionsmåde i vores model.
Vi har endnu ikke talt om ændringsbeskeder. Hvordan får vi opfrisket fremviserne, efter en kommando har ændret noget i modellen? Svaret er, at det fungerer i kraft af en kombination af EMF-adapterstandardbeskeder og en mekanisme til opfriskning af fremvisere leveret af EMF.Edit.
Under konstruktionen registrerer en AdapterFactoryContentProvider sig selv som lytter (dvs. org.eclipse.emf.edit.provider.INotifyChangedListener) for dens adapterfabrik (som implementerer grænsefladen org.eclipse.emf.edit.provider.IChangeNotifier). Adapterfabrikken overfører derefter sig selv til enhver elementudbyder, som den opretter, så den kan være modellens centrale ændringsbeskedfunktion. AdapterFactoryContentProvider registrerer også (i metoden inputChanged()) den fremviser, den leverer indhold til, så den kan opdatere sin fremviser, når den modtager en ændringsbesked).
I følgende diagram vises, hvordan en ændring i en EMF-model (f.eks. ændring af et firmanavn) foregår via adapterfabrikken tilbage til modellens fremvisere.
Hver gang et EMF-objekt skifter tilstand, kaldes metoden notifyChanged() på alle objektets adaptere, herunder elementudbyderne (i dette tilfælde CompanyItemProvider). Metoden notifyChanged() i elementudbyderen er ansvarlig for at afgøre, om hver aktivitetsbesked skal overføres til fremviseren, og hvilken type opdatering det i så fald skal resultere i.
Det gør den ved at pakke interessante beskeder ind i ViewerNotification, en simpel implementering af grænsefladen IViewerNotification. Denne grænseflade udvider den grundlæggende Notification-grænseflade på denne måde:
public interface IViewerNotification extends Notification { Object getElement(); boolean isContentRefresh(); boolean isLabelUpdate(); }
Disse metoder angiver, hvilket element i fremviseren der skal opdateres, om indholdet under det pågældende element skal opfriskes, og om elementets etiket skal opdateres. Eftersom elementudbyderen fastlægger et objekts underordnede og etiket, skal den også afgøre, hvordan fremviseren opdateres effektivt.
Metoden notifyChanged() i klassen CompanyItemProvider ser sådan ud:
public void notifyChanged(Notification notification) { ... switch (notification.getFeatureID(Company.class)) { case CompanyPackage.COMPANY__NAME: fireNotifyChanged(new ViewerNotification(notification, ..., false, true)); return; case CompanyPackage.COMPANY__DEPARTMENT: fireNotifyChanged(new ViewerNotification(notification, ..., true, false)); return; } super.notifyChanged(notification); }
I denne implementering medfører en ændring af attributten name en etiketopdatering, og en ændring af referencen department medfører en opfriskning af indholdet. Eventuelle andre ændringsbeskeder har ingen indflydelse på fremviseren.
Metoden fireNotifyChanged() er en bekvemmelighedsmetode i klassen ItemProviderAdapter (basisklassen for alle elementudbyderadapterne), som blot videresender beskeden til adapterfabrikken[3] . Dernæst afsender adapterfabrikken (ændringsbeskedfunktionen) beskeden til alle dens lyttere (i dette tilfælde blot træstrukturfremviserens indholdsudbyder). Til sidst opdaterer indholdsudbyderen fremviseren efter anvisningen i beskeden.
EMF-modeller bindes ofte sammen af referencer på tværs af modellerne. Når du vil udvikle et program, der skal redigere eller vise objekter, som spænder over mere end én EMF-model, har du brug for en adapterfabrik, der er i stand til at tilpasse de forenede objekter fra de to (eller flere) modeller.
Du vil ofte allerede have adapterfabrikker for de enkelte modeller og behøver blot at få dem kombineret med hinanden. Til det formål kan du bruge en anden EMF.Edit-bekvemmelighedsklasse, ComposedAdapterFactory.
En sammensat adapterfabrik (ComposedAdapterFactory) bruges til at levere en fælles grænseflade til andre adapterfabrikker, som den blot delegerer sin implementering til.
Du ville skrive kode i lighed med følgende for at konfigurere en sammensat adapterfabrik:
model1AdapterFactory = ... model2AdapterFactory = ... ComposedAdapterFactory myAdapterFactory = new ComposedAdapterFactory(); myAdapterFactory.addAdapterFactory(model1AdapterFactory); myAdapterFActory.addAdapterFActory(model2AdapterFactory); myContentProvider = new AdapterFactoryContentProvider(myAdapterFactory); ...
Bemærk: I Øveprogram: Generér en EMF-model findes en trinvis vejledning i generering af en EMF-model samt en EMF.Edit-editor.
EMF.Edit-kodegeneratoren kan ud fra en EMF-modeldefinition fremstille et fuldt funktionsdygtigt editorværktøj, som giver dig mulighed for at få vist forekomster af modellen ved brug af flere fælles fremvisere og tilføje, fjerne, klippe, kopiere og indsætte modelobjekter eller ændre objekterne i et standardegenskabsark, alle med fuld understøttelse af Fortryd og Gentag.
EMF.Edit-generatoren fremstiller komplette, fungerende plugins, der inkluderer følgende:
Editoren bør fungere, når den er genereret. Den åbnes, men fungerer muligvis ikke, som du forventer, fordi generatorens standardvalg måske ikke er egnet til din model. Det bør imidlertid være ret nemt at justere den genererede kode et par steder og hurtigt få en grundlæggende, fungerende editor op at køre.
Nedenfor ser vi nærmere på nogle af de mere interessante genererede klasser.
Den genererede ItemProviderAdapterFactory er en simpel underklasse for den genererede AdapterFactory-klasse, du fik, da du genererede din EMF-model.
Bemærk: Den genererede EMF-adapterfabrik opretter adaptere ved afsendelse til en typespecifik create()-metode, som underklasser (såsom ItemProviderAdapterFactory) skal tilsidesætte. EMF-adapterfabrikken (f.eks. ABCAdapterFactory) bruger en anden genereret klasse (ABCSwitch) til at implementere afsendelsen effektivt.
Når Stateful-mønsteret anvendes, opretter adapterfabrikken metoder ved blot at returnere et nyt objekt som her:
class ABCItemProviderAdapterFactory extends ABCAdapterFactoryImpl { ... public Adapter createCompanyAdapter() { return new CompanyItemProvider(this); } ... }
Hvis Singleton-mønsteret bruges i stedet, holder adapterfabrikken også styr på singleton-forekomsten og returnerer den for hvert kald:
protected DepartmentItemProvider departmentItemProvider; public Adapter createDepartmentAdapter() { if (departmentItemProvider == null) { departmentItemProvider = new DepartmentItemProvider(this); } return departmentItemProvider; }
For hver klasse i modellen genereres en tilsvarende elementudbyderklasse. De genererede elementudbydere blandes i alle de grænseflader, der kræves for at understøtte standardfremviserne, kommandoerne og egenskabsarket:
public class DepartmentItemProvider extends ... implements IEditingDomainItemProvider, IStructuredItemContentProvider, ITreeItemContentProvider, IItemLabelProvider, IItemPropertySource { ... }
Hvis en modelklasse er en rodklasse (dvs. ikke har nogen eksplicit basisklasse), udvides den genererede elementudbyder fra EMF.Edits basisklasse for elementudbydere, ItemProviderAdapter:
public class EmployeeItemProvider extends ItemProviderAdapter ...
Hvis modelklassen i stedet overtages fra en basisklasse, udvides den genererede elementudbyder fra basiselementudbyderen på denne måde:
public class EmployeeItemProvider extends PersonItemProvider ...
For en klasse med flere overtagelser udvides den genererede elementudbyder fra den første basisklasses elementudbyder (som i situationer med enkelt overtagelse), og den implementerer udbyderfunktionen for resten af basisklasserne.
Hvis du ser på de genererede elementudbyderklasser, vil du bemærke, at en stor del af deres funktion i virkeligheden implementeres i elementudbyderbasisklassen. De vigtigste funktioner i de genererede elementudbyderunderklasser er:
Den genererede editor og modelguide viser, hvordan alle de andre genererede dele skal samles med JFace-standardkomponenter, så der fremstilles en fungerende editor.
Modelguiden kan bruges til at oprette nye ressourcer med modellens type. Hvis du derimod allerede har en ressource, der er oprettet på anden vis, kan du importere den i arbejdsområdet, starte editoren for den og dermed helt omgå modelguiden.
[1] Notifier er basisgrænsefladen i EMF til objekter, der kan registrere adaptere og sende beskeder til dem. Den udvides af EObject, som er basisgrænsefladen for alle modelobjekter.
[2] I virkeligheden styres EMF-adapterfabrikker af overtagelse, så du kan vælge at bruge en basisadapter til at håndtere underklasser på et hvilket som helst niveau i modellen, hvor EObject er det mest ekstreme.
[3] Ud over adapterfabrikken, som fungerer som ændringsbeskedfunktionen for fremvisere, kan en ItemProviderAdapter også have andre (direkte) lyttere, som også kaldes i ItemProviderAdapter.fireNotifyChanged().