4.3 Structural Design Patterns

Introduction

Whilst Creational patterns allow for high degrees of flexibility in the instantiation of objects, Structural patterns provide flexibility with the composition of objects to form larger, complex objects. This is achieved through the use of inheritance and composition. Structural patterns describe ways to compose objects to allow for new functionality at run-time, which is impossible with static class construction.

As with the Creational patterns, we will consider only a subset of the Structural patterns. There are many more Structural patterns to learn, and it is recommended that you review the remaining patterns.


Adaptor

Purpose

An adaptor is used to convert the interface of classes into an expected, standardized interface. The Adaptor pattern allows compatibility between objects and client code.

Description

Sometimes we have the situation where a level of abstraction is required between client code and a number of different services, technologies or other classes. For example, if we want to create a Message class, and abstract away from the various technologies with which we can create or receive a message (ie: email, instant messenger, SMS, Twitter, Facebook, etc.), we could use the Abstract pattern to accomplish this. We might have classes for the API's of each of the various technologies with which to send and receive messages, and apply the adaptor to enable the client code to use a single, standardized interface for sending and receiving messages, irrespective of the technology used.

Solution

The Adaptor design pattern has the following components:

  • Target: The abstracted interface used by the Client.
  • Client: The client code making use of the Adaptee's functionality via the Adaptor.
  • Adaptee: The class being adapted by the Adaptor for use by the Client through the Target.
  • Adaptor: The class which provides the standardized interface to the functionality of the Adaptee.

The Client makes a request of the Target, who, in turn, makes a request of the Adaptor, which finally makes a specific request of the Adaptee which is being standardized by the Adaptor.

Adaptor_Diagram

Python Implementation

To learn how to apply the Adaptor pattern in Python, let us have a look at the above Messenger example implemented in Python code.

from abc import ABCMeta, abstractmethod

class Message:
    __metaclass__ = ABCMeta

    @staticmethod
    def Factory():
        return MessageAdaptor()

    @abstractmethod
    def send(self, method, to, message):
        pass

    @abstractmethod
    def receive(self, method):
        pass

class MessageAdaptor(Message):

    def send(self, method, to, message):
        if method == "email":
            adaptee = Email()
            adaptee.send_email(to, message)
        elif method == "facebook":
            adaptee = Facebook()
            adaptee.send_fb_message(to, message)
        else:
            raise Exception("Invalid method.")

    def receive(self, method):
        if method == "email":
            adaptee = Email()
            return adaptee.receive_email()
        elif method == "facebook":
            adaptee = Facebook()
            return adaptee.receive_fb_message()
        else:
            raise Exception("Invalid method.")

class Email:

    def send_email(self, to, message):
        print "Sending an email to %s" % (to)
        # Code to send an email goes here
        pass

    def receive_email(self):
        print "Receiving an email"
        # Code to receive an email
        pass

class Facebook:

    def send_fb_message(self, to, message):
        print "Sending a Facebook message to %s" % (to)
        # Code to send a Facebook message
        pass

    def receive_fb_message(self):
        print "Receiving a Facebook message"
        # Code to receive a Facebook message
        pass

if __name__ == '__main__':
    messager = Message.Factory()
    messager.send("email", "someone@domain.com", "Hi. How are you today?")
    messager.receive("facebook")

In the above example, the client code at the bottom (if __name__ == '__main__':) calls the Target, Message() which makes use of the Adaptor, MessageAdaptor() to provide a standardized interface for sending and receiving messages independently of the specific technology or protocol used.


Decorator

Purpose

Automatically extend functionality as an alternative to inheritance. The decorator intercepts messages between two objects and provides the extended functionality seamlessly.

Description

Let's imagine that we have a class which handles comments for a website. Rather than worrying about converting the comment into HTML within the Comment() class, we might want to apply a Decorator to handle the formatting automatically. Thus, as the Comment() object returns the comment to the client code, the Decorator would intercept the message, and apply the HTML formatting.

Solution

The Decorator design pattern is comprised of the following elements:

  • Component: Defines the abstract common interface for objects to be extended by Decorators.
  • Concrete Component: This is the class for objects which can be extended by Decorators, inheriting from the Component class.
  • Decorator: Abstract class which contains a reference to the Component class.
  • Concrete Decorator: Contains the code to extend the functionality of the Concrete Component.

Decorator_Diagram

Note: If there is only a need for a single piece of functionality to be extended, there is no need to make use of the abstract Decorator class and just define the Concrete Decorator.

Python Implementation

In Python, we have a specific syntax for Decorators. You will have seen in earlier sections something like

@staticmethod

Which is an example of a Decorator which converts the class method into a static method. Note the "@" prefix. Let's look at an example of using a Decorator in Python.

def htmlify(Component):
    def ConcreteDecorator(*obj, **kwargs):
        html = obj[0].comment
        html = html.replace("\n", "")
        html = html.replace("\t", "    ")
        html = html.replace(" ", " ")
        return html
    return ConcreteDecorator

class Comment:

    comment = ""

    def set_comment(self, comment):
        self.comment = comment

    @htmlify
    def get_comment(self):
        return self.comment

if __name__ == "__main__":
    my_comment = Comment()
    my_comment.set_comment("Hi,\nThis is my comment.")
    print my_comment.get_comment()

Here, we define the decorator first, htmlify, and then we prefix the get_comment() method in the Comment() class with the decorator syntax:

@htmlify

This causes the instance of the function at run time to be passed to the decorator, which then modifies the comment variable, and returns its value.


Facade

Purpose

Provide a single, unified interface for a collection of classes or a sub-system. The Facade design pattern simplifies usage of the sub-system through a single, simplified interface in a single Facade class.

Description

It is a common development practice to develop systems in the form of sub-systems or modules. An important goal when developing with sub-systems is to minimize the inter-module communication. By using the Facade design pattern, we achieve this purpose by having a single point for communication.

Solution

The Facade design pattern has the following components:

  • Facade: Delegates the requests made by the Client code to the appropriate Subsystem Classes.
  • Subsystem Classes: Collection of classes which implement the functionality of the subsystem.
  • Client: The client code utilizing Subsystem functionality via the Facade.

Facade_Diagram

Python Implementation

To demonstrate the Facade design pattern in Python, let's consider an example where we have a submodule which provides the Statistics functionality. We will then define a StatisticsFacade class to simplify interaction between the client code and the Statistics module.

import math

class Stats_NormalDistribution:

    mean = 0
    standard_deviation = 0

    def __init__(self, mean, sd):
        self.mean = mean
        self.standard_deviation = sd

    def graph(self):
        # Code to draw graph
        pass

class Stats_StraightLine:

    gradient = 0
    intersection = 0

    def __init__(self, m, c):
        self.gradient = m
        self.intersection = c

    def graph(self):
        # Code to draw graph
        pass

class Stats_Calculations:

    def average(self, x_arr):
        total = 0
        for i in x_arr:
            total = total + i
        average = total / len(x_arr)
        return average

    def standard_deviation(self, x_arr):
        average = self.average(x_arr)
        sum = 0
        for i in x_arr:
            sum = sum + ((i - average) ** 2)
        sqr_avg = sum / len(x_arr)
        sd = math.sqrt(sqr_avg)
        return sd

class StatisticsFacade:

    normal = 0
    straight_line = 0
    calculations = 0

    def __init__(self):
        self.normal = Stats_NormalDistribution(0, 0)
        self.straight_line = Stats_StraightLine(0, 0)
        self.calculations = Stats_Calculations()

    def average(self, x_arr):
        return self.calculations.average(x_arr)

    def standard_deviation(self, x_arr):
        return self.calculations.standard_deviation(x_arr)

    def normal_graph(self, avg, mean):
        self.normal.average = avg
        self.normal.mean = mean
        return self.normal.graph()

    def line_graph(self, gradient, intersection):
        self.straight_line.gradient = gradient
        self.straight_line.intersection = intersection
        return self.straight_line.graph()

if __name__ == "__main__":
    stats = StatisticsFacade()
    x = [1, 4, 6, 12, 13, 4, 19, 19, 20, 21, 22, 23, 31, 40, 45]
    avg = stats.average(x)
    sd = stats.standard_deviation(x)
    print x
    print "Average = %d, Standard Deviation = %d" % (avg, sd)
    normal_graph = stats.normal_graph(avg, sd)
    gradient = 2
    intersection = 1
    line_graph = stats.line_graph(gradient, intersection)

Exercises

Give three scenarios where it would be appropriate to utilize the following design patterns:

  1. Adaptor
  2. Decorator
  3. Facade

Continue to next section


Comments

comments powered by Disqus