Object-Oriented Solutions: Avoiding Getters

chartAlice and Bob were both working in a small team for an international broker, creating an application to react to certain events in the market. The application’s architecture was centered around Trades, that represents a transaction between a buyer and a seller exchanging something of value on an exchange.

The initial implementation of a Trade was committed by Alice, who designed the Trade according to Object-Oriented principles, concentrating on features the Trade must have, instead of the data it must contain. It looked like this:

public final class Trade {
   private final Date timestamp;
   private final Symbol symbol;
   private final BigDecimal price;
   private final int volume;

   public Trade(Date timestamp, Symbol symbol, BigDecimal price, int volume) {
      this.timestamp = timestamp;
      this.symbol = symbol;
      this.price = price;
      this.volume = volume;
   }

   public boolean isBelow(Trade trade) {
      return price.compareTo(trade.price) < 0;
   }

   public boolean isOffMarket() {
      return !symbol.isTradingAt(timestamp);
   }
   ...
}

It had no getter or setter (accessor) methods, instead it offered methods that closely followed business requirements, something that was part of the language the traders also used.

Remembering The Latest Trade

Bob was working on a different part of the system, calculating differences, rises and drops in prices, the breadth of changes occurring on certain symbols (like Google (GOOG), Apple (APPL), etc.).

To calculate how the price moved for a single symbol, Bob used a Map to remember the latest Trade for every Symbol. His initial implementation looked liked this:

private final Map<Symbol, Trade> lastTrades = new HashMap<>();

@Override
public void handleTrade(Trade trade) {
   Trade lastTrade = lastTrades.get(trade.getSymbol());
   if (lastTrade != null) {
      ... logic with lastTrade vs. trade
   }
   lastTrades.put(trade.getSymbol(), trade);
}

Unfortunately this code did not compile, because there was no getSymbol() method in the Trade object, therefore there was no way to get the Symbol of the Trade.

Bob opened the Trade class and to his relief noticed that the Trade does actually have the Symbol as a private field, so Bob did the following:

public final class Trade {
   private final Symbol symbol;
   ...
   public Symbol getSymbol() {
      return symbol;
   }
   ...
}

He added the “missing” getter accessor method to publish the symbol of the trade, so his code could compile. Fortunately Alice noticed the change in a code review and approached Bob to speak about this change:

Discussing the change

blabla

Alice: Hi Bob, I’ve noticed your change to Trade, you introduced a getter for the Symbol.
Bob: Yes, was there a conflict?
Alice: No, I was just curious what you need the Symbol for, and whether there was maybe something else in the Trade you could use…
Bob: I need the Symbol of the Trade to create a Map with the Symbol as key.
Alice: What are you using that Map for?
Bob: I need to remember the latest Trade for a given Symbol to calculate differences.
Alice: Ok, the difference calculation is already in the Trade. Could we move this “latest Trade” functionality into the Trade somehow?
Bob: I guess we could, but why would we?
Alice: Well, the Symbol is internal to the Trade, we shouldn’t just expose that for everybody.
Bob: Hm, but Symbol is a basic property of the Trade, I think exposing that does no harm, does it?
Alice: I’m not sure. I know there are features coming to change the Symbol, introducing Options and Option Chains. I’m pretty sure our Symbol concept will change eventually, and that shouldn’t impact our code elsewhere.
Bob: Ok, how could we include this in the Trade? The Trade has no concept of previous Trades.
Alice: Maybe the Trade can give you something new that can help, that does not expose the Symbol?
Bob: Well, it could give me the Map directly, I actually only need the Map.
Alice: That sounds good. Maybe we can call it a Cache, because it caches the latest Trades?
Bob: Sounds good, let’s define the interface for that…

Implementing the Trade Cache

Alice and Bob agreed to avoid exposing the Symbol property of the Trade to avoid maintenance problems later. Instead they created the Cache in the Trade, with the explicit functionality Bob needed:

public final class Trade {
   ...
   public static Cache newCache() {
      return new MapCache();
   }

   public interface Cache {
      /**
       * Invokes the 'logic' consumer with the latest
       * and the new trade, then updates the latest trade.
       * It is only invoked if there was a latest trade.
       */
      void withNewTrade(Trade newTrade, BiConsumer<Trade, Trade> logic);
   }

   private static final class MapCache implements Cache {
      private final Map<Symbol, Trade> latestTrades = new HashMap<>();

      @Override
      public void withNewTrade(Trade newTrade, BiConsumer<Trade, Trade> logic) {
         Trade latestTrade = latestTrades.get(newTrade.symbol);
         if (latestTrade != null) {
            logic.accept(latestTrade, newTrade);
         }
         latestTrades.put(newTrade.symbol, newTrade);
      }
   }
}

With this new feature from Trade, Bob’s code became simpler, but more importantly it became independent of the internal structure of Trade:

private final Trade.Cache tradeCache = Trade.newCache();

@Override
public void handleTrade(Trade trade) {
   tradeCache.withNewTrade(trade,
      (lastTrade, newTrade) -> ...logic...);
}

Conclusion

Object-Orientation is a way to isolate objects to protect against changes that can propagate through code unchecked. The main tool to reach this isolation is encapsulation and data hiding. Following these principles requires objects to take on real responsibilities, and avoid exposing the details of these responsibilities, like the “properties” or other objects that are needed for them.

This article shows one possible way to avoid exposing properties by moving a functionally closed, meaningful feature into the object best suitable for it. Although these kinds of solutions might be unfamiliar to developers trained or experienced in other paradigms, they are absolutely necessary for creating an Object-Oriented Design.

Advertisements

6 thoughts on “Object-Oriented Solutions: Avoiding Getters

  1. I love how Alice and Bob are having a nice, eloquent, grown up conversation about it and not, the usual, *your implementation is stupid, and you are stupid* type of banter. That’s how developers should solve communicate to each other. Open minded, forward thinking and with the thought that the person I’m talking to is not questioning my intellect, but rather had a different idea, which when I’ll listen too, I might actually learn something from it.

    Liked by 1 person

  2. I’m sorry to comment, because example are hard, but are we sure that this is the right approach in the context of this specific example?

    A “trade” is one single trade right? The Trade object maps to a real world concept which is comprised of a date and time, a “symbol” and a volume of the thing that symbol refers to. A “trade” is a single transaction. As such it violates the definition of “single transaction” to start using the object to track historical data. I think the fact that the cache functionality discussed has to be moved into the static context of the Trade object proves that it is not actually part of the concept of trade as a single transaction.

    To me this seems similar to saying “well we only have a Transaction object, so let’s make a bank account in its static context.” (I get that we’re only storing the “latest” one, but still.)

    In my opinion, the fact that we want to track the latest trade is pointing to the need for a new object. Maybe the missing concept here could be reasoned out from what are the possible meanings of Symbol, such as a “stock” or “option” (which is specifically raised by Alice in the dialogue). The “stock” or “option” is the real-world concept that has a symbol, a latest trade, and a price that is constantly changing based on time-series data from trades. It’s certainly not something that should be encapsulated inside of Trade come hell or high water, but a first-class real-world concept that’s going to come up all the time, and which has its own completely distinct meaning.

    Like

    1. Thanks for your comments, very good points.

      First, yes, the Trade is a single trade. As you mention this refers to a real-world concept of a buyer and seller coming together for a single transaction. However, to say that it always contains a date&time, symbol, volume and price is a bit misleading. Bob argues too, that the Symbol will always be there, so there is no harm publishing it.

      Aside from theoretical problems it causes for Object-Orientation to do such things, it has real pragmatical consequences. When you publish the Symbol you essentially have given up control over it. You burden the user of the code too, who now has to understand what a Symbol is and how it can be used, instead of just offering the functionality needed. Also you fix the structure of the Trade, that prevents change inside the trade. What if I want to move the Symbol to Stock and Option as you mention, and have those in the Trade? Now I have to publish the Symbol in those also, just to fulfil the promises I made for Trade.

      As for the concrete implementation, the Trade itself is of course not tracking the previous trades. It can however create another object (Cache) to do it. There may be other, nicer ways to do this, but it has to be inside the Trade.

      The point is to localize change. If you publish those things it will be difficult to change them, because the logic that relies upon them is not co-located, it is distributed everywhere in the code.

      There were actually many changes to this class since (it is a real project), and the Symbol is no longer in the Trade, it is higher up in a more abstract concept. We could however maintain the interface of the Trade, because we don’t publish data, we publish meaningful business functions that are needed regardless of how the Trade is structured.

      Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s