0

I have a system i've written on paper and am trying to write this into C# in a easy to use manner.

I want a series of connected waypoints (where by waypoints can be connected to any number of other waypoints).

But waypoints can either just be an arbritrary point along the path or an actual object in the system with some behaviour (these are usually the targets and starting points).

So i've setup basic data structure:

Waypoint¬ //struct
   List<Edge> Connections
   Vector3 Position
Edge¬ // struct
   Waypoint A
   Waypoint B
   Bool HasAnchor  //for curving to waypoint
   Vector3 AnchorPoint //bezier anchor point

Now the confusion i have is i have 2 types of objects that need to also count as way points. These are a "Producer" which produces agents for them to travel along the way points, And a "consumer" which is also a way point that receives and removes agents from the system.

And example would be an electrical grid, where by a Producer is the power generator, then some appliance like a TV is the consumer. But they are still way points on a graph.

How do i make a type like a power generator or a TV both a waypoint and a the type that they are?

2

First off, taking a shot in the dark, your "struct" comments suggest you are maybe contemplating using structs for this: this won't work, because it would define a recursive data structure: you must define your graph with reference types (e.g. classes and (though not technically reference-types) interfaces).


If it is possible, the nicest way of achieving your goal (as I understand it) would be to expose the concept of a Waypoint through an interface, such as this one:

interface IWaypoint
{
    IList<Edge> Connections { get; } // made this an IList`1 for good measure.
    Vector3 Position { get; }
}

class Edge
{
    public IWaypoint A { get; } // edge defined in terms of IWaypoints
    public IWaypoint B { get; }
    public bool HasAnchor { get; }
    public Vector3 AnchorPoint { get;}
}

Essentially you define your graph in terms of edges and IWaypoints: you could also abstract away the edges (e.g. so you could define roads, and power-lines, and pipes). Now you can have your Waypoint, Producer, and Consumer classes all implement this interface.

class Waypoint : IWaypoint
{
    public IList<Edge> Connections { get; }
    public Vector3 Position { get; }
}

You could have Produces and Consumers inherit directly from Waypoint, which would perhaps simplify the implementation, but it is possible that it wouldn't make sense (at least not without making the members in Waypoint virtual).

class PowerGenerator : Waypoint // extend Waypoint
{
    // stuff that is specific to PowerGenerator
}

More fiddly, but conceptually simpler/cleaner, is just to have each type re-implement the members as necessary.

class PowerGenerator : IWaypoint // implement IWaypoint
{
    IList<Edge> Connections { get; }
    Vector3 Position { get; }

    // stuff that is specific to PowerGenerator
}

The key benefit of all this, is that any code that needs to process Waypoints can instead handle the IWaypoint interface, which means you can provide it with any type that implements it. A graph-traversal method, for example, might have a signature like this:

/// <summary>
/// Given a start and end waypoint, computes the route with the fewest edges between the two
/// </summary>
Edge[] FindRoute(IWaypoint start, IWaypoint end);

You can pass anything that implements IWaypoint as the start or the end. It also makes your code extensible, because anyone can provide a new class that implements IWaypoint, and it will work with your code. As always in programming, interfaces are just a tool, and it's down to you as the system designer to select a tool based on your understanding of the domain: we can't answer this problem for you without knowing what sort of operations you are going to perform on your waypoint graphs.


Some rambling follows...

One 'problem' with sort of abstraction is that it is hard to provide ad-hoc behaviour based on the type of the Waypoint (e.g. with the current (simple) IWaypoint interface, it will be a nightmare to provide custom behaviour for different types of waypoint (e.g. if you need to make a decision based on whether it is a consumer or a produce). Adding more interface, and adding more to your interfaces, can go some way to resolve this, but some problems simply can't be expressed by having your types provide a common interface, and these problems warrant Unions, which unfortunately C# lacks so I won't go into detail.

If you are new to concept of interfaces, then you may want to do some reading; however, most explanations of polymorphism are (in my opinion) terrible, and fail completely to convey the value of abstract types such as interfaces: the real value (which can be seen in your example) comes from defining a single set of behaviours (the methods on the interface) on which the consuming code (the code which dynamically calls the methods on the interfaces) and implementing code (the classes which implement the interface) both depend, without creating an dependency between these two types of component (e.g. you can change either without changing the other. In your instance, you can provide many implementations of IWaypoint without modifying your graph-traversal code, and you can write general-purpose graph-traversal code without knowing anything about the types it will actually be operating on at runtime.

|improve this answer|||||
  • Thanks for the suggestion, i too also had similar ideas with interfaces but I was unsure on how to check whether the waypoint was actually a consumer or producer type since they were type IWaypoint in the graph. I'm currently guess i would need to give it an enum to check against so i can then cast it to the producer/consumer type to access it's public methods or something. – WDUK Apr 20 '18 at 18:41
  • You can just do a type check, which have become very clean in recent versions of C#. For example, if (start is Consumer c) { /* c is the waypoint cast as type Consumer */ }. This is the sort of issue I was referring to in the penultimate paragraph, but so long as you only have a few "special" types (e.g. Producer` and Consumer (these could be interfaces as well)) then it shouldn't be too painful. – VisualMelon Apr 20 '18 at 19:22

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.