24. oktober 2007 - 14:48Der er
18 kommentarer og 1 løsning
Magi med Generiske klasser
Hej eksperter
Jeg har brug for hjælp til at udarbejde en generic struktur for et graf bibliotek.
Ideen er, at biblioteket skal kunne arbejde med forskellige input af Time og Place(T og P).
Desuden ville jeg gerne kunne udskifte hvilken Node og Edge instans man benytter til at danne grafen og samtidig sørge for, at man kun kan inputte edge og node klasser, der nedarver fra specifikke klasser, som garanterer mig specifik funktionalitet.
Ved at bruge generics kan man eksempelvis lave en pæn AddNode metode i graf klassen, der tager en Node som input. Se koden herunder. Jeg har klippet det væsentligste ind.
public class MyNode<Edge, T, P> where Edge : MyEdge<MyNode<Edge, T, P>, T, P> where P : Place where T : IComparable<T> { List<Edge> adjacencyList = new List<Edge>(); ... }
public class MyEdge<Node, T, P> where P : Place where T : IComparable<T> where Node : MyNode<MyEdge<Node, T, P>, T, P> { Node fromNode; Node toNode; ... }
public sealed class Graph<Node, Edge, P, T> where P : Place where T : IComparable<T> where Node : MyNode<Edge, T, P> where Edge : MyEdge<Node, T, P> { public void AddNode(Node N) { ... } public void AddEdge(Edge e) { ... } }
Mit problem illustreres bedst ved et forsøg på at initialisere graf klassen: Graph<MyNode<MyEdge<MyNode<MyEdge<MyNode............
Node klassen skal kende edge klassen og edge klassen skal kende node klassen og så videre.
What to do? Kan man lave noget, der er smartere end min opbygning.
Er det overhovedet generics jeg skal have gang i for at kunne udskifte node og edge instanserne i grafen. Synes bare det er så overskuelig en opbygning, når addmetoderne blot skal have tage Edge eller Node som parameter.
Den mest logiske kosntruktion er vel at man starter med at opbygge en graf ved først at definere noderene, og så i anden omgang at koble dem sammen 2-og-2 med edges.
Jeg har leget med en idé tilsvarende dit forslag. Grunden til at jeg gerne vil "udskifte", hvilke Nodes og Edges grafen bruger et for at undgå typecasting.
Jeg vil eksempelvis gerne understøtte CapacitatedEdges. Denne type er helt magen til Edge, men tilføjer instans variablen, double capacity.
Den oplagte løsning er derfor at lade CapacitatedEdge nedarve fra Edge.
Graf klassen vil godt kunne sluge CapacitatedEdge klassen som input til AddEdge metoden, men når en Algoritme skal til at køre på en graf instans, så skal der typecastes, hver gang man skal have fat i en Edge med en accesor. Ellers kan man ikke få fat i capacitets variablen i den nedarvede klasse.
Kan du se mit problem. Det er derfor jeg gerne vil bruge generics til at vælge ved compile time hvilken slags nodes og edges, der skal bruges.
Mit problem med cirkulær gensidig afhængighed af Nodes og Edges skyldes en idé om, at man skulle sørge for, at brugere af graf biblioteket skulle forhindres i at benytte visse kombinationer af Nodes og Edges. Så hvis man lagde sig fast på 1 type Node, så giver det kun mening at oprette en tilsvarende bestemt delmængde af Edge instanserne.
Jeg er ikke længere overbevist om det giver mening, men for egen lærings skyld kunne det være sjovt at vide, hvordan man kunne begrænse, hvilke Node-Edge kombinationer, der var mulige at smide ind i grafen.
Hvis grafen skal være en adjacency graph (Edges gemmes i Nodes), så skal Node klassen indeholde en liste af edges.
class Node<TTime, TPlace> { List<Edge<TTime,Place>> adjacencylist; ... public void AddEdge(Edge<TTime,TPlace> edge) { ... } }
Så jeg er stadig ikke sluppet af med typecasting. Jeg har defineret listen og add metoden til at acceptere base klassen for Edge. Så når jeg arbejder med MyEdges, og eksempelvis skal returnere en Enumerator eller bare adjacencylisten direkte fra Noden, så er jeg nødt til at typecaste for at få MyEdges ud.
Det samme gør sig gældende for Edges, der gemmer 2 x Node<TTime, TPlace>. Hvis jeg arbejder med MyNodes, så skal jeg igen caste.
Hvad dælan gør man så?
Det er i essensen den cirkulær gensidighed jeg nævnte i det oprindelige spørgsmål. Man kan ikke give Node typen, som argument til Edge og samtidig give Edge som argument til Node.
Så ender man nemlig i Graph<MyNode<MyEdge<MyNode<MyEdge<MyNode............
Dough. What to do. Jeg finder nogle flere point frem til gode forslag
class Node { ... public Edge<TTime, TPlace> this[int index] { get { return adjacencyList[index]; } } }
Og så prøv at oprette en fromNode og en toNode af typen MyNode fra main og forbind dem med en MyEdge. Da vi arbejeder med adjacencyList repræsentation, gemmes Edges i fra-Nodes. Så hvis du kalder fromNode[0] skal du få returneret din Edge.
Nej, jeg har b3estem ikke givet fortabt - det er faktisk et interessant problem at arbejde med. Men jeg begynder at tro at det er umuligt. Uanset hvordan jeg drejer og vender den er jeg altid endt tilbage med en cirkulær reference. Jeg har dog endnu ikke kunnet gennemskue om det nødvendigvis må være sådan, eller om der er en eller anden lille smart variation som fixer det.
1) En Adjacency Graph som basis klasserne, sådan at alle klasser som beuger duine generics klasser automatisk også er en AG. Eller: 2) Du ønsker at have kunne indsætte en AG i din template.
Det nedenstående kode er et eksempel på hvordan dette kunne gøres. Der er godt nok en enkelt typecast, men den er gemt inde i AddEdge() funktionen i MyNode klassen - jeg ved ikke om du mener at det er problematisk?
Jeg er nødt til at kigge mere på din kode før jeg kan melde tilbage. Der er desværre ikke tid til det før søndag aften.
Hvis man kan begrænse typecast situationer til sine add metoder og holde accessors og indexers fri for typecasts, så er jeg fint tilfreds.
Angående dine 2 spørgsmål, så er jeg ikke helt 100 % med på hvad du spørger om.
1) Det er naturligvis kun Adjacency Graphs og evt. klasser, der nedarver fra AdjacencyGraph, der skal beytte Nodes, der internt gemmer adjacencylister. Hvis man vil bruge matrix repræsentation til sin graph, så skal man have fat i en anden type Graph og dermed en anden variant af Node.
Edge som jeg ser det er uafhængig af Node / Graph repræsentationen. Den skal bare udtrykke en sammenhæng mellem 2 nodes og er ligeglad med om man gemmer den i graph klassen eller i Node klassen.
Det må altså kræves, at basis funktionaliteten for AdjacencyGraph og AdjacencyNode passer sammen. AddEdge metoden i graph skal vide, at den skal gemme en Edge i Noden og Noden skal understøtte en sådan liste.
Edge er taget ud af den sammenhæng. En edge skal bare kende en from og toNode.
Man burde nok rettere have et hierarki som følger:
Node { id, place } MatrixNode : Node { } // For other graph type AdjacencyNode : Node { adjacencyList } TimePlaceNode : AdjacencyNode { time }
Så fik jeg aligevel kigget på dit seneste foreslag. Du har undgået det cirkulære ved at indføre, at Nodes og Edges passer sammen i par.
Så hvis man vil bruge en MyNode, skal man også bruge en MyEdge. Dermed er Node-Edge valget fastlagt ved initialiseringen af grafen.
Det er bestemt en løsning, men fjerner desværre også smidigheden i opbygningen.
Om man benytter en Edge, MyEdge eller en CapacitatedEdge er underordnet for valget af Node. Jeg vil gerne understøtte muligheden for at tilføje flere properties på både Edge og Node og det ideelle design vil være, at graph klassen begrænser, hvilke klasser man kan nedarve fra mht. både Nodes og Edges, men ellers åbner for frit slag indenfor under træerne i hierarkiet.
Så en Adjacency Graph kan udgøres af Nodes, der kun har et navn og et id(+ adjacency List), mens Edges kan være specialiserede Edges, med mange ekstra properties, som ligger længere nede i hierarkiet.
Tilsvarende kan en graph også være en meget specialiseret Node, der kun kædes med simple base Edges uden ekstra properties.
Til test/udvikling af algoritmer vil det være smart at kunne teste grafen med forskellige Node - Edge kombinationer og dermed forskellige måder at gemme data på.
Jeg havde håbet på stor smidighed i strukturen, men er måske nødt til at gå på kompromis.
Det hele kommer ned til hvordan man lavet en generisk struktur, hvor Node skal kende Edge typen og Edge skal kende Node typen.
Kunne man bare komme over den hurdle, så ville man kunne sende Edge og Node typen ind i graph og derfra videre ind i den valgte Node og Edge type.
Jeg er bange for, at du "snyder" lidt i din seneste løsning. En BaseEdge kan ingenting = har ingen properties. Den skulle gerne gemme en fra og til Node af den korrekte type. Ellers kan man ikke søge igennem grafen.
Det er nødvendigt, at kunne se hvor neighbour edges ender ellers vil en query på en node's adjacency/neighbour edges bare give dig en masse edge instanser, der ikke fortæller dig noget.
Hvis du indfører en From og ToNode i BaseEdge klassen, så er man tilbage til det oprindelige problem. BaseEdge afhænger af BaseNode og BaseNode afhænger af BaseEdge.
Hvad hedder problemstillingen? Nogle idéer. Jeg er vel ikke den første i verden, der har dette problem. Synes bare, at alt hvad jeg kan finde i Google er noget med cykliske referencer til projekter i Visual Studio. Men kan ikke rigtig finde nogen hits på noget med generics.
Læste et svar til en gut, der havde problemer med cykliske referencer mellem sine projekter og han fik at vide, at cykliske referencer opstod pga. dårligt design. Det er vel også mit problem, dårligt design? Ellers må man jo leve med de typecasts. Ved ikke hvor dyre de vil være, men det må jeg jo teste/acceptere.
Kan det være et design pattern man skal have gang i?
Synes godt om
Ny brugerNybegynder
Din løsning...
Tilladte BB-code-tags: [b]fed[/b] [i]kursiv[/i] [u]understreget[/u] Web- og emailadresser omdannes automatisk til links. Der sættes "nofollow" på alle links.