Be the worst at something

Emotionally its easy to be a young moderately talented programmer. Your boss's expectations are probably low to average, so when you knock it out of the park it hits the goddamn moon. You're probably a cowboy, and business people adore cowboys. You hack code at lightning speed because you're not worried about how your work affects tomorrow because you've never seen tomorrow, you yes-you-were-born-yesterday son of a bitch.

Somewhere along the line things change. You wonder, "When did my job become maintaining code? All the code I've written is shit!" You hightail it to the interwebs and read everything on writing maintainable code. You're ambitious and you take it as far as you can.

Armed with all your design patterns, best practices, years of experience, and senior-level title you're off to the races. Business people don't understand why your projects seem to take a bit longer, but who cares. Your developer peers looks at you like you are motherfucking Neo. Expectations are high, you are The One after all.

Your comfort zone has ballooned. You spend all day, every day within arms reach of it. That is what your job is asking of you, of course. You refine your process ever so finely. You're becoming an expert on the things you do most days.

Then a curveball comes. A new challenge. A paradigm shift. You need to adapt, but adapting isn't so easy anymore. A junior developer would handle it fine, and why wouldn't he? He went from working as a novice in subject A to working as a novice in subject B and thats where his managers expect him to be.

But you're a different story. You have high expectations, rules, practices. You don't just hack code. It actually feels impossible for you to submit code you know is terribly flawed.

You start building a set of patterns and practices to deal with this new challenge based on the patterns and practices that already serve you with your regular day-to-day work. Your first projects are bloated with refactoring cycles. You can see your mistakes but sometimes you aren't too sure the right way to solve them. You aren't just writing code; you're thinking, researching, and making a lot of new decisions. You're patient and adjust as you go. Adjusting takes time. Maybe lots of time, and now you're behind schedule.

This is a bad spot for anxious people. Your expectations and maybe your project managers' expectations are too high given the reality of the situation. You aren't good at this - not yet - and your senior-level title doesn't change that fact.

If you're here theres a few things I think you should keep in mind:

  • Forgive yourself. You don't know everything, and, unless you're an asshole, you probably don't pretend to. This challenge is one of those things you just don't know. Be honest and upfront about it.
  • Don't expect perfection. Your standards should be high but you need to face the fact that your first project in this new world will not be your best work and thats OK.
  • Don't go into hero-mode where you stop communicating and put in a billion extra hours to meet that overconfident project estimate that seemed reasonable before you dove in. Hero mode seems like a good idea only if it works (you make the deadline), but if it doesn't work you look like a bad programmer and, in fact, are a bad communicator and teammate.
  • Communicate. Let your managers know how your project is progressing. How they react to it is not something you can control, so don't burden yourself by worrying about it. This is a common situation that everyone needs to learn how to deal with.
  • You probably won't get fired. Its OK to get a chink in your armor once in a while. Lots of companies are bad at letting loose under-performing programmers, and if the story thus far describes you then you aren't one of those guys anyways.
  • The absolute worst thing that could possibly happen here is you get fired. Not beaten or murdered in the street. It seems silly to point this out, but this is the absolute worst case scenario and its recoverable. When it comes to anxiety its easy to forget these simple facts.
  • Your employer is better off giving you the time to learn. If you don't learn you stagnate and your skills become irrelevant as the next wave of new technologies comes in and takes over. At this point your employer has invested years and an increasing salary into an employee whose practical value may be dropping.
  • If you get fired you might be better off. Believe it or not, you want new technical challenges. The reality is that the risk you're facing is low and the potential gain is high. There could be more risk in not facing challenges like these.

When you're terrible at something theres no place to go but up. Stay positive and embrace the opportunity for learning. A good employer understands that this is part of doing business and its in everyones best interests to continue to invest in you. You're a senior developer. You're a leader. A strong leader stands to strengthen those he leads. Be a strong, confident leader and march on!

Backbone for Invertebrates

Its 2013 and I'm about to talk about Backbone.js, a four year old library that isn't on the bleeding edge of anything. This isn't a post about Backbone.js so fans of AngularJS, Knockout, or new M-V-yadda-yadda-yadda framework can settle down. I want to focus on what we can learn from any system like Backbone.js.

Backbone.js is a JavaScript library that separates data from display. It defines data in terms of Models and Collections [of Models]. UI work is left to Views. Views generate HTML and react to user-initiated events like button clicks. Views may change Model and Collection data and they may react to changes to Model and Collection data. These changes are communicated using custom Events. Backbone.js is not an MVC framework, but its close.

As I eluded to earlier, this kind of system is not new and its not unique. I think most programmers have heard and, to some extent, understand this approach, yet, in my experience, JavaScript is usually not written this way unless a project already uses Backbone or another similar library as part of standard practice. Why is that? There are probably a lot of reasons and I'd like to address a couple:

  • The idea that this type of structure is not beneficial on small projects
  • The idea that you can't have this kind of structure without Backbone.js or a similar system

Lets explore these reasons by building a small photo gallery.

Evolution of a photo gallery

Our photo gallery will show three thumbnails and one large photo. When you click a thumbnail the large photo space shows that photo. If you asked a typical developer to build this product, how would they implement it? I might guess something like this...

$(function() {
    var photos = [
        { thumbnail: '0-thumbnail.jpg', large: '0-large.jpg' },
        { thumbnail: '1-thumbnail.jpg', large: '1-large.jpg' },
        { thumbnail: '2-thumbnail.jpg', large: '2-large.jpg' }
    ];

    var thumbnails = $('#thumbnails'),
        large = $('#large');    

    // add thumbnails
    var imgs = [];
    $.each(photos, function(index, photo) {
        var thumbnail = $('<img>')
            .prop('src', photo.thumbnail)
            .prop('alt', index)
            .data(photo);
        imgs.push(thumbnail);
    });
    thumbnails.append(imgs);

    // listen for thumbnail clicks
    thumbnails.on('click', 'img', function() {
        $('img', large).attr('src', $(this).data().large);
    });

    // add large photo
    var img = $('<img>')
        .prop('src', photos[0].large)
        .prop('alt', 'large');
    large.append(img);
}); 

See it on jsfiddle

Only about 30 lines of code. We have one type of control, the clickable thumbnails. One type of data, the photos. The data is coupled with the thumbnails using jQuery's .data() method, which "stores arbitrary data associated with the matched elements". I use event delegation using jQuery's .delegate() method so I only need one listener on my thumbnails container rather than one listeners on each individual thumbnail. I also benefit from being able to add and remove thumbnails from inside the container without having to re-attach event listeners each time.

My equivalent Backbone.js implementation is below. You'll notice that its not so terse - about 100 lines of code. More than three times the code for the same result.

$(function() {
    var Photo = Backbone.Model.extend({});

    var Photos = Backbone.Collection.extend({
        model: Photo,

        initialize: function() {
            this.index = 0;
        },

        goTo: function(index) {
            if(index == this.index) return;

            this.index = index;
            this.trigger('goto');
        },

        current: function() {
            return this.at(this.index);
        }
    });

    var LargeView = Backbone.View.extend({
        el: '#large',

        initialize: function() {
            this.listenTo(this.collection, 'goto', this.change);
        },

        render: function() {
            var photo = this.collection.current();

            var img = $('<img>')
                .prop('src', photo.get('large'));

            this.$el.append(img);

            return this;
        },

        change: function() {
            this.$el.empty();
            this.render();
        }
    });

    var ThumbnailsView = Backbone.View.extend({
        el: '#thumbnails',

        events: {
            'click img': 'click'
        },

        render: function() {
            var imgs = [];
            $.each(this.collection.models, function(index, photo) {
                var thumbnail = $('<img>')
                       .prop('src', photo.get('thumbnail'))
                       .data('index', index);
                imgs.push(thumbnail);
            });
            this.$el.append(imgs);

            return this;
        },

        click: function(event) {
            this.collection.goTo($(event.currentTarget).data('index'));
        }
    });

    var AppView = Backbone.View.extend({
        el: '#gallery',

        initialize: function() {
            this.photos = new Photos();
            this.photos.add([
                { thumbnail: '0-thumbnail.jpg', large: '0-large.jpg' },
                { thumbnail: '1-thumbnail.jpg', large: '1-large.jpg' },
                { thumbnail: '2-thumbnail.jpg', large: '2-large.jpg' }
            ]);

            this.large = new LargeView({
                collection: this.photos
            });

            this.thumbnails = new ThumbnailsView({
                collection: this.photos
            });
        },

        render: function() {
            this.thumbnails.render();
            this.large.render();
        }
    });

    new AppView().render();
});

See it on jsfiddle

This version definitely leaves you with a lot more to soak in, though I think a developer familiar with Backbone.js would know whats going on immediately. We have a Photo Model for our photos and a Photos Collection which I've modified so it implements a simple Iterator pattern. Then we have two Views. One controls the large photo and the other controls the group of thumbnails. Finally, we have AppView which just ties the whole gallery together.

The ThumbnailsView has an event listener. If the user clicks an image the Photos Collection is told to go to the clicked image index within the collection. This fires a "goto" event on the Collection. The LargeView is listening for this event. Its event handler gets the current image from the Photo Collection and modifies the DOM to display the image.

And the winner is?

If the winner was picked here I'd have to say the first example, the one without Backbone.js, would win. Its a simple product with a simple solution. Unfortunately, products rarely seem to be so simple. Lets add a few features:

  • Previous image button which is disabled on the first image
  • Next image button which is disabled on the last image
  • Border around the active thumbnail

Our non-Backbone code grows to about 70 lines (2.3x increase) and starts looking a little unstructured. It doesn't exactly offer up a solid pattern for building other similar UI components.

$(function() {
    var photos = [
        { thumbnail: '0-thumbnail.jpg', large: '0-large.jpg' },
        { thumbnail: '1-thumbnail.jpg', large: '1-large.jpg' },
        { thumbnail: '2-thumbnail.jpg', large: '2-large.jpg' }
    ];

    var thumbnails = $('#thumbnails'),
        large = $('#large'), 
        previous = $('#previous'), 
        next = $('#next'),
        active_index = -1;

    function updateButtons() {
        previous.prop('disabled', active_index == 0);
        next.prop('disabled', active_index == photos.length - 1);
    }

    function updateLarge() {
        $('img', large).attr('src', photos[active_index].large);
    }

    function updateThumbnails() {
        thumbnails.children().each(function(index) {
            var thumbnail = $(this);
            thumbnail.toggleClass(
                'active', 
                thumbnail.data('index') == active_index
            );
        });
    }

    function updateGallery(index) {
        if(index == active_index) return;
        if(index < 0) return;
        if(index >= photos.length) return;

        active_index = index;

        updateButtons();
        updateLarge();
        updateThumbnails();
    }

    // add thumbnails
    var imgs = [];
    $.each(photos, function(index, photo) {
        var thumbnail = $('<img>')
            .prop('src', photo.thumbnail)
            .data(photo)
            .data('index', index);
        imgs.push(thumbnail);
    });
    thumbnails.append(imgs);

    // listen for thumbnail clicks
    thumbnails.on('click', 'img', function() {
        updateGallery($(this).data('index'));
    });

    // listen for previous and next button clicks
    next.click(function() {
        updateGallery(active_index + 1);
    });
    previous.click(function() {
        updateGallery(active_index - 1);
    });

    // add large photo
    large.append($('<img>'));

    // initialize gallery state
    updateGallery(0);
});

See it on jsfiddle

Now to update the Backbone.js version.

$(function() {
    var Photo = Backbone.Model.extend({});

    var IterableCollecton = Backbone.Collection.extend({
        initialize: function() {
            this.index = 0;
        },

        goTo: function(index) {
            if(index == this.index) return;
            if(index < 0) return;
            if(index >= this.length) return;

            this.index = index;
            this.trigger('goto');
        },

        current: function() {
            return this.at(this.index);
        },

        previous: function() {
            this.goTo(this.index - 1);
        },

        next: function() {
            this.goTo(this.index + 1);
        },

        isFirst: function() {
            return this.index == 0;
        },

        isLast: function() {
            return this.index == this.length - 1;
        }
    });

    var Photos = IterableCollecton.extend({
        model: Photo
    });

    var LargeView = Backbone.View.extend({
        el: '#large',

        initialize: function() {
            this.listenTo(this.collection, 'goto', this.change);
        },

        render: function() {
            var photo = this.collection.current();

            var img = $('<img>')
                .prop('src', photo.get('large'));

            this.$el.append(img);

            return this;
        },

        change: function() {
            this.$el.empty();
            this.render();
        }
    });

    var ThumbnailsView = Backbone.View.extend({
        el: '#thumbnails',

        events: {
            'click img': 'click'
        },

        initialize: function() {
            this.listenTo(this.collection, 'goto', this.change);
        },

        render: function() {
            var imgs = [];
            $.each(this.collection.models, function(index, photo) {
                var thumbnail = $('<img>')
                       .prop('src', photo.get('thumbnail'))
                       .data('index', index);
                imgs.push(thumbnail);
            });
            this.$el.append(imgs);

            this.change();

            return this;
        },

        click: function(event) {
            this.collection.goTo($(event.currentTarget).data('index'));
        },

        change: function() {
            var active_index = this.collection.index;

            this.$el.children().each(function(index) {
                var thumbnail = $(this);
                thumbnail.toggleClass(
                    'active', 
                    thumbnail.data('index') == active_index
                );
            });
        }
    });

    var IteratorButtonView = Backbone.View.extend({
        events: {
            'click': 'click'
        },

        initialize: function() {
            this.listenTo(this.collection, 'goto', this.render);
        },

        render: function() {
            this.$el.prop('disabled', this.isDisabled());
            return this;
        },

        isDisabled: function() {
            return false;
        }
    });

    var PreviousView = IteratorButtonView.extend({
        el: '#previous',

        isDisabled: function() {
            return this.collection.isFirst();
        },

        click: function() {
            this.collection.previous();
        }
    });

    var NextView = IteratorButtonView.extend({
        el: '#next',

        isDisabled: function() {
            return this.collection.isLast();
        },

        click: function() {
            this.collection.next();
        }
    });

    var AppView = Backbone.View.extend({
        el: '#gallery',

        initialize: function() {
            this.photos = new Photos();
            this.photos.add([
                { thumbnail: '0-thumbnail.jpg', large: '0-large.jpg' },
                { thumbnail: '1-thumbnail.jpg', large: '1-large.jpg' },
                { thumbnail: '2-thumbnail.jpg', large: '2-large.jpg' }
            ]);

            this.large = new LargeView({
                collection: this.photos
            });

            this.thumbnails = new ThumbnailsView({
                collection: this.photos
            });

            this.previous = new PreviousView({
                collection: this.photos
            });

            this.next = new NextView({
                collection: this.photos
            });
        },

        render: function() {
            this.thumbnails.render();
            this.large.render();
            this.previous.render();
            this.next.render();
        }
    });

    new AppView().render();
}); 

See it on jsfiddle

My Backbone.js version grows to about 190 lines (1.9x increase), but some interesting things are happening.

First, my Photos Collection became a richer Iterator library and there wasn't anything photo-specific in it so I created the IterableCollection class. This generic class could undoubtedly be used in other parts of my application. In our line-of-code comparison we could subtract 35 lines and write this up as a dependency.

Second, I created the IteratorButtonView class which I extend twice. Its very generic and could definitely be used in other projects since all it does is trigger iteration calls on my new IterableCollection type. Subtract another 17 lines.

The Backbone.js version has a lot more function and class declarations which, in JavaScript, comes with significantly more boilerplate code. We're realistically comparing 70 lines to somewhere fairly south of 138 lines (1.3x increase) with the bonus of three new classes that will save you time on future projects.

Backbone.js has other advantages to consider. To name a few:

  • It provides a pattern to follow when building new applications.
  • The code is readable and easy to reason about.
  • Each class and function has a clear purpose. This breaking down of responsibilities promotes creation of reusable code.
  • The code is very testable as event handlers are exposed though the public interface. Event triggering can be easily mocked or ignored entirely which leaves you with simpler synchronous tests to write.

For my taste, I think the Backbone.js version wins given the new requirements. I believe that as the photo gallery's requirements grow the decision to use Backbone.js will become increasingly beneficial as well.

Make the right decision even without Backbone

Removing the Backbone.js dependency from my last example is extremely simple as you can see below. Its important to remember that the dependency was very limited. I was only using Backbone.js for structure and event handling. That said, Backbone.js isn't doing anything earth-shattering and it gets a lot for free by working on top of Underscore.js. More importantly - you are already doing this work somewhere. In fact, you may be doing this work all over your product instead of in just one place.

Building a system like Backbone, that standardizes how you model UI components, should reduce the amount of code, code duplication, and complexity you need to worry about on any individual project.

$(function() {
    function IterableCollection() {
        this.initialize.apply(this, arguments);
    }

    $.extend(IterableCollection.prototype, {
        initialize: function() {
            this.models = [];
            this.index = 0;
        },

        add: function(models) {
            this.models = models;
        },

        at: function(index) {
            return this.models[index];
        },

        on: function() {
            $.fn.on.apply($(this), arguments);            
        },

        trigger: function() {
            $.fn.trigger.apply($(this), arguments);
        },

        goTo: function(index) {
            if(index == this.index) return;
            if(index < 0) return;
            if(index >= this.models.length) return;

            this.index = index;
            this.trigger('goto');
        },

        current: function() {
            return this.at(this.index);
        },

        previous: function() {
            this.goTo(this.index - 1);
        },

        next: function() {
            this.goTo(this.index + 1);
        },

        isFirst: function() {
            return this.index == 0;
        },

        isLast: function() {
            return this.index == this.models.length - 1;
        }
    });

    function LargeView() {
        this.initialize.apply(this, arguments);
    }

    $.extend(LargeView.prototype, {
        $el: $('#large'),

        initialize: function(options) {
            this.collection = options.collection;
            this.collection.on('goto', this.change.bind(this));
        },

        render: function() {
            var photo = this.collection.current();

            var img = $('<img>')
                .prop('src', photo.large);

            this.$el.append(img);

            return this;
        },

        change: function() {
            this.$el.empty();
            this.render();
        }
    });

    function ThumbnailsView() {
        this.initialize.apply(this, arguments);
    }

    $.extend(ThumbnailsView.prototype, {
        $el: $('#thumbnails'),

        initialize: function(options) {
            this.collection = options.collection;
            this.$el.on('click', 'img', this.click.bind(this));
            this.collection.on('goto', this.change.bind(this));
        },

        render: function() {
            var imgs = [];
            $.each(this.collection.models, function(index, photo) {
                var thumbnail = $('<img>')
                    .prop('src', photo.thumbnail)
                    .data('index', index);
                imgs.push(thumbnail);
            });
            this.$el.append(imgs);

            this.change();

            return this;
        },

        click: function(event) {
            this.collection.goTo($(event.currentTarget).data('index'));
        },

        change: function() {
            var active_index = this.collection.index;

            this.$el.children().each(function(index) {
                var thumbnail = $(this);
                thumbnail.toggleClass(
                    'active', 
                    thumbnail.data('index') == active_index
                );
            });
        }
    });

    function IteratorButtonView() {
        this.initialize.apply(this, arguments);
    }

    $.extend(IteratorButtonView.prototype, {
        initialize: function(options) {
            this.collection = options.collection;
            this.$el.bind('click', this.click.bind(this));
            this.collection.on('goto', this.render.bind(this));
        },

        render: function() {
            this.$el.prop('disabled', this.isDisabled());
            return this;
        },

        isDisabled: function() {
            return false;
        }
    });

    function PreviousView() {
        this.initialize.apply(this, arguments);
    }

    $.extend(PreviousView.prototype, IteratorButtonView.prototype, {
        $el: $('#previous'),

        isDisabled: function() {
            return this.collection.isFirst();
        },

        click: function() {
            this.collection.previous();
        }
    });

    function NextView() {
        this.initialize.apply(this, arguments);
    }

    $.extend(NextView.prototype, IteratorButtonView.prototype, {
        $el: $('#next'),

        isDisabled: function() {
            return this.collection.isLast();
        },

        click: function() {
            this.collection.next();
        }
    });

    function AppView() {
        this.initialize.apply(this, arguments);
    }

    $.extend(AppView.prototype, {
        $el: $('#gallery'),

        initialize: function() {
            this.photos = new IterableCollection();
            this.photos.add([
                { thumbnail: '0-thumbnail.jpg', large: '0-large.jpg' },
                { thumbnail: '1-thumbnail.jpg', large: '1-large.jpg' },
                { thumbnail: '2-thumbnail.jpg', large: '2-large.jpg' }
            ]);

            this.large = new LargeView({
                collection: this.photos
            });

            this.thumbnails = new ThumbnailsView({
                collection: this.photos
            });

            this.previous = new PreviousView({
                collection: this.photos
            });

            this.next = new NextView({
                collection: this.photos
            });
        },

        render: function() {
            this.thumbnails.render();
            this.large.render();
            this.previous.render();
            this.next.render();
        }
    });

    new AppView().render();
});

See it on jsfiddle

Template Method Trap

Inheritance is often a trap. Its starts off like an adorable lion cub but grows up to be a man-eater. One common use of inheritance is within the Template Method Pattern. It is the Jekyll and Hyde of patterns; half pattern, half anti-pattern.

What is Template Method?

Let me step back for a moment. What is Template Method? In the example below, a base class (CurrencyConverter) has one or more invariant methods (convert()) which wire together other variant methods (sourceCurrencyCode() and targetCurrencyCode()). These variant methods are available to be implemented in subclasses (like UsdToCadConverter). The variant methods may be abstract or null in the base class. These invariant method stubs form a template for subclasses, hence the name Template Method.

class CurrencyConverter():
    def sourceCurrencyCode(self):
        raise NotImplementedError()

    def targetCurrencyCode(self):
        raise NotImplementedError()

    def convert(self, units):
        return units * get_exchange_rate(self.sourceCurrencyCode(),
            self.targetCurrencyCode())

class UsdToCadConverter(CurrencyConverter):
    def sourceCurrencyCode(self):
        return 'USD'

    def targetCurrencyCode(self):
        return 'CAD'

Template Method implementations usually attempt to accomplish one or more of these goals:

  • Vary configuration values
  • Vary behavior
  • Trigger events using Hooks

There may be other goals but these are the ones I see most in the wild. The example above is varying configuration values.

Behavior-modifying template methods don't just return static value; they perform an action for the invariant method. Below you can see that the crop() method varies in SimpleProfilePictureCreator and FaceFindingProfilePictureCreator.

class ProfilePictureCreator():
    def crop(self, image, width, height):
        raise NotImplementedError()

    def getProfilePicture(self, image, width, height):
        self.crop(image, width, height)
        sharpen(image)
        saturate(image)
        add_sweet_mustache_even_on_ladies_dont_judge_me(image)

def SimpleProfilePictureCreator(ProfilePictureCreator):
    def crop(self, image, width, height):
        crop(image, 0, 0, width, height)

def FaceFindingProfilePictureCreator(ProfilePictureCreator):
    def crop(self, image, width, height):
        center = locate_face(image)
        crop(image, center.x - (width/2), 
            center.y - (height/2), width, height)

Hooks are frequently seen in MVC framework Controller classes. The below should look familiar. Subclasses like MyController can vary the behavior of beforeRender() which is called from render().

class Controller():
    def __init__(self):
        self.view = View()

    def beforeRender(self):
        pass

    def render(self, action):
        self.beforeRender()
        getattr(self, action)()
        result = self.view.render(action + ".html")
        return result

class MyController(Controller):
    def beforeRender(self):
        self.view.set('is_logged_in', Authorization().isLoggedIn())

    def doSomething(self):
        self.view.set('message', 'Hello World!')

The Template Method Pattern may indeed save time in some basic cases, but it comes at an immediate cost, and potentially more growing pains as complexity and variation increase. Lets talk about these weaknesses and possible alternatives.

Injecting configuration

The subclasses in a Template Method Pattern either expose those variant methods in the public interface, when they are implementation details not meant for public consumption, or hide them from the interface making them hard or impossible to test in isolation.

This can easily be taken care of by injecting the configuration. The currency example is dead simple. The two configuration variables are passed into the constructor. No implementation details spill into the public interface.

class CurrencyConverter():
    def __init__(self, source_currency, target_currency):
        self.source_currency = source_currency
        self.target_currency = target_currency

    def convert(self, units):
        return units * get_exchange_rate(self.source_currency,
            self.target_currency)

You could argue that the constructors took no parameters in the earlier currency example so you just instantiate the class and you're ready to go. However, imagine supporting 20 currencies with this approach! This code clearly would not grow well.

Strategy pattern

Testing of the variant methods cannot be done without instantiating the class which, depending on what the base class does, may not be desirable and may be overcomplicated. Ever try testing one controller method and all of a sudden you're loading an entire framework?

Injecting methods to modify behavior or define hooks is a bit different. In languages like Python you can inject functions. In stricter object-oriented languages you might make an interface for cropper's and create multiple implementations of it. This injecting of interchangeable objects or functions is the Strategy Pattern in action. In the example below simple_profile_picture_cropper and face_finding_profile_picture_cropper are both strategies.

I created a Factory to handle wiring together the pieces, but, were there more pieces, one could use a Builder Pattern.

class ProfilePictureCreator():
    def __init__(self, cropper):
        self.cropper = cropper

    def getProfilePicture(self, image, width, height):
        self.cropper(image, width, height)
        sharpen(image)
        saturate(image)

def simple_profile_picture_cropper(image, width, height):
    crop(image, 0, 0, width, height)

def face_finding_profile_picture_cropper(image, width, height):
    center = locate_face(image)
    crop(image, center.x - (width/2), 
        center.y - (height/2), width, height)

class Factory():
    def createSimpleCreator(self):
        return ProfilePictureCreator(simple_profile_picture_cropper)

    def createFaceFindingCreator(self):
        return ProfilePictureCreator(face_finding_profile_picture_cropper)

In the first profile picture example the constructor had no parameters so you could easily instantiate the profile picture creator of your choosing and be off to the races. In this one you need to call a factory method before you start to work. The first example requires slightly less typing to use the code. The second example is more testable, more flexible in its creation (consider if more options exist, or will exist), and hides its implementation details. So it's a choice, and the choice is yours.

Common variant methods

With a Template Method implementation you may need to share the behavior of some of the variant methods in multiple subclasses. The typical way to handle this problem is to create a new class with those common variant methods and subclass it. Below is an example illustrating this approach.

class CurrencyConverter():
    def sourceCurrencyCode(self):
        raise NotImplementedError()

    def targetCurrencyCode(self):
        raise NotImplementedError()

    def convert(self, units):
        return units * get_exchange_rate(self.sourceCurrencyCode(),
            self.targetCurrencyCode())

class UsdConverter(CurrencyConverter):
    def sourceCurrencyCode(self):
        return 'USD'

class UsdToCadConverter(UsdConverter):
    def targetCurrencyCode(self):
        return 'CAD'

class UsdToGbpConverter(UsdConverter):
    def targetCurrencyCode(self):
        return 'GBP'

Notice the UsdConverter class isn't to be used alone. Its just a means for UsdTo* classes to get their sourceCurrencyCode() methods for free. So whats the problem? Well, there are a few...

  • Depending on the size of your system there may unnecessarily be lots of common-method classes and they be multiple levels of inheritance deep.
  • Naming common-method classes also becomes difficult since the grouping of methods might not have a logical relationship or single purpose. One consequence of this problem is that your outermost subclasses inherit from common-method classes that give little indication of what they do.
  • At some point a change in the common-method class may be undesirable in some of its subclasses since they may lack that true is-a relationship.

If you don't couple your configuration options, you don't really have this problem. You can vary your more complicated situations using Strategies, Decorators, Chains Of Responsibility - all kinds of patterns depending on what you are trying to build. You can, if needed, wire them all together with Builders. If you find you're repeating a lot of your construction process by calling the same set of methods on your Builder, then perhaps you can combine those calls into a new method on your Builder, or create a Factory which pre-configures your Builder with a set of method calls.

This approach makes you create some really powerful and flexible Builders. Your construction process reads top-down like a recipe rather than a shuffled stack of common-method classes.

My advice is to always consider inheritance as a last option when that is-a relationship is not present. This is much easier if you have a firm grasp on the other patterns which do not rely on inheritance.

« Page 2 / 9 »