Technical Blog

2 Posts tagged with the design tag

I've been seeing some misuse of inheritance in Java lately, and wanted to share some thoughts on when to use inheritance, and when to use composition. As a general rule, I would say that I almost always try to use composition, except where it is particularly cumbersome. I'll try to explain some of the reasons for using composition instead of inheritance, along with some concrete examples.

 

1. Building a Better Map


Let's start with the example of building a case-insensitive map. We want a map where the key is always the lower-case toString() of the key. Let's try implementing this with inheritance.

public class NoCaseHashMap extends HashMap {
 
    private String toKey(Object key) {
        if (key == null) {
            return null;
        }
        return key.toString().toLowerCase();
    }
 
    public Object get(Object key) {
        return super.get(toKey(key));
    }
 
    public Object put(Object key, Object value) {
        return super.put(toKey(key), value);
    }
 
    public boolean containsKey(Object key) {
        return super.containsKey(toKey(key));
    }
 
    public Object remove(Object key) {
        return super.remove(toKey(key));
    }
}

 

Pretty easy, right? But what if we need a case-insensitive map where they keys are stored in alphabetical order? We'd probably want a TreeMap instead of HashMap. Do we create a new class, NoCaseTreeMap that extends TreeMap? That way lies madness. We'll end up having to copy-paste all the methods from our NoCaseHashMap. Let's try reimplementing it with composition and the decorator pattern.
(If you'd like a refresher on the wonderful decorator pattern, have a look here: http://www.javaworld.com/javaworld/jw-12-2001/jw-1214-designpatterns.html)

public class NoCaseMap implements Map {
 
    private Map map;
 
    public NoCaseMap(Map map) {
        this.map = map;
    }
 
    private String toKey(Object key) {
        if (key == null) {
            return null;
        }
        return key.toString().toLowerCase();
    }
 
    public Object get(Object key) {
        return map.get(toKey(key));
    }
 
    public Object put(Object key, Object value) {
        return map.put(toKey(key), value);
    }
 
    public boolean containsKey(Object key) {
        return map.containsKey(toKey(key));
    }
 
    public Object remove(Object key) {
        return map.remove(toKey(key));
    }
 
    /* Note the need to implement putAll(map) with composition */
    public void putAll(Map t) {
        for (Iterator it = t.entrySet().iterator(); it.hasNext();) {
            Map.Entry entry = (Map.Entry) it.next();
            put(entry.getKey(), entry.getValue());
        }
    }
 
    // --------------------------------------
    // The rest of these are just the standard cruft of composition:
    // --------------------------------------
    public void clear() {
        map.clear();
    }
 
    public boolean containsValue(Object value) {
        return map.containsValue(value);
    }
 
    public Set entrySet() {
        return map.entrySet();
    }
 
    public boolean isEmpty() {
        return map.isEmpty();
    }
 
    public Set keySet() {
        return map.keySet();
    }
 
    public int size() {
        return map.size();
    }
 
    public Collection values() {
        return map.values();
    }
}

 

Obviously that's a lot more code, but it also gives us a lot more. We can have a new NoCaseMap(new TreeMap()) as we wanted, as well as a new NoCaseMap(new LinkedHashMap()) or a new NoCaseMap(new  ConcurrentHashMap()), and so on and so forth. Eclipse helps us out with  this a bit too. Those seven last methods can be implemented in a few keystrokes with Eclipse, with Source -> Generate Delegate Methods. You might notice this pattern is similar to InputStream and its family. It's also the way the JDK does it for things like Collections.unmodifiableMap. InputStream takes it a step further by having a FilterInputStream class that already implements all the methods as boilerplate methods calling the delegate, so if you want to decorate an InputStream, you can just extend FilterInputStream, and override the methods you care about. (Those of you who are particularly on the ball might comment that InputStream is a class, not an interface, but the pattern still applies.)

 

2. Teamwork is Great, but Sometimes You Can't Depend on Others

 

Another example, taken from recent work, would be a service that makes HTTP calls to a third party and parses the response. Since this service needs to make HTTP calls, let's extend our HttpClientImpl class.

public class WidgetServiceImpl extends HttpClientImpl implements WidgetService {
    private static final String WIDGET_LIST_URL = "http://rest.widgets-inc.com/widgets";
    private final WidgetListParser parser;
    
    public WidgetServiceImpl(WidgetListParser parser) {
        this.parser = parser;
    }
 
    public List<Widget> getWidgets() {
        final String content = callHttp(WIDGET_LIST_URL);
        return parser.parse(content);
    }
}

 

This  works fine, until you try to unit test it. How do you run your unit test if Widgets Unlimited's server is down? What if you're on the plane? Your WidgetServiceImpl is tightly coupled with HttpClientImpl and can't be tested without actually talking to this external server. We're depending on their server in order to test our code. Let's try again, but with composition.

public class WidgetServiceImpl implements WidgetService {
    public static final String WIDGET_LIST_URL = "http://rest.widgets-inc.com/widgets";
    private final WidgetListParser parser;
    private final HttpClient httpClient;
    
    public WidgetServiceImpl(HttpClient httpClient, WidgetListParser parser) {
        this.httpClient = httpClient;
        this.parser = parser;
    }
 
    public List<Widget> getWidgets() {
        final String content = httpClient.callHttp(WIDGET_LIST_URL);
        return parser.parse(content);
    }
}

 

With an HttpClient being passed in now, we can pass in some dummy implementation that doesn't talk to a real server. It can do whatever we need in order to test our WidgetServerImpl.
(You may notice I've used HttpClient instead of HttpClientImpl. I like putting lots of interfaces on things, because it makes them more convenient to test with JMock.)

 

Note also that WidgetListParser is being passed in, not constructed within the WidgetServiceImpl. This also makes testing easier; if we mock/stub that, we can test WidgetServiceImpl without having to worry about what the content actually looks like. We'll test that separately when we test WidgetListParserImpl. Testing WidgetServiceImpl with JMock now is easy.

@RunWith(JMock.class)
public class WidgetServiceImplTest {
 
    private Mockery mockery = new Mockery();
 
    private HttpClient httpClient;
    private WidgetListParser parser;
    private WidgetService widgetService;
 
    @Before
    public void setUp() {
        httpClient = mockery.mock(HttpClient.class);
        parser = mockery.mock(WidgetListParser.class);
 
        widgetService = new WidgetService(httpClient, parser);
    }
 
    @Test
    public void shouldMakeHttpCallAndReturnWidgetList() {
        final String content = "some dummy content";
        final List<Widget> expectedWidgetList = new ArrayList<Widget>();
 
        mockery.checking(new Expectations() {{
            one(httpClient).callHttp(WidgetServiceImpl.WIDGET_LIST_URL);
            will(returnValue(content));
 
            one(parser).parse(content);
            will(returnValue(expectedWidgetList));
        }});
 
        final List<Widget> actualWidgetList = widgetService.getWidgets();
        Assert.assertSame(expectedWidgetList, actualWidgetList);
    }
}

 

3. When to Use Inheritance and When to Use Composition

 

The traditional answer to the question is to use inheritance if there is an "is a" relationship between the classes and composition when there is a "has a" relationship. I don't think that's entirely sufficient  though. I agree that if it's not an "is a" relationship, then it certainly shouldn't be done with inheritance. Is WidgetService an HttpClient? It uses an HttpClient to do it's job, but as far as consumers of the WidgetService are concerned, it could just as well talk to a database. But even if there is an "is a" relationship, there are plenty of times to use composition instead. Does the class you're extending have an interface? If you don't need to call protected methods of that class, consider composition instead. If a class ends with "Util" or "Helper", they should always be used via composition, not inheritance.

 

If any of this was new to you, you may also enjoy reading this:
http://www.tiedyedfreaks.org/eric/CompositionVsInheritance.html
Some of my examples were stolen liberally from that page.

0 Comments Permalink

Checkout Revisited

Posted by Janis Lanka Oct 1, 2009

The checkout process is quite possibly the scariest part of the online shopping experience. Customers get anxious about divulging sensitive personal information, not to mention parting with their hard-earned cash. Long and complicated customer registration forms to fill out, strange error messages, security concerns, too many steps, etc., all contribute to shopper frustration and all too often, cart abandonment.

 

We recently did a review of our current out-of-the-box checkout process and we made the following observations:

 

  • There are too many steps, resulting in a high cart abandonment rate.
  • The distinction between customer types is confusing. "Am I a registered customer or an existing one?" "What's a 'guest'?"
  • Giving the option to register during the first step of checkout is an unnecessary distraction. It gives customers too much time to change their mind!
  • While the cart total is visible during each step, it doesn't provide a breakdown on specific items that have been added to the cart.
  • Customers who have a promotion code don't know where to enter it, which leads to frustrated calls to customer service.

 

We started looking at ways to simplify the process and we came up with a shorter, smoother, and clearer checkout process that works well for various industries and customer types/profiles. In this new and improved checkout process, there are only three stages:

  • shipping information
  • billing information
  • receipt and (optional) registration.

Shipping Information

In the new process, the login and shipping information pages are merged into a single page. If a returning customer can't remember their login name or password, they can skip it and just enter the shipping details without any extra clicks. Here, guests and new customers are treated the same.


The contents of the shopping cart are displayed on this page and on both of the other checkout pages, so the customer always knows exactly what they're buying. The coupon and promo code fields are also available at each step, under the shopping cart summary. Once a coupon or promo code is applied, the cart total is updated automatically via AJAX.

c_shipping.png

 

Billing Information

The billing step asks for the customer's email address and any other information required for invoice purposes. All the shipping information entered in the last step appears once again for final customer review. The final confirmation block is a good place to have a subscription or "sign up for email alerts" check box. Even if customers don't create an account, they may still choose to be on the mailing list.

c_billing.png

 

Receipt and Registration

Unlike the standard checkout process, the customer is only asked to register for an account after they've made their purchase. Some retailers might worry that nobody will register unless they're required to. If that's a concern, you can provide some incentive for registering (like a discount on their next purchase). Keep in mind that the point is to avoid distracting the shopper from their goal, which is to make their purchase.

 

If the customer registers for an account after checkout is complete, that order will be associated with the new account. Unfortunately, previous orders they created will not be, even if they were created under the same email address as the new account. (This is for security reasons.)

c_confirmation.png

 

This new checkout process will require using AJAX/JavaScript validation at every step, making it a bit tedious to code, but worth it in the long run.

 

We are currently in the process of integrating this new process for several of our clients and will be keeping a close eye on their performance and results. Of course, this process may not work for every ecommerce site. Hopefully this will at least give you some inspiration for ways to improve your own checkout process. There are always areas that can be simplified and streamlined. Just keep testing various aspects of your checkout process. That's the only way to really learn what works best for your audience.

 

Stay tuned and subscribe to our RSS feed because down the road we will follow up with another post regards the results and observations.

1 Comments Permalink