Encapsulation: To break or not to break, that is the question.

Started by
4 comments, last by Nick of ZA 15 years, 4 months ago
Greetings I'm in the beginning stages of implementing an A* pathfinder for the RPG portion of my game (Roguelike, with graphics). I've opted for a simple 2D array to store my terrain grid. The array indices point to Location objects, which contain all necessary per-tile game data such as terrain type, movement cost etc. I have a bit of concern though. I need to use an interface to specify required functionality for implementing A*. This is because I will be using the A* code in a discrete portion of my game where the entire map setup is substantially different: it is hex-based/strategy vs quad-based/RPG in nature. Now, I realise that I can set my tiles ("Locations") to implement an interface, eg. public interface AStarNode { function setParent(parentNode:AStarNode):void; function getParent():AStarNode; function setTravellable(travellable:Boolean):void; function isTravellable():Boolean; } ...But I also realise that if I access the tiles directly (i.e. vs. referencing them via methods in the Map object that holds them in its array), then I am breaking encapsulation. And that I should then actually have the Map implement an interface instead which provides similar methods but also take coords to the tile I want to perform these ops on, e.g.: public interface AStarSearchable { function setParentOf(row:int, col:int, parentNode:AStarSearchable):void; function getParentOf(row:int, col:int):*; function setTravellable(row:int, col:int, travellable:Boolean):void; function isTravellable(row:int, col:int):Boolean; } From you more experienced folks: Would the former be bad move? I'm typically an advocate of maintaining good standards, and of course encapsulation is a cornerstone of OO design. But... who here would take that path if it proved more fruitful? Bearing in mind as well Knuth's saying about premature optimisation... in essence, Should I be concerned about simply referencing the tiles directly, from the outset? -Nick
Advertisement
I think people often misunderstand encapsulation, both what it is and what the purpose is.

To sum it up, it means keep your hands off my data. It does not mean you can't use my data what it means is that all changes to the data go through a single point (the encapsulating class). The reason for this is that if you get bad data in that class you know for a fact where the changes were made. This makes finding the bug a lot easier because you don't have to go searching through a lot of code, you just have to look in the class.

So feel free to look at all the data you want, just don't allow it to be changed except through the class.

theTroll
Troll,

Thanks for this, got me thinking more clearly.

Anyone else got anything to add?
There are several definitions of encapsulation floating around:

  • Encapsulation is sometimes used as a synonym for information hiding, or as the technique used to achieve information hiding (wikipedia). The point and purpose of information hiding is to keep the class easy to maintain, for instance by allowing a change in its implementation without breaking the code that uses it. With this definition, even code which merely reads data from the class will have to be rewritten when the implementation changes, and so breaks encapsulation.

  • Encapsulation is sometimes used to describe the process of sticking together things within an object. That is, as long as you're sticking together data and behavior, you're encapsulating them. With this definition, there's no problem with accessing data in any way you wish: it's still encapsulated.

  • Encapsulation is sometimes used to implement a gatekeeper: an object which is responsible for a piece of state or data. This is the definition that TheTroll follows. With this definition, reading the data is always allowed, but changing it should happen through other methods.


I tend to believe that the sense which is most used in OOP is the first, because it has the same objective as OOP (keep the software as easy to refactor as possible, so that reuse and extension remain easy).

But it's up to you, given your project aims and needs, to decide which definition (or definitions) you wish to respect, and respect them. As for the question of A* pathfinding, it's much easier for A* to work with a definition of a graph, such as:

public interface AStarGraph{  // Return the heuristic for going from A to B  // (the smallest possible total distance)  function getHeuristic(start:int, end:int): Number;  // Get the list of passable nodes around a node  // (associated with their distance to that node)  function getAdjacent(start:int): Array;}


Once you use this interface (which would probably be implemented as some kind of adapter on top of the map class) the A* algorithm becomes a simple priority-queue-based exploration of a graph which will remain independent of map class implementation changes.
Quote:Original post by Nick of ZA
Anyone else got anything to add?


One thing i would add about the importance of encapsulation is that how encapsulated a class it is inversely proportional (i think that’s the right way round) to how much code will break if the class is changed.

So lets say you have a class which keeps a value you may need, in a member variable. If you make it public and allow anyone acces to this value, then people will use it. Ok fine, but now let’s say you want to change that value to a calculation. You can't do that without breaking the code of the people who used the public version of the value.

If it was encapsulated (in the first place) via some kind of get function (i.e declared private and have a public member function to get to it). Then you could change the implementation of the get function and the clients would be none the wiser.
ToohrVyk

That was a very useful clarification, thank you. I read it yesterday and have got going again at (nearly) full speed again since having that clarified.

Cheers,

-Nick

This topic is closed to new replies.

Advertisement