开发者

Question regarding the "Tell, don't Ask" idea

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:

The first approach is to

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.

The second approach is about

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!).

So...

I am aware that if I intend to make a library, I am probably stuck with the first approach,开发者_C百科 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.


CurtainDog is right, invoke Yagni and figure out what you actually need right now, implement that, then make sure it's not going to prevent any features that may be desirable in the future.

The second approach violates the principle that superclasses should not know about each of its subclasses. I think the element you're missing is that the base class can define template methods, like getBoardSize, countRedPieces, countBlackPieces, that can be overridden by subclasses and your superclass has code that uses those template methods, therefore telling its subclasses what to do, but not how to do it.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜