Abstractions are Important 3 – “What?” Before “How?”
So, I’m back from my two weeks overseas, refreshed, enriched, and generally wiser, I suppose. We traveled to Spain and Portugal, visiting a ton of historic sites, eating good food and having fun. For my first post back, I’d like to make a third post in my series on abstractions.
Methods as Recipes
I was looking at some code yesterday. It was some long method, probably 60 or 70 lines long, and I sighed as scrolled disinterestedly through it. At the moment, I couldn’t muster the energy to try to figure out what the author thought it did or probably wanted it to do, so I started ruminating about what leads to methods like this. And, I think I understand it. It’s the idea of a method as a recipe.
By way of homage to Spain, let’s write a “CookPaella()” method. When writing methods, do you ever start by doing the following?
public void CookPaella()
{
//1. Get a medium bowl
//2. Mix together 2 tablespoons olive oil, tsp paprika, , tsp oregano, salt and pepper to taste
//3. Stir in 2 pounds chicken breasts cut into 2 inch pieces, to coat
//4. Refrigerate chicken.
//5. Heat 2 tablespoons olive oil in paella pan over medium heat
//6. Stir in 3 cloves garlic, 1 tsp red pepper flakes, and 2 cups rice
//7. Cook to coat rice with oil -- about 3 minutes
//8. Stir in a pinch of saffron, 1 bay leaf, 1/2 bunch parsley, 1 quart chicken stock and optional zest of two lemons
//9. Bring to a boil, then cover, reduce heat to medium low and simmer 20 minutes
//10. Meanwhile, heat 2 tbsps olive oil in a separate skillet over medium heat and stir in marinated chicken with onions and cook for 5 minutes.
//11. Stir in bell pepper and sausage and cook for 5 minutes.
//12. Stir in shrimp, turning until both sides are pink
//13. Spread rice mixture onto a serving tray, topping with meat and seafood mixture
}
(recipe compliments of All Recipes).
This is a sane approach. Much like making an outline for an essay in English class, you list out the basic procedure that you want to follow, and you fill in the details:
public void CookPaella()
{
//1. Get a medium bowl
var myBowl = new Bowl("Medium");
//2. Mix together 2 tablespoons olive oil, tsp paprika, , tsp oregano, salt and pepper to taste
myBowl.AddTablespoons(2, "olive oil");
myBowl.AddTeaspoons(1, "paprika");
myBowl.AddTeaspoons(1, "oregano");
myBowl.AddPinch("salt");
myBowl.AddPinch("pepper");
//3. Stir in 2 pounds chicken breasts cut into 2 inch pieces, to coat
//4. Refrigerate chicken.
//5. Heat 2 tablespoons olive oil in paella pan over medium heat
//6. Stir in 3 cloves garlic, 1 tsp red pepper flakes, and 2 cups rice
//7. Cook to coat rice with oil -- about 3 minutes
//8. Stir in a pinch of saffron, 1 bay leaf, 1/2 bunch parsley, 1 quart chicken stock and optional zest of two lemons
//9. Bring to a boil, then cover, reduce heat to medium low and simmer 20 minutes
//10. Meanwhile, heat 2 tbsps olive oil in a separate skillet over medium heat and stir in marinated chicken with onions and cook for 5 minutes.
//11. Stir in bell pepper and sausage and cook for 5 minutes.
//12. Stir in shrimp, turning until both sides are pink
//13. Spread rice mixture onto a serving try, topping with meat and seafood mixture
}
This is great because rather than starting without any kind of gameplan, we’ve stubbed everything out that needs to happen, and now we’re in the process of filling in the template. Whether you’re cooking or assembling a piece of furniture or anything else, there is a tendency to read through (or skip) to the end so that the actual following of the instructions reveals no mysteries. We take the same approach here.
Once this is complete, you’re going to have a large method, so some refactoring is probably in order. At the very least, our numbered bullets provide some logical methods to create:
public void CookPaella()
{
//1. Get a medium bowl
var myBowl = new Bowl("Medium");
//2. Mix together 2 tablespoons olive oil, tsp paprika, , tsp oregano, salt and pepper to taste
MixTogether(myBowl);
//3. Stir in 2 pounds chicken breasts cut into 2 inch pieces, to coat
//4. Refrigerate chicken.
//5. Heat 2 tablespoons olive oil in paella pan over medium heat
//6. Stir in 3 cloves garlic, 1 tsp red pepper flakes, and 2 cups rice
//7. Cook to coat rice with oil -- about 3 minutes
//8. Stir in a pinch of saffron, 1 bay leaf, 1/2 bunch parsley, 1 quart chicken stock and optional zest of two lemons
//9. Bring to a boil, then cover, reduce heat to medium low and simmer 20 minutes
//10. Meanwhile, heat 2 tbsps olive oil in a separate skillet over medium heat and stir in marinated chicken with onions and cook for 5 minutes.
//11. Stir in bell pepper and sausage and cook for 5 minutes.
//12. Stir in shrimp, turning until both sides are pink
//13. Spread rice mixture onto a serving try, topping with meat and seafood mixture
}
private static void MixTogether(Bowl myBowl)
{
myBowl.AddTablespoons(2, "olive oil");
myBowl.AddTeaspoons(1, "paprika");
myBowl.AddTeaspoons(1, "oregano");
myBowl.AddPinch("salt");
myBowl.AddPinch("pepper");
}
There, look at that. We’re going to have this reduced to a nice 13 line method and, we could probably even group the calls further from there, resulting in a CookPaella() method that had three instructions: Prep(), CookRice(), CookMeat(). Those methods would consist of three or four lines themselves, and things would spread on out from there like a tree. This is a series of well factored methods that are probably clean and reasonable (discounting the fact that we’re instantiating what we need with Bowl, rather than having variables passed in — that’s for example purposes and not a weakness of the approach).
So where do these long methods come from? Well, I would argue that they come from people thinking in terms of recipe, but not creating or factoring to the outline. That is, they sit down to write the method and simply start banging out code line by line until they’re done. They think in terms of a recipe — a procedure — and methods are simply containers for procedures. So, when they start out, they don’t know what all a method is going to do; rather, the method twists and winds and meanders its way toward some eventual end in ad-hoc fashion.
In a less contrived case than this one, a method will probably start out as just a jump point for a series of instructions. The instructions are coded sequentially until there are no more instructions and then the method is at an end. The “how” is defined, and then the author looks at the “how” and decides what to name the method. He describes “how” and then, based on “how”, decides “what”. Ah, I see that I’ve assembled a series of instructions that seems to cook a paella, so “CookPaella” is probably a good thing to call this.
Methods as Abstractions
So, is there another way to do this? Absolutely. You can flip the script and decide “what” without worrying about “how”. With this approach, we completely discard the procedural/sequential concept and focus instead on creating meaningful abstractions. Procedural/sequential programming is good for, say, batch scripts, but object oriented programmers need to think in abstractions. I don’t want a specific, blow-by-blow recipe for cooking paella to become the ‘architecture’ of my code. I want to write code that a cook can use to get things done. That’s an important distinction.
Let’s think of our paella cooking a little differently. Let’s just think of it as cooking. In this fashion, we can define implements like pots and pans, ingredients like paprika and meat, and actions, like sear or boil. From there, we can start thinking about an object model. What is a “bowl” and what should it do? A bowl does things other than mix ingredients for paella — it has properties and, depending on the object model, may have some actions. Let’s decide what those properties and actions are without worrying about how they work.
For example, let’s define a bowl, along with concepts called “ingredient” and “quantity”:
public class Ingredient
{
public Ingredient()
{
}
}
public class Quantity
{
public Quantity()
{
}
}
public interface Bowl
{
int Diameter { get; set; }
int Height { get; set; }
void Add(Ingredient ingredient, Quantity quantity);
void MixIngredients();
}
Notice that we have a Bowl interface, rather than any kind of implementation. How do these methods work? Who cares. We don’t need to know that to make our paella. From here, we might define a paella pan interface as well, and perhaps various inheritors of ingredient and quantity, depending on their implementations. The point is, we’ll reason about each individual object and how it should behave. After we do enough of this, we can start to create larger constructs, such as some MeatCooker class that takes an arbitrary number of meats and a pan and cooks them. A few classes like this, and before you know it, your CookPaella() method will write itself.
Notice that the “start at the beginning and sequentially do everything” scripting style approach is gone, but so too is the structured, outline approach. With this abstraction-based approach, we’re defining objects with properties and behaviors that make sense in our domain model. This is what allows easy assembly. But, the important thing to understand is we’re defining abstractions rather than procedures. These are going to be easier to reason about, and they’re also going to be more flexible. We can use our meat cooker and pan and bowl to cook lasagnas as well as paellas.
So, the overriding message here is to think sequentially and abstractly when considering how to model a problem in an object oriented language. Don’t think about “how” until the very end. Instead, think about “what”. What objects should you have? What properties should they have? What actions? What interactions do you foresee for them. As you answer these questions, the specifics will become much easier. The longer you defer those specifics, the more flexible the design. You’re using an object oriented language, so leverage that language. Don’t code like you’re batch scripting. Don’t model your application by writing recipes even when you’re modeling, well, cooking a recipe.
Abstruction is very important in Java for proper polymorphism ( dynamic runtime binding) and multiple inheritance, because you can extend / inherit only one base class, but you can implement multiple interfaces, also one interface can extend multiple interfaces. I think, the best design will be define all common methods in interface and create a Abstruct base class to implement all the common methods ( just like Adapter design pattern ) and extend different subclass types from base Abstruct class and override only those business methods specific to that subclass. Lastly use only interface reference from any method as a parameter,… Read more »
Hi Madhu,
Thanks for the comment and for reading. The “abstraction” I’m referring to in my post is more the philosophical definition rather than language mechanics. That is, I’m talking about abstraction as a mechanism for simplifying a complex concept for users (as we do when writing methods).
I do like your thoughts on polymorphism in general, though, especially about preferring to retain local references to interfaces instead of specific object instances.