4

When I was first taught Object Oriented principles, it was drilled into me that when using objects in a "hasA" relationship (or any similar situations where a helper object encapsulates a discrete smaller purpose in a bigger system), the owned class should not have awareness of it's greater environment, so, for example:

  • Class Deck has a Card[], each Card has no upward access to Deck
  • Class GameBoard has GamePiece, each GamePiece has no upward access towards the board

This has held well and true for a while, but I've finally had a problem where I'm strongly tempted to break from this.

In an application where there's user defined "commands" that are parsed from input, and run, it made relative sense to define Class Command and Class Conditional, which each have subclasses for each of the many possible Command types the user can define.

Added context This is a Roleplaying utility, so each command relates to an operation done on the "Character" object, or messing with the generators rules. So AddStat(WIL,5) increments that value accordingly. Commands shouldn't need access to the greater generator to run themselves, but they'd need at least access to the object their represented command modifies. So for most purposes, the character is the "environment" the command would be acting on.

Makes sense, seems at first glance to help things. But there's a missing link when a lot of these classes can only do very basic input validation without a greater context, not to mention the fact that without more "aware" objects, every part of the code dealing with them would need to do that main ifelseif branch to first figure out which command it's dealing with...

The design thing I'm not sure where to go with here is as follows (this is not yet coded because of the amount of changes it'd take to adapt one of these divergent solutions)

  • Command remains "dumb" with no Character/Environment awareness. Conditional is allowed a resolve(Character obj) method, which lets it be passed enough awareness to return a meaningful true/false. This means Command remains a relatively simple basic syntax validator, and body code elsewhere does the heavy lifting for running
  • As before, except Command also gains it's own resolve(Character obj) method, and each subclass contains the code necessary to execute the command
  • As before, except we outright create Command objects with a reference to the Character

I'm not really sure where to go on this one... While technically there's already some code organization gained by the simplest option, it feels the readability of the application would benefit from Option 2/Option 3, even if the encapsulation purism suffers.

Which route is the way to go here? Or am I off the mark completely?

7
  • Is this problem tied specifically to input validation or is that just an example of a more general issue? Sep 10, 2015 at 8:30
  • It seems that you are trying to make an object model for a general representation of executable code. That is, something that looks like it could generate Java source code, or execute them inline. Please try to think about what the users want - what would they find useful? What are their most pressing needs, etc? How general does "Environment" needs to be?
    – rwong
    Sep 10, 2015 at 8:33
  • The problem is attempting to organize code by putting it into more logical places rather than having long code blocks in the environment code (or a CommandManager class, same problem). Moving input validation to the classes via option 1 alleviates that a little, but not by as much as it could. (For context, there are ~20+ commands and currently 7ish conditionals. This forms an intimidating, unreadable, block)
    – Vigilant
    Sep 10, 2015 at 8:33
  • It would be helpful to give some examples of what "Command" and "Conditional" would be doing. Are they elements in a domain-specific language, where the users would enter source code in that syntax?
    – rwong
    Sep 10, 2015 at 8:36
  • That's a good point, added info, I hope that helps. (This x2, noticed a sentence was missing)
    – Vigilant
    Sep 10, 2015 at 8:39

2 Answers 2

6

Your learning experience is textbook example of situation, where you are taught that something is wrong without being taught why is it wrong. While this is easy for teacher to do and easier for student to remember, it will soon be challenged by situation where it is just too limiting. And knowing why this rule exists would allow you to weight pros and cons of abiding by this rule.

So why should "the owned class should not have awareness of it's greater environment"? Because it would create cyclic dependency. In your example Deck depends on Card and Card depend on Deck. It then becomes impossible to be able to use one without the other or vice versa. From modularity perspective, both Deck and Card for single logical "aggregate" and should be viewed as such from rest of the code. This reduces modularity and maintainability so it is mostly bad idea.

BUT! Sometimes it does make sense for multiple classes to form an aggregate. The case of Deck and Cards referencing each other would make sense if rest of the application never works with just cards without reference to the deck that the cards belong to. So that when Deck's lifetime ends, so do it's Card's.

So in your case, if you are not expecting that the Commands will outlive the Environment or Player, then I see no problem referencing them.

2

The principle you describe is a guideline, not a concrete rule.

Consider the case where you have a deck of non-standard cards: that is, the cards are not {Clubs, Spades, Diamonds, Hearts} / {Ace - King} but rather the cards are for one of the modern, popular trading card games like Pokémon, Yu-Gi-Oh, or Magic the Gathering, where the cards have special abilities.

In this case, assume you have set up a system where each Card has a void use(...) that performs its action. You have classes representing a Deck of cards, a Hand of cards, and maybe other containers of cards too (discard, whatever).

Some cards might have an ability which causes them to remove themselves from your hand of cards, or which target other cards and removes them from the hand or deck they are contained within. Some cards even have an ability to completely remove themselves from the game, in which case a card might need to reach all the way back to the Game which encapsulates all of the above. Game has a Player[], Player has a Deck and a Hand perhaps, and so on, and this special card needs to refer at a minimum to the deck's or hand's getOwner().remove(this), if not the player and the game somehow too. Remember, the Deck (or Hand) has no idea that the Card has a special ability to remove itself from its container since you likely do not have this notion in the Card's interface.

To find some other workaround for this which allows you to avoid giving the Card an owner property could add more complexity than it is worth and make the system less maintainable rather than the increased maintainability you were hoping to gain by blindly following a concrete rule handed on by your teachers. This increase in complexity could be especially true if you are maintaining a large legacy code base.

So you see, even in your classic example of "A Card should not refer to its Deck" there are exceptions.

If your framework allows a way for you to easily avoid coupling the Card to the Deck, then prefer to avoid coupling. If there is no reasonable way within the framework you are working with, and if coupling the Card to the Deck is the best option, then you should couple; just don't do so lightly.

When you decide to do this, do not think only about how much easier it makes the current task for you to develop. Try your best to think about how some future code maintainer (possibly you, possibly not) will react when they need add further features.

1
  • Commenting since this tidbit is not necessary to the answer: In Magic the Gathering, there are cards that do almost anything you can think of, including "<You/Opponent> <win(s)/lose(s)> the game." If you have a card whose ability is "You win the game." which you need to figure out how to accomplish through Card.use(), then having some reference to the owner is the simplest way.
    – Aaron
    Mar 21, 2017 at 21:56

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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