There is this famous quote that says
Procedural code gets information then makes decisions. Object-oriented code tells objects to do things. — Alec Sharp
The subject of the post is precisely about that.
Let's assume we are developing a game in which we have a Game
where there is a Board
.
When facing the problem of deciding which methods are we going to implement on the Board
class, I always think of two different ways:
populate the Board
class with getSize()
, getPieceAt(x, y)
, setPieceAt(x, y, piece)
. This will seem reasonable and is what is generally found in libraries/frameworks. The Board
class has a set of internal features that wants to share and has a set of methods that will allow the client of the class to control the class as he wishes. The client is supposed to ask for the things he needs and to decide what to do. If he wants to set all board pieces to black, he will "manually" iterate over them to accomplish that goal.
looking for Board
's dependent classes, and see what they are "telling" it to do. ClassA
wants to count how many pieces are red, so I'd implement a calculateNumberOfRedPieces()
. ClassB
intends to clear all the pieces on the Board
(set all of them to NullPiece
, for example), so I'd add a clearBoard()
method to the Board
class. This approach is less general, but allows for a lot more flexibility on other aspects. If I "hide" Board
behind an IBoard
interface, and decide that I'd want to have a board with infinite size, doing in the first way, I'd be stuck, as I'd have to iterate over an infinite number of items! On the other hand, in this way, I could do fine (I could, for instance, assume all pieces are null other than the ones contained in a hashtable!).
I am aware that if I intend to make a library, I am probably stuck with the first approach, as it is way more general. On the other hand, I'd like to know which approach to follow when I am in total control of the system that'll make use of the Board
class -- when I am the one who is going to also design all the classes that'll make use of the Board
. Currently, and in the future (won't the second approach raise problems if later I decide to add new classes that are dependent on the Board
with different "desires"?).
The quote is really warning you away from data structures that don't do anything with the data they hold. So your Board class in the first approach might be able to be done away with and replaced by a generic collection.
Regardless, the Single Responsibility Principle still applies, so you need to treat the second approach with caution.
What I would do is invoke YAGNI (you aren't gonna need it) and try to see how far I could go using a generic collection rather than a Board class. If you find that later you do need the Board class its responsibility will likely be much more clear by then.
Let me offer the contrarian point of view. I think the second approach has legs. I agree with the single responsibility principle, but it seems to me that there's a defensible single mission/concern for a Board
class: Maintaining the playing field.
I can imagine a very reasonable set of methods such as getSize()
, getPiece(x,y)
, setPiece(x, y, color)
, removePiece(x, y)
, movePiece(x1,y1,x2,y2)
, clear()
, countPieces(color)
, listPiecePositions(color)
, read(filename)
, write(filename)
, etc. that have a congent and clear shared mission. The handling of those board-management concerns in an abstracted way would allow other classes to implement game logic more cleanly, and for either Board
or Game
to be more readily extended in the future.
YAGNI is all well and good, but my understanding is that it urges you to not start building beautiful edifices with the hope that one day they'll be usefully occupied. For example, I wouldn't spend any time working toward the future possibility of an infinite playing surface, a 3D playing surface, or a playing surface that can be embedded onto a sphere. If I wanted to take YAGNI very seriously, I wouldn't write even straightforward Board
methods until they were needed.
But that doesn't mean I would discard Board as a conceptual organization or possible class. And it certainly doesn't mean that I wouldn't put any thought at all into how to separate concerns in my program. At least YAGNI in my world doesn't require you start with the lowest-level data structures, little or nothing by way of encapsulation, and a completely procedural approach.
I disagree with the notion that the first approach is more general (in any useful way), or what appears to the the consensus that one should "just see how far you can get without abstracting anything." Honestly, that sounds like how we solved eight queens. In 1983. In Pascal.
YAGNI is a great guiding principle that helps avoid a lot of second system effect and similar bottoms-up, we-can-do-it-so-we-should mistakes. But YAGNI that's crossed the Agile Practice Stupidity Threshold is not a virtue.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With