but then why use OO? what does OO offer that this method does not? In fact one could argue that this method offers better decoupling since it separates the rendering from the game Data.
OO is better because bundling data and code together is better, because code is data, and I have no idea what I'm babbling about. </strawman>
The real advantage of OOP, as used today in Java/C++, is instantiation. Instead of having procedures working on global variables, you have procedure working on local variables, including complex data structures.
Instantiation took some time to get widespread adoption. Originally, even local variables were actually global, scoped variables, thus forbidding even recursive calls (see earlier versions of FORTRAN). Programming languages since use a stack to instantiate their local variables (and return pointers), thus enabling recursion. The instantiation of more complex data structures (arrays, user defined structs…) followed.
Ironically, instantiation took some time to become ubiquitous in the C language even though it fully supported it from the very beginning, with a stack and user-defined compound types. Case in point: Lex/Yacc, which use global names (and state) by default.
Now however, instantiation is so pervasive (global variables are assumed evil by default), that we don't even call it OO any more. We just call it good practice.
> Now however, instantiation is so pervasive (global variables are assumed evil by default), that we don't even call it OO any more. We just call it good practice.
This is another one of my pet peeves... If it's global (a static resource), make it a global. Local variables for static resources make code so much less readable. The only argument against globals is testing, and that's only an argument because common OO languages have no support for resetting global "objects"! Solution: Just don't use OO syntax in the first place - it's wrong. Just write init() and exit() functions.
No, the newer argument against globals is testing, but that's mostly a side effect of the older issue, that globals limit composability/reusability, which was the main objection to globals before TDD became a popular religion.
The "reusability" argument is just as wrong... True reusability (without any changes) is not possible in most cases anyway, and furthermore I don't see a reason why surrounding some static object (which can only exist once in a program, like a sound module, a network module, etc) with braces and a "class" keyword would somehow increase this vague idea of reusability.
It's only a syntactic change. It's not changing what should actually happen. It's just making it less readable. How in the world can that be an improvement on any frontier?
> I don't see a reason why surrounding some static object (which can only exist once in a program, like a sound module, a network module, etc) with braces and a "class" keyword would somehow increase this vague idea of reusability.
Often, because the assumption that it can exist only once is wrong; this is particularly true of instances of some descriptive class of interface to an external hardware resource, which covers all of your examples.
> It's only a syntactic change.
No, it's usually not; while the syntactic change is usually necessary, it usually isn't the whole difference between the desirable modular code and the bad global-using code that should be made, and quite often if you aren't writing it as a global resource in the first place, you never make the other wrong decisions that would need to be changed.
Using globals may occasionally be justified (either as an optimization or, even more rarely, as “correct” from a fundamental design perspective), but most often it's a symptom of sloppy thinking.
> Often, because the assumption that it can exist only once is wrong; this is particularly true of instances of some descriptive class of interface to an external hardware resource, which covers all of your examples.
The key here is how to understand the word "can". It's "only" a design decision! Of course you can do almost anything on a computer. However, most programs don't make sense with two sound modules or network modules or graphics modules. So "can" here means, "it absolutely makes no sense, and I'm never realistically going to instance two or more of this thing". (And if I really want to do that later, 1 in 1000 times, I'll just edit the code).
> it usually isn't the whole difference between the desirable modular code and the bad global-using code that should be made, and quite often if you aren't writing it as a global resource in the first place, you never make the other wrong decisions that would need to be changed.
Give an example: I don't think there are any. I can easily give you some bad things that happen when avoiding globals to represent static resources: Much more input and output arguments to type. Then, the syntactically ugly, useless, meaningless Singleton. I've seen it many times, and it is the best proof that it made no sense to avoid the global in the first place, and it even potentially leads to nondeterministic initialization.
And more importantly, an occasional reader has a much harder time browsing through the code because she never knows where the local variables are pointing at.
Of course, I would use a global whenever appropriate. But I would try and limit the number of entry points to that global (for instance by limiting its scope, or putting a big fat comment about how we're supposed to use it), so I don't end up making implicit data dependencies spaghetti.
One should always avoid unnecessary dependencies. But I don't use special syntax or complicated scope rules to achieve that. This only makes the code more complex.
No -- I just don't depend. I don't reference the global where it's not needed. Simple :-)
>The real advantage of OOP, as used today in Java/C++, is instantiation. Instead of having procedures working on global variables, you have procedure working on local variables, including complex data structures.
You can have the same in functional programming, by creating closures on demand.
You use OO because your interviewer wants an example that demonstrates you understand it.
Secondly, OO is more easily extensible. If the company wants the game to support other size boards, bit wise storage has to be ripped out. 90% of the work in 90% of the jobs is writing clear, easily maintainable and extensible code, not maximizing performance. Frankly, if I interview someone who answers a question like OP, i pass thinking their code will be a premature optimization nightmares.
Except, this is not premature optimization. Chess programs are a domain the author is familiar with. He goes out of his way to explain why-- based on his experience-- chess code should be written with space-optimization in mind.
Secondly, if you want to support non-standard boards, his solution requires a small tweak (depending on the language): long -> bigint and some parameter to denote board size.
Yea, you are describing code that’s more difficult to enhance and maintain.
Your pieces and board objects can have serialize/deserialize methods that write/read them to/from bitboards for most efficient storage for whatever variant of chess you want, while still having your code in an OO design that’s easier to understand and extend. And allow you to demonstrate the skills the gdmn interviewer wanted you to demonstrate, which is the actual point of the excercise!
I'd rather fail the interview than work for the one who failed me in this case.
Assuming I am asked to design something in a domain I am familiar with, where an OOP-looking solution is not a good approach, I will simply go with a good approach instead. If this triggers a discussion, wonderful. If the interviewer assumes I don't understand OOP, shame on them for not explicitly asking for an OOP design in the first place. And if the interviewer suggests I should go for an OOP design instead of my solution, I will contradict them on the spot —politely. I may comply for the sake of the exercise, but will repeat and insist that there are better approaches to this class of problems —politely.
More generally, I am highly sceptical of claims that OO designs are in general easier to understand, maintain, and extend (this includes this chess example, where I believe the bit-board approach is competitive even for variable board sizes). They're often not, if only because being OO for the sake of it tends to generate more code. More code to understand, more code to debug, more code to extend… you get the idea.
I also tend to believe OOP is not the best approach for most problem domains, possibly even including GUI (I have yet to study this in detail). I'd rather not work for shops that insist on using OOP everywhere.
The real advantage of OOP, as used today in Java/C++, is instantiation. Instead of having procedures working on global variables, you have procedure working on local variables, including complex data structures.
Instantiation took some time to get widespread adoption. Originally, even local variables were actually global, scoped variables, thus forbidding even recursive calls (see earlier versions of FORTRAN). Programming languages since use a stack to instantiate their local variables (and return pointers), thus enabling recursion. The instantiation of more complex data structures (arrays, user defined structs…) followed.
Ironically, instantiation took some time to become ubiquitous in the C language even though it fully supported it from the very beginning, with a stack and user-defined compound types. Case in point: Lex/Yacc, which use global names (and state) by default.
Now however, instantiation is so pervasive (global variables are assumed evil by default), that we don't even call it OO any more. We just call it good practice.