Take the 2-minute tour ×
Programmers Stack Exchange is a question and answer site for professional programmers interested in conceptual questions about software development. It's 100% free, no registration required.

I'm writing an open source library which handles hexagonal grids. It mainly revolves around the HexagonalGrid and the Hexagon class. There is a HexagonalGridBuilder class which builds the grid which contains Hexagon objects. What I'm trying to achieve is to enable the user to add arbitrary data to each Hexagon. The interface looks like this:

public interface Hexagon extends Serializable {

    // ... other methods not important in this context

    <T> void setSatelliteData(T data);

    <T> T getSatelliteData();
} 

So far so good. I'm writing another class however named HexagonalGridCalculator which adds some fancy pieces of computation to the library like calculating the shortest path between two Hexagons or calculating the line of sight around a Hexagon. My problem is that for those I need the user to supply some data for the Hexagon objects like the cost of passing through a Hexagon, or a boolean flag indicating whether the object is transparent/passable or not.

My question is how should I implement this?

My first idea was to write an interface like this:

public interface HexagonData {

    void setTransparent(boolean isTransparent);

    void setPassable(boolean isPassable);

    void setPassageCost(int cost);
}

and make the user implement it but then it came to my mind that if I add any other functionality later all code will break for those who are using the old interface.

So my next idea is to add annotations like

@PassageCost, @IsTransparent and @IsPassable

which can be added to fields and when I'm doing the computation I can look for the annotations in the satelliteData supplied by the user. This looks flexible enough if I take into account the possibility of later changes but it uses reflection. I have no benchmark of the costs of using annotations so I'm a bit in the dark here.

I think that in 90-95% of the cases the efficiency is not important since most users wont't use a grid where this is significant but I can imagine someone trying to create a grid with a size of 5.000.000.000 X 5.000.000.000.

So which path should I start walking on? Or are there some better alternatives?

Note: These ideas are not implemented yet so I did not pay too much attention to good names.

share|improve this question
add comment

6 Answers

For as ill-defined a problem as this, I prefer to write an actual application first, without particular reference to what the interface will be. Of course, you should think about what the interface will be as you go, but there's a single client so you're free to reorganize as necessary.

When you start the second application that would use the interface, start abstracting methods you need for the second application from those provided by the first. Iterate. The second application will inevitably force the first one to be changed to support a common interface. The third will force the second and first to change. Etc.

When you get to the point where adding a new application no longer requires the interfaces to change, then you are ready to publish your interface.

share|improve this answer
    
This is the Rule of the Three, right? I in fact already have a demo application but your answer looks like you think this is a Catch 22: I can't write an interface while there are no applications and I can't write applications while there is no interface. So there should be one interface for a start. –  Adam Arold Nov 12 '13 at 0:45
    
It's pointless to have a formal interface that will only be used once. You can write all the specs or interfaces you want, and it might not be a bad idea, but they'll be wrong when the rubber meets the road. It's similar to the military aphorism that no battle plan lasts past when the first shot is fired. –  ddyer Nov 12 '13 at 0:48
    
You don't get the point. –  Adam Arold Nov 12 '13 at 9:47
    
@AdamArold: I think he gets the point completely. You're worrying about forwards compatibility in an interface which hadn't even considered basic pathfinding yet! Building a good interface is not at all about sticking to your guns with your initial design, it's about providing an interface which covers all the necessities, and to do that you need a lot of domain knowledge. Best way to get that domain knowledge is to build things and extract the interface from that. –  Phoshi Nov 12 '13 at 14:39
add comment

My first idea was to write an interface [...] but then it came to my mind that if I add any other functionality later all code will break for those who are using the old interface.

Use an abstract class instead:

public interface Hexagon<T extends HexagonData>{
    T getData();
    void setData(T data);
}

public abstract class HexagonData{
    public boolean isTransparent(){ return false; }
    public boolean isPassable(){ return true; }
    public int getPassageCost(){ return 0; }
}

Don't use annotations unless you actually need metadata about the member that has been annotated.

share|improve this answer
    
I don't want the user to have to extend a class since he may have one which is already extending something. –  Adam Arold Nov 12 '13 at 9:47
    
Could you describe those classes? What purpose would they serve other than to provide Hexagon data? –  ajlane Nov 13 '13 at 1:39
    
I am not the user so I can't tell what an user will use. This is just a library. –  Adam Arold Nov 13 '13 at 9:37
1  
It is perfectly acceptable (and often preferred) to require the use of abstract classes in a library. You might want to refer to Microsoft's Design Guidelines for Class Libraries for more guidance. –  ajlane Nov 14 '13 at 0:30
add comment

If you want to add dataelements/properties/variables to your Hexagon without changing the other HexagonHandlers like HexagonalGridCalculator you can add dynamic properties like this:

public class Hexagon implements Serializable {

    // ... other methods not important in this context

    void setProperty(int id, object data);

    object getProperty(int id, object notFoundValue);
} 

and define constants for the dataelements/properties/variables

const int SatelliteData = 1;

If you later want to have additialal data just define new constants

const int Transparent = 2;
const int Passable = 3;
const int tPassageCost = 4;

which does not affect the other handlers.

share|improve this answer
    
This approach looks good, +1! The proposed structure needs some polishing though since it is a work of an enum: enum DataType { TRANSPARENT, PASSABLE, PASSAGE_COST, SATELLITE_DATA} and the whole thing can be used in a map: Map<DataType, Object> data; –  Adam Arold Nov 12 '13 at 14:28
1  
Enum would prevent further extension here. –  Basilevs Nov 12 '13 at 14:35
1  
@Basilevs: How? In order to take advantage of the new behaviour you'll need to recompile anyway, at which point you have the new enum definitions. –  Phoshi Nov 12 '13 at 14:40
    
You can use different ids in different libraries which could be even dynamically loaded. Why recompile? –  Basilevs Nov 12 '13 at 14:47
    
If you want to use functionality tied to an id you have to get the code anyway (like the class which implements shortest path search) so using ids will just make code unreadable. In this sense an enum is almost the same but more readable and less error-prone. I don't see why an enum would prevent extension. –  Adam Arold Nov 12 '13 at 14:55
add comment

I propose faceted approach similar to the one used in C++ standard library.

interface Hexagon {
  void setData(int id, T data); //these should only be used from Facet class
  Object getData(int id);

  sealed class IFacet<T> {
    private static int count = 0;
    private int _id = count++;
    public T getData(Hexagon hexagon) {
       return (T) hexagon.getData(_id);
    }
    public void setData(Hexagon hexagon, T data) {
       hexagon.setData(_id, data);
    }
  }
}

//Usages:

class CostManager {
  public readonly Facet<int> PASSAGE_COST = new Facet<int>();

  static void incrementCost(Hexagon hex) {
    PASSAGE_COST.setData(hex, PASSAGE_COST.getData(hex)+1);
  }
}

class TransparencyManager {
  public readonly Facet<boolean> TRANSPARENT = new Facet<boolean>();
  static void disable(Hexagon hex) {
    TRANSPARENT.setData(hex, false);
  }

}

Each Facet addresses exactly one property of Hexagon (as they use unique _id). If Hexagon.setData() and Hexagon.getData() are always used through a Facet class, the data stored will always have the type of corresponding Facet. This will prevent user from putting trying to write data of wrong type to any given id.

To illustrate the kind of errors this approach protects from consider an example based on @k3b answer:

  static void configure(Hexagon hex) {
     hex.setData(DataType.TRANSPARENCY, "very transparent"); // compiler accepts this just fine, strings are objects, aren't they?
  }

  static void processHexagon(Hexagon hex) {
    boolean data = (Boolean)hex.getData(DataType.TRANSPARENCY); // compiler accepts this, Boolean are objects too
  }

  static void doSomeJob(Hexagon hex) {
     configure(hex);
     // working
     // more work
     // ...
     processHexagon(hex); //oops, you've got runtime error
  }

Now consider the same code with Facets:

  static void configure(Hexagon hex) {
     TRANSPARENT.setData(hex, "very transparent"); // compiler slaps you
  }

  static void processHexagon(Hexagon hex) {
    boolean data = TRANSPARENT.getData(hex); // no downcast!
  }

  static void doSomeJob(Hexagon hex) {
     configure(hex);
     // working
     // more work
     // ...
     processHexagon(hex); //no runtime errors, just compile-time ones
  }

As you can see, the code is even less verbose, as there is no need for downcasts now.

share|improve this answer
1  
Sorry, but I don't understand how it works from the example. Where does the incrementCost method go? where does the constant TRANSPARENT go? what is a sealed class? Why should get/setData should be private? –  Adam Arold Nov 12 '13 at 14:25
    
Updated. Sealed class can't be extended. msdn.microsoft.com/en-us/library/88c54tsw.aspx This way we avoid dangers of downcasting in IFacet.getData() - if every storage is done with a Facet instance, the should be no InvalidCastException. –  Basilevs Nov 12 '13 at 14:32
    
BTW, do you prefer Java? I could adapt this then. –  Basilevs Nov 12 '13 at 14:38
    
No, I can understand it now but I still don't see how that will help me. Every Hexagon can contain a different value for all of those facets. BTW that downvote is not by me. –  Adam Arold Nov 12 '13 at 14:56
    
The idea is that hexagon could keep a Dictionary<int, Object> which allows its user to store objects of different types (adressed by different ids, note count++ part). This is very similar to @k3b answer but more typesafe. –  Basilevs Nov 12 '13 at 15:01
show 2 more comments

I would build it like a DTO. Have interfaces with getters and setters for the data you need and do your processing in external systems. Maybe provide a default Hex they could extend if they wanted to. The result would be more entity component system than object oriented but that is kind of the point of component systems the flexibility to extend in ways you didn't anticipate up front.

share|improve this answer
add comment

As far as I can tell, you want to provide an API allowing users of your library to build complex objects (Hexagons), having a set of attributes that potentially can be extended in the future (transparent, passable, passage cost etc).

Design issues like that are targeted by Builder pattern:

the builder pattern uses another object, a builder, that receives each initialization parameter step by step and then returns the resulting constructed object at once... Builders are good candidates for a fluent interface...

For your case, implementation could look as follows (assuming newAttribute being added in later version library):

public class HexagonBuilder {

    public Hexagon build() { /*...*/ }

    public HexagonBuilder transparent(boolean isTransparent) { /*...*/; return this }

    public HexagonBuilder passable(boolean isPassable) { /*...*/; return this }

    public HexagonBuilder passageCost(int cost) { /*...*/; return this }

    public HexagonBuilder newAttribute(Object attribute) { /*...*/; return this }
}

As you can see, old code would compile just fine with newer version of your library, because it's only difference is compatible - it just doesn't invoke newAttribute method.

Note that for old code not only compile, but also work smoothly with newer version library, you will need to provide an appropriate default behavior for client code that doesn't set new attribute.

For the sake of completeness, consider taking a look at other creational patterns - Factory, Prototype etc, although per details provided in the question so far, Builder looks like the best fit.

share|improve this answer
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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