104

The basic idea behind OOP is that data and behavior (upon that data) are inseparable and they are coupled by the idea of an object of a class. Object have data and methods that work with that (and other data). Obviously by the principles of OOP, objects that are just data (like C structs) are considered an anti-pattern.

So far so good.

The problem is I have noticed that my code seems to be going more and more in the direction of this anti-pattern lately. Seems to me that the more I try to achieve information hiding between classes and loosely coupled designs, the more my classes get to be a mix of pure data no behavior classes and all behavior no data classes.

I generally design classes in a way which minimizes their awareness of other classes' existence and minimizes their knowledge of other classes' interfaces. I especially enforce this in a top-down fashion, lower level classes don't know about higher level classes. E.g.:

Suppose you have a general card game API. You have a class Card. Now this Card class needs to determine visibility to players.

One way is to have boolean isVisible(Player p) on Card class.

Another is to have boolean isVisible(Card c) on Player class.

I dislike the first approach in particular as it grants knowledge about higher level Player class to a lower level Card class.

Instead I opted for the third option where we have a Viewport class which, given a Player and a list of cards determines which cards are visible.

However this approach robs both Card and Player classes of a possible member function. Once you do this for other stuff than visibility of cards, you are left with Card and Player classes which contain purely data as all functionality is implemented in other classes, which are mostly classes with no data, just methods, like the Viewport above.

This is clearly against the principal idea of OOP.

Which is the correct way? How should I go about the task of minimizing class interdependencies and minimizing assumed knowledge and coupling, but without winding up with weird design where all the low level classes contain data only and high level classes contain all the methods? Does anyone have any third solution or perspective on class design which avoids the whole problem?

P.S. Here's another example:

Suppose you have class DocumentId which is immutable, only has a single BigDecimal id member and a getter for this member. Now you need to have a method somewhere, which given a DocumentId returns Document for this id from a database.

Do you:

  • Add Document getDocument(SqlSession) method to DocumentId class, suddenly introducing knowledge about your persistence ("we're using a database and this query is used to retrieve document by id"), the API used to access DB and the like. Also this class now requires persistence JAR file just to compile.
  • Add a some other class with method Document getDocument(DocumentId id), leaving DocumentId class as dead, no behavior, struct-like class.
RokL
  • 2,421
  • 21
    Some of your premises here are completely wrong, which is going to make answering the underlying question very difficult. Keep your questions as concise and opinion-free as you can and you will get better answers. – pdr Apr 02 '14 at 12:53
  • 31
    "This is clearly against the principal idea of OOP" - no, its not, but its a common fallacy. – Doc Brown Apr 02 '14 at 12:55
  • 2
    It is, because data and behaviour are completely and entirely separated. I might as well be using structs and global functions. – RokL Apr 02 '14 at 12:57
  • 7
    I guess the problem lies in the fact that there have been different schools for "Object Orientation" in the past - the way it was originally meant by people like Alan Kay (see http://geekswithblogs.net/theArchitectsNapkin/archive/2013/09/08/oop-as-if-you-meant-it.aspx), and the way is was taught in context of OOA/OOD by those people from Rational (http://en.wikipedia.org/wiki/Object-oriented_analysis_and_design). – Doc Brown Apr 02 '14 at 13:09
  • 1
    Related: http://programmers.stackexchange.com/questions/224941/anemic-domain-models-what-sort-of-methods-a-domain-object-might-need – Doc Brown Apr 02 '14 at 13:30
  • Also related: http://stackoverflow.com/questions/7177904/worker-vs-data-class – pdr Apr 02 '14 at 13:53
  • 27
    This is a very good question, and well posted -- contrary to some of the other comments, I'd say. It shows clearly how naive or incomplete most advices on how to structure program are -- and how difficult it is to do it, and how unreachable a proper design is in many situations no matter how much one strives to do it right. And although an obvious answer to the specific question is multi-methods, the underlying problem of designing persists. – Thiago Silva Apr 02 '14 at 13:58
  • You can even enhance your data objects by making them immutable. – Ingo Apr 02 '14 at 14:49
  • 3
    Stay out of all the so called "Best practices" and don't listen to blind rules. Usually they make things much harder. There are too many "wise" people with all they dirty stuff. Instead try to learn and discover for yourself. – luke1985 Apr 02 '14 at 16:40
  • 1
    http://en.wikipedia.org/wiki/Data_transfer_object – Robert Harvey Apr 02 '14 at 17:35
  • 5
    Who says beheaviourless classes are an anti-pattern? – James Anderson Apr 03 '14 at 03:49
  • 1
    Martin Fowler and Eric Evans, among others. http://www.martinfowler.com/bliki/AnemicDomainModel.html –  Apr 03 '14 at 08:17
  • 5
    Behavior-less classes are not an anti-pattern. They are simply not very object-oriented, according to some people's definition of object-oriented. That does not make them less useful. If you attempt to make code more and more general purpose, it becomes less and less likely that data and behavior can stay together in a class, because general purpose methods operate on any data. It comes down to object-oriented versus functional. Each has advantages and disadvantages. – Frank Hileman Apr 04 '14 at 17:11
  • 1
    I think behavior-less classes are a "bad smell" because so many are likely to implement anemic models. In the case of the answer to this, though, I'd posit that - like the answer-er says - a card needn't know anything, so if I came across a card-game app that generally had logic and data in classes but represented cards as structs, I'd consider it a good design choice. – Montagist Apr 04 '14 at 21:03
  • 4
    @Montagist: Data types which simply aggregate a few discrete values or traits without any particular attached behavior are often useful. Consider, for example, what should be returned by a function to compute the minimum and maximum values in a collection of BigInteger; since the time required to find both the minimum and the maximum would be much less than twice than the time required to find one or the other, having one call return both values is useful. A type which simply encapsulates a BigInteger called minimum and one called maximum,... – supercat Apr 04 '14 at 22:34
  • 1
    ...each of which simply holds whatever was put there, is apt to be cleaner than trying to use something fancier. The real meaning of minimum and maximum, along with any corner cases, are going to be determined by whatever method is suppose to put those values into the MinMaxRange object. – supercat Apr 04 '14 at 22:36
  • FWIW, the visibility of a card to a player is determined by neither of them. Instead games determine it. Thus in addition to Card and Player, you want a Game object that has the member bool Game::IsVisibleTo(Card, Player). – Thomas Eding Apr 23 '14 at 20:02
  • Regarding Cards and Players, to me it seems obvious that a player is composed by (among other things) a set of Cards (or possibly even a Hand abstraction). A player can only see it's own cards, although a player probably needs to be able to be queried for the current number of cards int it's hand. – sara Mar 29 '16 at 16:38
  • 3
    This question is very old and my comment is not really an answer, so it will remain just a comment. I'd say that you have discovered the joys of functional programming; in particular, you describe basic algebraic data types and type classes as Haskell would call them: that is, types with just data and types with just methods. – Ryan Reich Nov 05 '17 at 22:27
  • This blog post of mine may also be helpful: Getters/Setters. Evil. Period. – yegor256 Jun 27 '23 at 08:09

10 Answers10

160

The basic idea behind OOP is that data and behavior (upon that data) are inseparable and they are coupled by the idea of an object of a class.

You're making the common error of assuming that classes are a fundamental concept in OOP. Classes are just one particularly popular way of achieving encapsulation. But we can allow that to slide.

Suppose you have a general card game API. You have a class Card. Now this Card class needs to determine visibility to players.

GOOD HEAVENS NO. When you are playing Bridge do you ask the seven of hearts when it is time to change the dummy's hand from a secret known only to the dummy to being known by all? Of course not. That's not a concern of the card at all.

One way is to have boolean isVisible(Player p) on Card class. Another is to have boolean isVisible(Card c) on Player class.

Both are horrid; don't do either of those. Neither the player nor the card are responsible for implementing the rules of Bridge!

Instead I opted for the third option where we have a Viewport class which, given a Player and a list of cards determines which cards are visible.

I've never played cards with a "viewport" before, so I have no idea what this class is supposed to encapsulate. I have played cards with a couple decks of cards, some players, a table, and a copy of Hoyle. Which one of those things does Viewport represent?

However this approach robs both Card and Player classes of a possible member function.

Good!

Once you do this for other stuff than visibility of cards, you are left with Card and Player classes which contain purely data as all functionality is implemented in other classes, which are mostly classes with no data, just methods, like the Viewport above. This is clearly against the principal idea of OOP.

No; the basic idea of OOP is that objects encapsulate their concerns. In your system a card isn't concerned about much. Neither is a player. This is because you are accurately modeling the world. In the real world, the properties are cards that are relevant to a game are exceedingly simple. We could replace the pictures on the cards with the numbers from 1 to 52 without much changing the play of the game. We could replace the four people with mannequins labeled North, South, East and West without much changing the play of the game. Players and cards are the simplest things in the world of card games. The rules are what is complicated, so the class that represents the rules is where the complication should be.

Now, if one of your players is an AI, then its internal state could be extremely complicated. But that AI doesn't determine whether it can see a card. The rules determine that.

Here's how I'd design your system.

First off, cards are surprisingly complicated if there are games with more than one deck. You have to consider the question: can players distinguish between two cards of the same rank? If player one plays one of the seven of hearts, and then some stuff happens, and then player two plays one of the seven of hearts, can player three determine that it was the same seven of hearts? Consider this carefully. But aside from that concern, cards should be very simple; they're just data.

Next, what is the nature of a player? A player consumes a sequence of visible actions and produces an action.

The rules object is what coordinates all of this. The rules produce a sequence of visible actions and inform the players:

  • Player one, the ten of hearts has been handed to you by player three.
  • Player two, a card has been handed to player one by player three.

And then asks the player for an action.

  • Player one, what do you want to do?
  • Player one says: treble the fromp.
  • Player one, that is an illegal action because a trebled fromp produces an indefensible gambit.
  • Player one, what do you want to do?
  • Player one says: discard the queen of spades.
  • Player two, player one has discarded the queen of spades.

And so on.

Separate your mechanisms from your policies. The policies of the game should be encapsulated in a policy object, not in the cards. The cards are just a mechanism.

Eric Lippert
  • 46,189
  • 3
    "the basic notion, from which everything in object technology derives, is class" (OOSC 2nd Edition) -- with all due respect, I prefer to share such a "common error" with Meyer – gnat Apr 02 '14 at 17:51
  • 41
    @gnat: A contrasting opinion from Alan Kay is "Actually I made up the term "object-oriented", and I can tell you I did not have C++ in mind. " There are OO languages with no classes; JavaScript comes to mind. – Eric Lippert Apr 02 '14 at 17:59
  • 1
    FWIW Meyer's / OOSC language is Eiffel not C++. As for JavaScript, it's an interesting style but I would hesitate to call it mainstream and use it to justify fundamental concepts of OOP – gnat Apr 02 '14 at 18:09
  • 21
    @gnat: I would agree that JS as it stands today is not a great example of an OOP language, but it shows that one could quite easily build an OO language without classes. I agree that the fundamental unit of OO-ness in Eiffel and C++ both is the class; where I disagree is the notion that classes are the sine qua non of OO. The sine qua non of OO is objects that encapsulate behaviour and communicate with each other via a well-defined public interface. – Eric Lippert Apr 02 '14 at 18:29
  • maybe... or maybe not. Thing is, Meyer's statement I quoted is based on six prior chapters in the book where he thoroughly laid out his reasoning; I haven't yet seen anything comparable to challenge his conclusion (comments at Programmers don't qualify) – gnat Apr 02 '14 at 18:47
  • 1
    @gnat A class is not an object at all, according to On Understanding Data Abstraction, Revisited. It's an Abstract Data Type. Interfaces are objects. – Doval Apr 02 '14 at 19:49
  • 17
    I agree w/ @EricLippert, classes are not fundamental to OO, nor is inheritance, whatever the mainstream may say. Encapsulation of data, behavior and responsibility is, however that is accomplished. There are prototype-based languages beyond Javascript which are OO but classless. It is particularly a mistake to focus on inheritance over these concepts. That said, classes are very useful means of organizing encapsulation of behavior. That you can treat classes as objects (and in prototype languages, vice versa) makes the line blurry. – Schwern Apr 02 '14 at 19:50
  • 1
    Perhaps I provided a poor example, which you focused on in particular. The question is more general. Yes in card games, rule objects will dictate available actions and they contain the domain logic. However for any problem domain in existence I can implement things as a combination of controller (domain rules) objects and data objects (with no functionality). This makes separation of concerns and encapsulation easier to achieve, which is why I've been doing this. The reason I asked this question is because I got concerned I was separating data and logic too far. – RokL Apr 03 '14 at 08:29
  • 3
    @U Mad: there's still plenty of scope for data objects to have some behaviour, when that behaviour "naturally" depends only on things that you're happy to couple with the data class (and in particular when it depends only on the data itself). Your controller is coupled with Player and Card in order to prevent either being coupled to the other, which is fine. You might want to avoid, however, writing a CardController that compares the ranks of two cards of the same suit. The card can handle that provided it's a BridgeCard. Other games will vary. – Steve Jessop Apr 03 '14 at 12:31
  • 2
    Btw, combining Eric's description of a game with mine, BridgeCard is still formally part of the rules of bridge. But even if there's a PlayingCard abstraction lying somewhere underneath it, BridgeCard is the only abstraction for a card that the rest of the rules of Bridge need to interact with and hence almost certainly (encapsulation) the only one they should. That's your opportunity to combine data and behaviour where it's helpful to do so. – Steve Jessop Apr 03 '14 at 12:40
  • 7
    Consider it this way: what behavior does an actual card, in the real world, exhibit on its own? I think the answer is "none." Other things act on the card. The card itself, in the real world, literally is only information (4 of clubs), with no intrinsic behavior of any kind. How that information (a.k.a. "a card") is used is 100% up to something/somebody else, a.k.a. the "rules" and the "players." The same cards can be used for an infinite (well, maybe not quite) variety of different games, by any number of different players. A card is just a card, and all it has are properties. – Craig Tullis Apr 03 '14 at 21:15
  • As @Schwern said, encapsulation of data, behaviour and responsibility are fundamental, indeed. Single responsibility, removal of unneeded dependencies through stable abstractions, substituability of specializations, et al. All these things, you can achieve in C, without the aid of an "OO" language. Any other principle, whoever the author, that goes against these is foul. – Laurent LA RIZZA Apr 04 '14 at 08:43
  • Maybe a "Card" is not an abstract playing card, but a rendered object on the display: in this case, having an "isVisible" method quite a bit of sense. In that case, though, I think the problem is that there is just one "Card" class, when there really should be at least two: a class to represent the datatype containing a rank and a suit, and a class to represent the 'physical' object a user interacts with. –  Apr 04 '14 at 08:54
  • @Joe: I knew someone would get that. – Eric Lippert Apr 04 '14 at 18:39
  • Some really good comments here, but aren't there actually classes in Javascript? Or - because it's not really built for hiding logic or data from other logic or data - are you all simply not considering these true implementations of Classes? – Montagist Apr 04 '14 at 20:39
  • 3
    @Montagist: Well, if you think there are classes in JS, make the case. I've implemented two JS compilers and didn't come across anything resembling a class in the language but perhaps I missed something. Where are there classes in JS? – Eric Lippert Apr 04 '14 at 20:48
  • Haha :) I think what you're saying is that you fall into the latter school of thought. Perhaps I'm unclear as to exactly what constitutes a class (the ability to have an honestly private scope?), but I'd assume having a constructor, properties for "internal" state, and methods tied those properties would make them classes? You also get inheritance if you're willing to mess with the prototype chain. – Montagist Apr 04 '14 at 20:53
  • 5
    @Montagist: Let me clarify things a bit then. Consider C. I think you would agree that C does not have classes. However, you could say that structs are "classes", you could make fields of function pointer type, you could build vtables, you could make methods called "constructors" that set up the vtables so that some structs "inherited" from each other, and so on. You can emulate class-based inheritance in C. And you can emulate it in JS. But doing so means building something on top of the language that isn't already there. – Eric Lippert Apr 04 '14 at 20:59
  • Got'cha. I guess that'd be why I've seen so many hand-rolled "inheritance the way I want it" implementations in JS. – Montagist Apr 04 '14 at 21:04
  • I'm not sure I follow the reasoning for a card not having the responsibility of declaring whether or not it's visible. The method U Mad suggested was isVisible, not shouldBeVisibleAccordingToTheRulesOfThisGame. In a card game, it may be the rules that specify how we interact with the cards, but whether or not I can see a card is very much a physical property of the card itself. Not that I disagree with your conclusions, it just seems like somebody following a similar 'model object responsibilities by analogy to real life' approach could draw completely opposite conclusions. – Ben Aaronson Apr 05 '14 at 23:49
  • 2
    @BenAaronson: "Visible" is an incomplete predicate; the "visibility" predicate is a relation between two things: an object and a viewer. A card in a game might be visible to one player and not to another; that's not a property of the card or the player, it's a consequence of the rules of the game. – Eric Lippert Apr 05 '14 at 23:58
  • Well again I'd say the rules of the game only say whether a player should be able to see a card. It's the physical properties of the player and card that determine whether they actually can. In a sense it's a quibble- why would you create a program that needed to distinguish between being able to physically see a card and being able to legally see one? – Ben Aaronson Apr 06 '14 at 00:30
  • But if my guiding principle in designing a card game was to accurately model the real world, I'd reason that I don't see a card because I'm allowed to. The rules tell the players who is legally allowed see which card, then the players are responsible for setting who can actually see which card. – Ben Aaronson Apr 06 '14 at 00:30
  • @BenAaronson consider that the card itself (in the real world) does not have a visibility cloak of any kind. It's just a card, just information. However, according to the rules of a particular game, a player will have a set/collection of cards that are only visible to that player. Other players aren't supposed to see them. Or a team has a set of players, and the team also has a set of cards, so all the players on that team can see those cards, but other teams can't. Or each player on a team has a set of cards, but players on a team can see each other's cards. It's all in the rules of the game. – Craig Tullis Apr 06 '14 at 07:51
  • But the card itself? It seems highly likely that an isVisible property (which is ultimately also just information, just a property) is inappropriate, that it would actually constitute a bad modelling decision. Visibility is not intrinsic to the card. Visibility of a particular card is an effect of the rules of the game. Context is everything. Slapping an isVisible property on a card may well represent a leaking abstraction. I didn't say that it does, just that it might (context). – Craig Tullis Apr 06 '14 at 07:56
  • 3
    This is such a great answer -- a load of insights on OOP in a short piece of text. I keep coming back to it again and again. – Myk Mar 21 '19 at 22:46
47

What you describe is known as an anemic domain model. As with many OOP design principles (like Law of Demeter etc.), it's not worth bending over backwards just to satisfy a rule.

Nothing wrong about having bags of values, as long as they don't clutter the entire landscape and don't rely on other objects to do the housekeeping they could be doing for themselves.

It would certainly be a code smell if you had a separate class just for modifying properties of Card - if it could be reasonably expected to take care of them on its own.

But is it really a job of a Card to know which Player it is visible to?

And why implement Card.isVisibleTo(Player p), but not Player.isVisibleTo(Card c)? Or vice versa?

Yes, you can try to come up with some sort of a rule for that as you did - like Player being more high level than a Card (?) - but it's not that straightforward to guess and I'll have to look in more than one place to find the method.

Over time it can lead to a rotten design compromise of implementing isVisibleTo on both Card and Player class, which I believe is a no-no. Why so? Because I already imagine the shameful day when player1.isVisibleTo(card1) will return a different value than card1.isVisibleTo(player1). I think - it's subjective - this should be made impossible by design.

Mutual visibility of cards and players should better be governed by some sort of a context object - be it Viewport, Deal or Game.

It's not equal to having global functions. After all, there may be many concurrent games. Note that the same card can be used simultaneously on many tables. Shall we create many Card instances for each ace of spade?

I might still implement isVisibleTo on Card, but pass a context object to it and make Card delegate the query. Program to interface to avoid high coupling.

As for your second example - if the document ID consists only of a BigDecimal, why create a wrapper class for it at all?

I'd say all you need is a DocumentRepository.getDocument(BigDecimal documentID);

By the way, while absent from Java, there are structs in C#.

See

for reference. It's a highly object-oriented language, but noone makes a big deal out of it.

  • 2
    Just a note about structures in C#: They are no typical structs, like you know them in C. In fact, they do also support OOP with inheritance, encapsulation and polymorphism. Besides some peculiarities the main difference is how the runtime handles instances when they are passed to other objects: structures are value types and classes are reference types! – Aschratt Apr 02 '14 at 15:05
  • 4
    @Aschratt: Structures don't support inheritance. Structures can implement interfaces, but structures that implement interfaces behave differently from class objects that do likewise. While it's possible to make structs behave somewhat like objects, the best use case for structs in when one wants something that behaves like a C struct, and the things one is encapsulating are either primitives or else immutable class types. – supercat Apr 05 '14 at 23:19
  • 1
    +1 for why "it's not equal to having global functions." This hasn't been much addressed by others. (Although if you have several decks, a global function would still return different values for distinct instances of the same card). – alexis Apr 06 '14 at 10:05
  • @supercat This is worthy of a separate question or a chat session, but I'm not currently interested in either :-( You say (in C#) "structures that implement interfaces behave differently from class objects that do likewise". I agree that there are other behavioral differences to consider, but AFAIK in code following Interface iObj = (Interface)obj;, the behaviour of iObj is not affected by the struct or class status of obj (except that it will be a boxed copy at that assignment if it is a struct). – Mark Hurd Jul 02 '14 at 02:17
  • You said: "Shall we create many Card instances for each ace of spade?" Would you not? – BVernon Nov 12 '19 at 04:53
29

You are correct that the coupling of data and behaviour is the central idea of OOP, but there's more to it. For example, encapsulation: OOP/modular programming allow us to separate a public interface from implementation details. In OOP this means that the data should never be publicly accessible, and should only be used via accessors. By this definition, an object without any methods is indeed useless.

A class that offers no methods beyond accessors is essentially an overcomplicated struct. But this isn't bad, because OOP gives you the flexibility to change internal details, which a struct does not. For example, instead of storing a value in a member field, it could be recomputed each time. Or a backing algorithm is changed, and with it the state that has to be kept track of.

While OOP has some clear advantages (esp. over plain procedural programming), it is naive to strive for “pure” OOP. Some problems do not map well to an object oriented approach, and are solved more easily by other paradigms. When encountering such a problem, do not insist on an inferior approach.

  • Consider calculating the Fibonacci sequence in an object oriented manner. I can't think of a sane way to do that; simple structured programming offers the best solution to this problem.

  • Your isVisible relation belongs to both classes, or to neither, or actually: to the context. Behaviourless records are typical of a functional or procedural programming approach, which seems to be the best fit to your problem. There is nothing wrong with

    static boolean isVisible(Card c, Player p);
    

    and there is nothing wrong with Card having no methods beyond rank and suit accessors.

amon
  • 134,135
  • Well there's nothing wrong with a static function, but if I start doing this frequently I'll wind up with a bunch of struct-like classes and static functions (like global functions in other langages). At that point it stops being OOP and starts being functional or procedural programming. – RokL Apr 02 '14 at 13:02
  • 11
    @UMad yes, that's my point exactly, and there is nothing wrong with that. Use the correct language paradigm for the job at hand. (By the way, most languages besides Smalltalk aren't pure object-oriented. E.g. Java, C# and C++ support imperative, structured, procedural, modular, functional and object-oriented programming. All those non-OO paradigms are available for a reason: so that you can use them) – amon Apr 02 '14 at 13:11
  • 1
    There is a sensible OO way to do Fibonacci, call the fibonacci method on an instance of integer. I wish emphasize your point that OO is about encapsulation, even in seemingly small places. Let the integer figure out how to do the job. Later you can improve the implementation, add caching to improve performance. Unlike functions, methods follow the data so all the callers get the benefit of an improved implementation. Maybe arbitrary precision integers are added later, they can be transparently treated like normal integers and can have their own performance tweaked fibonacci method. – Schwern Apr 02 '14 at 19:33
  • @Schwern I disagree. It seems odd that integer would hold multiple integers (as is required to fibonacci). I'd rather use a fibonacci class, would better serve caching, maintaining state, integers can be reused, less instances required, .etc. – George Reith Apr 02 '14 at 20:03
  • @GeorgeReith Fibonacci is certainly not a class, you wouldn't have an Add class, it is a behavior of integers. You might have a Fibonacci role/trait to add to Integer. There is no requirement to hold multiple integers, it remains f(n-1) + f(n-2). The fundamental change to a method is to (n-1).f() + (n-2).f(), asking each integer to do the calculation. If Integer is a full object, as opposed to autoboxed, the cache can be located on the Integer itself (which caches just its own result). Or a cache of all Integers can be attached to the method definition or to the Integer class as a whole. – Schwern Apr 02 '14 at 20:11
  • 1
    @Schwern n-1, n-2 are two separate integers based on an initial seed. Fibonacci requires two seeds, without storing the two seeds then how do we know what n-1 or n-2 are? Most integers don't appear in the fibonacci sequence, it is certainly not a behaviour of integers, it contains specific integers. – George Reith Apr 02 '14 at 20:26
  • 2
    @Schwern if anything Fibonacci is a subclass of the abstract class Sequence, sequence is utilised by any number set and is responsible for storing seeds, state, caching and an iterator. – George Reith Apr 02 '14 at 20:39
  • @GeorgeReith An implementation of a fibnoacci method on an autoboxed integer class. The cache in the method serves both to store the seeds and as a performance cache for all Integers. One on a full Integer class, while a bit stilted, demonstrates how each object only stores its own fibonacci result. Finally, here is one taking advantage of the immutability of integers to seed the calculation. These are for demonstration purposes only. – Schwern Apr 02 '14 at 21:01
  • @GeorgeReith I think I see what the confusion is between us. You're viewing the sequence itself as an object. That's a totally valid approach as well. It could be its own class, or just once instance of a Sequence class with its function and seeds defined for Fibonacci. Integer->fibonacci could just as easily make use of that sequence demonstrating the value of encapsulation for radically changing implementation. – Schwern Apr 02 '14 at 21:05
  • 2
    I didn't expect “pure OOP Fibonacci” to be so effective at nerd-sniping. Please stop any circular discussion about that in these comments, although it had a certain entertainment value. Now let's all do something constructive for a change! – amon Apr 02 '14 at 21:07
  • @schwern I've never used perl but that is how I am thinking. You're probably right though and they are both good ways to define it and it depends more on how it's going to be used / what else you need to define. – George Reith Apr 02 '14 at 21:27
  • 3
    it would be silly to make fibonacci a method of integers, just so you can say it's OOP. It's a function and should be treated like a function. – user253751 Apr 03 '14 at 05:14
  • 1
    @immibis, it's silly I agree, but it's a sequence, not (any) function. You can ask for the next number, the n'th number, or for the number at a particular index or indices. And while the values are fixed, how you calculate them is an implementation detail. – alexis Apr 06 '14 at 09:59
18

The basic idea behind OOP is that data and behavior (upon that data) are inseparable and they are coupled by the idea of an object of a class. Object have data and methods that work with that (and other data). Obviously by the principles of OOP, objects that are just data (like C structs) are considered an anti-pattern. (...) This is clearly against the principal idea of OOP.

This is a tough question because it's based on quite a few faulty premises:

  1. The idea that OOP is the only valid way to write code.
  2. The idea that OOP is a well-defined concept. It's become such a buzzword that it's hard to find two people that can agree on what OOP is about.
  3. The idea that OOP is about bundling data and behavior.
  4. The idea that everything is/should be an abstraction.

I won't touch upon #1-3 much, because each could spawn their own answer, and it invites a lot of opinion-based discussion. But I find the idea of "OOP is about coupling data and behavior" to be especially troubling. Not only does it lead to #4, it also leads to the idea that everything should be a method.

There's a difference between the operations that define a type, and the ways you can use that type. Being able to retrieve the ith element is essential to the concept of an array, but sorting is just one of the many things I can choose to do with one. Sorting doesn't need to be a method any more than "create a new array containing only the even elements" needs to be.

OOP is about using objects. Objects are just one way of achieving abstraction. Abstraction is a means to avoid unnecessary coupling in your code, not an end all unto itself. If your notion of a card is defined solely by the value of its suite and rank, it's fine to implement it as simple tuple or record. There's no non-essential details that any other part of the code could form a dependency on. Sometimes you just don't have anything to hide.

You wouldn't make isVisible a method of the Card type because being visible is likely not essential to your notion of a card (unless you have very special cards that can turn translucent or opaque...). Should it be a method of the Player type? Well, that's probably not a defining quality of players either. Should it be a part of some Viewport type? Once again that depends on what you define a viewport to be and whether the notion of checking the visibility of cards is integral to defining a viewport.

It's very much possible isVisible should just be a free function.

Doval
  • 15,397
  • 2
    +1 for common sense instead of mindless droning. –  Apr 04 '14 at 13:48
  • From the lines I've read, the essay you linked look like that one solid read I haven't have in a while. – Diane M Apr 04 '14 at 16:12
  • @ArthurHavlicek It's harder to follow if you don't understand the languages used in the code sample, but I found it quite illuminating. – Doval Apr 04 '14 at 16:36
9

Obviously by the principles of OOP, objects that are just data (like C structs) are considered an anti-pattern.

No, they aren't. Plain-Old-Data objects are a perfectly valid pattern, and I would expect them in any program that deals with data that needs to be persisted or communicated between distinct areas of your program.

While your data-layer might spool up a full Player class when it reads in from the Players table, it could instead just be a general data library that returns a POD with the fields from the table, which it passes to another area of you program which converts a player POD to your concrete Player class.

Use of data objects, either typed or untyped, may not make sense in your program, but that doesn't make them an anti-pattern. If they make sense, use them, and if they don't, don't.

DougM
  • 6,371
  • 5
    Don't disagree with anything you've said, but this doesn't answer the question at all. That said, I blame the question more than the answer. – pdr Apr 02 '14 at 12:50
  • 2
    Exactly, cards and documents are just containers of information even in the real world and any "pattern" that can't handle it needs to be ignored. – JeffO Apr 02 '14 at 12:51
  • 1
    Plain-Old-Data objects are a perfectly valid pattern

    I didn't say they weren't, I'm saying it's wrong when they populate the entire lower half of the application.

    – RokL Apr 02 '14 at 12:59
9

Personally I think Domain Driven Design helps to bring clarity to this issue. The question I ask is, how do I describe the card game to human beings? In other words, what am I modeling? If the thing I'm modeling genuinely includes the word "viewport" and a concept that matches its behavior, then I would create the viewport object and have it do what it logically should.

However if I don't have the concept of the viewport on my game, and it's something I think I need because otherwise the code "feels wrong". I think twice about adding it my domain model.

The word model means that you are building a representation of something. I caution against putting in a class that represents something abstract beyond the thing that you are representing.

I will edit to add that it's possible that you may need the concept of a Viewport in another part of your code, if you need to interface with a display. But in DDD terms this would be an infrastructure concern and would exist outside of the domain model.

RibaldEddie
  • 3,273
  • 1
  • 15
  • 17
4

I don't usually do self-promotion, but the fact is I wrote a lot about OOP design issues on my blog. To sum up several pages: you shouldn't start design with classes. Starting with interfaces or APIs and shape code from there have higher chances to provide meaningful abstractions, fit specifications and avoid bloating concrete classes with non-reusable code.

How this apply to Card-Player problem: Creating a ViewPort abstraction makes sense if you think of Card and Player as being two independent libraries (which would imply Player is sometimes used without Card). However, I'm inclined to think a Player holds Cards and should provide a Collection<Card> getVisibleCards () accessor to them. Both these solutions (ViewPort and mine) are better than providing isVisible as a method of Card or Player, in terms of creating understandable code relationships.

An outside-the-class solution is much, much better for the DocumentId. There is little motivation to make (basically, an integer) depend on a complex database library.

Peter Mortensen
  • 1,045
  • 2
  • 12
  • 14
Diane M
  • 2,056
3

I am not sure the question at hand is being answered at the right level. I had urge the wise in the forum to actively think on the core of the question here.

U Mad is bringing up a situation where he believes that programming as per his understanding of OOP would generally result in a lot of leaf nodes being data holders while his upper level API is comprises of most of the behavior.

I think the topic went slightly tangential into whether isVisible would be defined on Card vs Player; it was a mere example illustrated, albeit naive.

I had push the experienced here to look at the problem at hand though. I think there is a good question that U Mad has pushed for. I understand that you would push the rules and the concerned logic to an object of its own; but as I understand the question is

  1. Is it OK to have simple data holder constructs (classes / structs ; I don't care about what they are modeled as for this question) which do not really offer much functionality ?
  2. If yes, What is the best or preferred way to model them?
  3. If no, how do we incorporate this data counter parts into higher API classes (including behavior)

My view:

I think you are asking a question of granularity that is hard to get right in object oriented programming. In my little experience, I would not include an entity in my model that does not include any behavior by itself. If I have to, I had probably use a construct a struct that is designed to hold such abstraction unlike a class that has the idea of encapsulating data and behavior.

Harsha
  • 41
  • 3
    The problem is that the question (and your answer) are about how to do things "in general". The fact is, we never do things "in general". We always do specific things. It is necessary to examine our specific things and measure them against our requirements in order to determine whether our specific things are the correct things for the situation. – John Saunders Apr 03 '14 at 04:40
  • @JohnSaunders I perceive your wisdom here and agree to an extent but there is also mere conceptual approach required before addressing a problem. After all, the question here is not as open-ended as it seems to be. I think it is a valid OOD question any OO designer faces in the initial usages of OOP. What is your take? If a concretion helps, we could discuss building an example of your choice. – Harsha Apr 03 '14 at 04:48
  • I've been out of school for over 35 years now. In the real world, I find very little value in "conceptual approaches". I find experience to be a better teacher than Meyers in this case. – John Saunders Apr 03 '14 at 04:53
  • I don't really understand the class for data vs class for behavior distinction. If you abstract your objects properly, there is no distinction. Imagine a Point with getX() function. You could imagine it's getting one of it's attribute, but it could also read it from disk or the internet. Getting and setting is behavior, and having classes that do just that is completely fine. Databases only get and set data fwiw – Diane M Apr 04 '14 at 06:24
  • @ArthurHavlicek: Knowing what a class won't do is often just as useful as knowing what it will do. Having something specify in its contract that it will behave as nothing more than a sharable immutable data holder, or as nothing more than a non-sharable mutable data holder, is useful. – supercat Apr 04 '14 at 22:44
2

One common source of confusion in OOP stems from the fact that many objects encapsulate two aspects of state: the things they know about, and the things that know about them. Discussions of objects' state frequently ignore the latter aspect, since in frameworks where object references are promiscuous there is no general way to determine what things might know about any object whose reference has ever been exposed to the outside world.

I would suggest that it would probably be helpful to have a CardEntity object which encapsulates those aspects of the card in separate components. One component would relate to the markings on the card (E.g. "Diamond King" or "Lava Blast; players have AC-3 chance to dodge, or else take 2D6 damage"). One might relate to a unique aspect of state such as position (e.g. it's in the deck, or in Joe's hand, or on the table in front of Larry). A third might relate to can see it (perhaps nobody, perhaps one player, or perhaps many players). To ensure that everything is kept in sync, places where a card might be would not be encapsulated as simple fields, but rather CardSpace objects; to move a card to a space, one would give it a reference to the proper CardSpace object; it would then remove itself from the old space and put itself into the new space).

Explicitly encapsulating "who knows about X" separately from "what X knows" should help avoid a lot of confusion. Care is sometimes needed to avoid memory leaks, especially with many-many associations (e.g. if new cards can come into existence and old cards disappear, one must ensure that cards which should be abandoned aren't left perpetually attached to any long-lived objects) but if the existence of references to an object will form a relevant part of its state, it's entirely proper for the object itself to explicitly encapsulate such information (even if it delegates to some other class the work of actually managing it).

supercat
  • 8,445
  • 23
  • 28
0

However this approach robs both Card and Player classes of a possible member function.

And how is that bad/ill-advised?

To use a similar analogy to your cards example, consider a Car, a Driver and you need to determine if the Driver can drive the Car.

OK, so you decided that you don't want your Car to know if the Driver has the right car key or not, and for some unknown reason you also decided you don't want your Driver to know about the Car class (you didn't fully flesh this out in your original question as well). Hence, you have an intermediary class, something along the lines of an Utils class, which contains the method with business rules in order to return a boolean value for the question above.

I think this is fine. The intermediary class may only need to check for car keys now, but can be refactored to consider if the driver has a valid driving license, under the influence of alcohol, or in a dystopian future, check for DNA biometrics. By encapsulation, there is really no big problems having these three classes co-existing together.

h.j.k.
  • 1,737