Missing Opportunities for Polymorphism

From WikiContent

(Difference between revisions)
Jump to: navigation, search
Current revision (03:39, 19 July 2009) (edit) (undo)
 
(11 intermediate revisions not shown.)
Line 1: Line 1:
-
It is not without irony that one of the best examples of polymorphism is also a sign that you are missing opportunities to exploit polymorphism. The example is best expressed in Smalltalk where the if-then-else control structure is implemented as method call. If you send an if: ablock else: anotherBlock message to true, it's implementation of if:else: will execute ablock. Sending that same message to false will cause anotherBlock to be executed. This side by side implementation is demonstrated in listing 1.
+
Polymorphism is one of the grand ideas that is fundamental to OO. The word, taken from Greek, means many (''poly'') forms (''morph''). In the context of programming polymorphism refers to many forms of a particular class of objects or method. But polymorphism isn't simply about alternate implementations. Used carefully, polymorphism creates tiny localized execution contexts that let us work without the need for verbose ''if-then-else'' blocks. Being in a context allows us to do the right thing directly, whereas being outside of that context forces us to reconstruct it so that we can then do the right thing. With careful use of alternate implementations, we can capture context that can help us produce less code that is more readable. This is best demonstrated with some code, such as the following (unrealistically) simple shopping cart:
-
|
+
-
true
+
-
if: aBlock else: anotherBlock
+
-
^aBlock execute.
+
-
false
+
public class ShoppingCart {
-
if: aBlock else: anotherBlock
+
private ArrayList<Item> cart = new ArrayList<Item>();
-
^anotherBlock execute.
+
public void add(Item item) { cart.add(item); }
 +
public Item takeNext() { return cart.remove(0); }
 +
public boolean isEmpty() { return cart.isEmpty(); }
 +
}
-
Listing 1. Smalltalk's polymorphic implementation of if:else:
+
Let's say our webshop offers items that can be downloaded and items that need to be shipped. Let's build another object that supports these operations:
-
By unifying the API for these booleans, we can write code such as "result := someBoolean if: aBlock else: anotherBlock." and end up with the expected results. However, the fact that we had to execute and if-then-else in the first place was because at some point in the past we lost or have not properly captured some vital piece of information. Now that this bit of information is missing, we have to recreate that information and the tool that is most commonly used to do that is if-then-else.
+
public class Shipping {
 +
public boolean ship(Item item, SurfaceAddress address) { ... }
 +
public boolean ship(Item item, EMailAddress address { ... }
 +
}
-
How does this relate to polymorphism? One of the beautiful features of capturing state and behavior into an object is that these objects act as mini execution contexts. If we revisit the code in listing 1, we can see that we don't have to make any decisions. We can use the fact that our execution context is either true or false allows us to get straight to the block that is to executed. If we didn't completely understand the context in which we were working in, we'd be forced to some how rebuild that context in order for us to know what to do.
+
When a client has completed checkout we need to ship the goods:
-
Lets consider the case where we are modeling bank accounts. Now we could define all accounts as just that, Account. We could distinguish between a checking and savings account an attribute (enum?), accountType. I could also capture the distinction by creating two classes, CheckingAccount and SavingsAccount. Lets say we now implement an accrueInterest() method where each type of account has a different interest rate. With the former case we will have to write some logic to match the type of account with the rate of interest. With the later, each object knows what it is and hence can just directly acquire the interest rate. If can do this because of how it is defined without using if-then-else to rebuild that information.
+
while (!cart.isEmpty()) {
 +
shipping.ship(cart.takeNext(), ''???'');
 +
}
-
Classes designed to exploit polymorphism have a much reduced dependency on if-then-else. They also tend to be more readable and contain less code. Each of these attributes show that improved polymorphism is more than just a neat coding trick, it's a technique that leads to better code.
+
The ''<code>???</code>'' parameter isn't some new fancy elvis operator, it's asking should I email or snail-mail the item? The context needed to answer this question no longer exists. We have could captured the method of shipment in a <code>boolean</code> or <code>enum</code> and then use an ''if-then-else'' to fill in the missing parameter. Another solution would be create two classes that both extend Item. Lets call these <code>DownloadableItem</code> and <code>SurfaceItem</code>. Now let's write some code. I'll promote <code>Item</code> to be an interface that supports a single method, <code>ship</code>. To ship the contents of the cart, we will call <code>item.ship(shipper)</code>. Classes <code>DownloadableItem</code> and <code>SurfaceItem</code> will both implement <code>ship</code>.
 +
 
 +
public class DownloadableItem implements Item {
 +
public boolean ship(Shipping shipper) {
 +
shipper.ship(this, customer.getEmailAddress());
 +
}
 +
}
 +
 +
public class SurfaceItem implements Item {
 +
public boolean ship(Shipping shipper) {
 +
shipper.ship(this, customer.getSurfaceAddress());
 +
}
 +
}
 +
 
 +
In this example we've delegated the responsibility of working with Shipping to each Item. Since each Item knows hows it's best shipped, this arrangement allows us to get on with it without the need for an ''if-then-else''. The code also demonstrates a use of two patterns that often play well together: command and double dispatch. Effective use of these patterns relies on careful use of polymorphism. When that happens there will be a reduction in the number of ''if-then-else'' blocks in our code.
 +
 
 +
While there are cases where it's much more practical to use ''if-then-else'' instead of polymorphism, it is more often the case that a more polymorphic coding style will yield a smaller, more readable and less fragile code base. The number of missed opportunities is a simple count of the ''if-then-else'' statements in our code.
 +
 
 +
By [[Kirk Pepperdine]]
 +
 
 +
This work is licensed under a [http://creativecommons.org/licenses/by/3.0/us/ Creative Commons Attribution 3]
 +
 
 +
 
 +
 
 +
Back to [[97 Things Every Programmer Should Know]] home page

Current revision

Polymorphism is one of the grand ideas that is fundamental to OO. The word, taken from Greek, means many (poly) forms (morph). In the context of programming polymorphism refers to many forms of a particular class of objects or method. But polymorphism isn't simply about alternate implementations. Used carefully, polymorphism creates tiny localized execution contexts that let us work without the need for verbose if-then-else blocks. Being in a context allows us to do the right thing directly, whereas being outside of that context forces us to reconstruct it so that we can then do the right thing. With careful use of alternate implementations, we can capture context that can help us produce less code that is more readable. This is best demonstrated with some code, such as the following (unrealistically) simple shopping cart:

public class ShoppingCart {
    private ArrayList<Item> cart = new ArrayList<Item>();
    public void add(Item item) { cart.add(item); }
    public Item takeNext() { return cart.remove(0);  }
    public boolean isEmpty() { return cart.isEmpty(); }
}

Let's say our webshop offers items that can be downloaded and items that need to be shipped. Let's build another object that supports these operations:

public class Shipping {
    public boolean ship(Item item, SurfaceAddress address) { ... }
    public boolean ship(Item item, EMailAddress address { ... }
}

When a client has completed checkout we need to ship the goods:

while (!cart.isEmpty()) {
    shipping.ship(cart.takeNext(), ???);
}

The ??? parameter isn't some new fancy elvis operator, it's asking should I email or snail-mail the item? The context needed to answer this question no longer exists. We have could captured the method of shipment in a boolean or enum and then use an if-then-else to fill in the missing parameter. Another solution would be create two classes that both extend Item. Lets call these DownloadableItem and SurfaceItem. Now let's write some code. I'll promote Item to be an interface that supports a single method, ship. To ship the contents of the cart, we will call item.ship(shipper). Classes DownloadableItem and SurfaceItem will both implement ship.

public class DownloadableItem implements Item {
    public boolean ship(Shipping shipper) {
        shipper.ship(this, customer.getEmailAddress());
    }
}

public class SurfaceItem implements Item {
    public boolean ship(Shipping shipper) {
        shipper.ship(this, customer.getSurfaceAddress());
    }
}

In this example we've delegated the responsibility of working with Shipping to each Item. Since each Item knows hows it's best shipped, this arrangement allows us to get on with it without the need for an if-then-else. The code also demonstrates a use of two patterns that often play well together: command and double dispatch. Effective use of these patterns relies on careful use of polymorphism. When that happens there will be a reduction in the number of if-then-else blocks in our code.

While there are cases where it's much more practical to use if-then-else instead of polymorphism, it is more often the case that a more polymorphic coding style will yield a smaller, more readable and less fragile code base. The number of missed opportunities is a simple count of the if-then-else statements in our code.

By Kirk Pepperdine

This work is licensed under a Creative Commons Attribution 3


Back to 97 Things Every Programmer Should Know home page

Personal tools