106

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.
20
  • 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
    Commented Apr 2, 2014 at 12:53
  • 31
    "This is clearly against the principal idea of OOP" - no, its not, but its a common fallacy.
    – Doc Brown
    Commented Apr 2, 2014 at 12:55
  • 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 geekswithblogs.net/theArchitectsNapkin/archive/2013/09/08/…), and the way is was taught in context of OOA/OOD by those people from Rational (en.wikipedia.org/wiki/Object-oriented_analysis_and_design).
    – Doc Brown
    Commented Apr 2, 2014 at 13:09
  • 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. Commented Apr 2, 2014 at 13:58
  • 5
    Who says beheaviourless classes are an anti-pattern? Commented Apr 3, 2014 at 3:49

10 Answers 10

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.

5
  • 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
    Commented Apr 2, 2014 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
    Commented Apr 5, 2014 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
    Commented Apr 6, 2014 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
    Commented Jul 2, 2014 at 2:17
  • You said: "Shall we create many Card instances for each ace of spade?" Would you not?
    – BVernon
    Commented Nov 12, 2019 at 4:53
161

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.

26
  • 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. Commented Apr 2, 2014 at 17:59
  • 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. Commented Apr 2, 2014 at 18:29
  • 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
    Commented Apr 2, 2014 at 19:50
  • 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. Commented Apr 3, 2014 at 21:15
  • 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. Commented Apr 4, 2014 at 20:59
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.

13
  • 11
    @UMad yes, that's my point exactly, and there is nothing wrong with that. Use the correct <del>language</del> 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
    Commented Apr 2, 2014 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
    Commented Apr 2, 2014 at 19:33
  • 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. Commented Apr 2, 2014 at 20:39
  • 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
    Commented Apr 2, 2014 at 21:07
  • 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.
    – user20574
    Commented Apr 3, 2014 at 5:14
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.

3
  • 2
    +1 for common sense instead of mindless droning.
    – user4595
    Commented Apr 4, 2014 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
    Commented Apr 4, 2014 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
    Commented Apr 4, 2014 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.

3
  • 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
    Commented Apr 2, 2014 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
    Commented Apr 2, 2014 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
    Commented Apr 2, 2014 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.

1
  • Lippert's answer above is a better example of this concept. Commented Apr 3, 2014 at 9:44
4

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.

5
  • 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. Commented Apr 3, 2014 at 4:40
  • 1
    @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
    Commented Apr 3, 2014 at 4: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. Commented Apr 3, 2014 at 4: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
    Commented Apr 4, 2014 at 6: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
    Commented Apr 4, 2014 at 22:44
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.

1
  • I like your blog.
    – RokL
    Commented Apr 7, 2014 at 8:24
3

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

1

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.

Not the answer you're looking for? Browse other questions tagged or ask your own question.