Chapter 9

Design Patterns

Building more beautiful and repeatable software!

Subsections of Design Patterns

Introduction

Up to this point, we’ve mainly been developing our programs without any underlying patterns between them. Each program is custom-written to fit the particular situation or use case, and beyond using a few standard data structures, the code and structure within each program is mostly unique to that application. In this chapter, we’re going to learn about software design patterns, a very powerful aspect of object-oriented programming and a way that we can write code that is more easily recognized and understood by other developers. By using these patterns, we can see that many unrelated programs can actually share similar code structures and ideas.

Some of the key terms we’ll cover in this chapter:

  • Software Design Patterns
  • The Gang of Four
  • Creational Patterns
    • Builder Pattern
    • Factory Method Pattern
    • Singleton Pattern
  • Structural Patterns
    • Adapter Pattern
  • Behavioral Patterns
    • Iterator Pattern
    • Template Method Pattern

After reviewing this chapter, we should be able to recognize and use several of the most common design patterns in our code.

The Gang of Four

YouTube Video

Video Materials

Design Patterns Cover Design Patterns Cover1

While the first discussions of patterns in software architecture occurred much earlier, the concept was popularized in 1994 with the publication of Design Patterns: Elements of Reusable Object-Oriented Software by Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides. Collectively, the four authors of this book have been referred to as the “Gang of Four” or “GoF” within the software development community, so it is common to see references to that name when discussing the particular software design patterns discussed in the book.

In their book, the authors give their thoughts on how to build software following the object-oriented programming paradigm. This includes focusing on the use of interfaces to design how classes should appear to function to an outside observer, while leaving the actual implementation details hidden within the class. Likewise, they favor the use of object composition over inheritance - instead of inheriting functionality from another class, simply store an internal instance of that class and use the public methods it contains.

The entire first chapter of the book is a really great look at one way to view object-oriented programming, and many of the items discussed by the authors have been implemented by software developers as standard practice. In fact, it is still one of the best selling books on software architecture and design, even decades after its release!

Of course, it isn’t without criticism. One major complaint of this particular book is that it was developed to address several things that cannot be easily done in C++, which have been better handled in newer programming languages. In addition, the reliance on reusable software design patterns may feel a bit like making the problem fit the solution instead of building a new solution to fit the problem.

References

Subsections of The Gang of Four

Software Design Patterns

The most important part of the book by the “Gang of Four,” as evidenced by the title, are the 23 software design patterns that are discussed within the book.

A software design pattern is a reusable structure or solution to a common problem or usage within software development. Throughout the course of developing many different programs in the object-oriented paradigm, developers may find that they are reusing similar ideas or code structures within applications with completely different uses, which leads to the idea of formalizing these ideas into reusable structures.

However, it is important to understand that a design pattern is not a finished piece of code that can simply be dropped into a program. Instead, a design pattern is a framework, structure, or template that can be used to achieve a particular result or solve a given problem. It is still up to the developer to actually determine if the design pattern can be used, and how to make it work.

The power of these design patterns lies in their common structure and the familiarity that other developers have with the pattern. For example, when building a program that requires a single global instance of class, there are many ways to do it. One way is the singleton pattern, which we’ll explore later in this chapter. If we choose to use that pattern, we can then tell other developers “this uses the singleton pattern” and, hopefully, they’ll be able to understand how it works as long as they are familiar with the pattern. If they aren’t, then the usefulness of the pattern is greatly reduced. So, it is very helpful for developers to be familiar with commonly-used design patterns, while constantly being on the lookout for new and interesting patterns to learn about and add to their ever growing list of patterns that can be used.

A great analogy is poetry. If we write a simple poem containing 5 lines, where the first, second, and fifth all end in a rhyming word and have the same number of syllables, and the third and fourth also rhyme and have fewer syllables, it could be very difficult to explain that structure to another writer. However, if we just say “I’ve written a limerick” to another writer, that writer might instantly understand what we mean, just based on their own familiarity with the format. However, if the writer is not familiar with a limerick, then referencing that pattern might not be helpful at all.

Software Design Pattern Categories

In Design Patterns, the “Gang of Four” introduced 23 patterns, which were grouped into three categories:

  • Creational Patterns - these patterns are used to create instances objects, typically by doing so in a programmatic way instead of directly instantiating the object.
    • Abstract Factory Pattern
    • Builder Pattern
    • Factory Method Pattern
    • Prototype Pattern
    • Singleton Pattern
  • Structural Patterns - these patterns are mainly used to structure individual classes or groups of classes using inheritance, interfaces, and composition.
    • Adapter Pattern
    • Bridge Pattern
    • Composite Pattern
    • Decorator Pattern
    • Facade Pattern
    • Flyweight Pattern
    • Proxy Pattern
  • Behavioral Patterns - these patterns determine how objects act and interact, mainly by communicating between objects using message passing.
    • Chain of Responsibility Pattern
    • Command Pattern
    • Interpreter Pattern
    • Iterator Pattern
    • Mediator Pattern
    • Memento Pattern
    • Observer Pattern
    • State Pattern
    • Strategy Pattern
    • Template Method Pattern
    • Visitor Pattern

In addition, many modern references also include a fourth category: Concurrency Patterns, which are specifically related to building programs that run on multiple threads, multiple processes, or even across multiple systems within a supercomputer. We won’t deal with those patterns in this course since they are greatly outside the scope of what we’re going to cover.

Instead, we’re going to primarily focus on three creational patterns: the builder pattern, the factory method pattern, and the singleton pattern. Each one of these is commonly used in many object-oriented programs today, and we’ll be able to make use of each of them in our ongoing course project.

We’ll also look at a few of the structural and behavioral patterns: the iterator pattern, the template method pattern, and the adapter pattern.

Builder Pattern

YouTube Video

Video Materials

The first pattern we’ll look at is the builder pattern. The builder pattern is used to simplify building complex objects, where the class that needs the object shouldn’t have to include all of the code and details for how to construct that object. By decoupling the code for constructing the complex object from the classes that use it, it becomes much simpler to change the representations or usages of the complex object without changing the classes that use it, provided they all adhere to the same general API.

Builder Pattern UML Builder Pattern UML1

The UML diagram above gives one possible structure for the builder pattern. It includes a Builder interface that other objects can reference, and Builder1 is a class that implements that interface. There could be multiple builders, one for each type of object. The Builder1 class contains all of the code needed to properly construct the ComplexObject class, consisting of ProductA1 and ProductB1. If a different ComplexObject must be created, we can create another class Builder2 that also implements the Builder interface. To the Director class, both Builder1 and Builder2 implement the same interface, so they can be treated as the same type of object.

Example: Deck of Cards

A great example of this would be creating a deck of cards for various card games. There are actually many different types of card decks, depending on the game that is being played:

  • Standard 52 Cards: 2-10, J, Q, K, A in four Suits
  • Standard 52 Cards with Jokers: add one or two Jokers to a Standard 52 Card Deck
  • Pinochle Deck: 9, J, Q, K, 10, A in four suits, two of each; 48 cards total
  • Old Maid: Remove any 1 card from a Standard 52 Card Deck
  • Uno: One 0 and Two each of 1-9, Skip, Draw Two and Reverse in four colors, plus four Wild and four Wild Draw Four; 108 cards total
  • Rook: 1-14 in four colors, plus a Rook (Joker); 57 cards total

As we can see, even though each individual card is similar, constructing a deck for each of these games might be quite the complex process.

Instead, we can use the builder pattern. Let’s look a how this could work.

The Card Class

First, we’ll assume that we have a very simple Card class, consisting of three attributes:

  • SuitOrColor - the suit or color of the card. We’ll use a special color for cards that aren’t associate with a group of other cards
  • NumberOrName - the number or name of the card
  • Rank - the sorting rank of the card (lowest = 1).
public class Card{
    String suitOrColor;
    String numberOrName;
    int rank;
    
    public Card(String suit, String number, int rank) {
        this.suitOrColor = suit;
        this.numberOrName = number;
        this.rank = rank;
    }
}
class Card:
    def __init__(self, suit: str, number: str, rank: int) -> None:
        self._suit_or_color: str = suit
        self._number_or_name: str = number
        self._rank: int = rank

The Deck Class

The Deck class will only consist of an aggregation, or list, of the cards contained in the deck. So, our builder class will return an instance of the Deck object, which contains all of the cards in the deck.

The Deck class could also include generic methods to shuffle, draw, discard, and deal cards. These would work with just about any of the games listed above, regardless of the details of the deck itself.

import java.util.LinkedList;
import java.util.List;

public class Deck{
    List<Card> deck;
    
    public Deck() {
        deck = new LinkedList<>();
    }
    
    void shuffle();
    Card draw();
    void discard(Card card);
    List<List<Card>> deal(int hands, int size);
}
from typing import List


class Deck:
    def __init__(self) -> None:
        self._deck: List[Card] = list()
    
    def shuffle(self) -> None:
    def draw(self) -> Card:
    def discard(self, card: Card) -> None:
    def deal(self, hands: int, size: int) -> List[List[Card]]:

The Builder Interface

Our DeckBuilder interface will be very simple, consisting of a single method: buildDeck(). The type of the class that implements the DeckBuilder interface will determine which type of deck is created. If the decks created by the builder have additional options, we can add additional methods to our DeckBuilder interface to handle those situations.

The Builder Classes

Finally, we can create our builder classes themselves. These classes will handle actually building the different decks required for each game. First, let’s build a standard 52 card deck.

public class Standard52Builder implements DeckBuilder {
    String[] suits = {"Spades", "Hearts", "Diamonds", "Clubs"};

    public Deck buildDeck() {
        Deck deck = new Deck();
        for (String suit : suits) {
            for (int i = 2; i <= 14; i++) {
                if (i == 11) {
                    deck.add(new Card(suit, "Jack", i));
                } else if (i == 12) {
                    deck.add(new Card(suit, "Queen", i));
                } else if (i == 13) {
                    deck.add(new Card(suit, "King", i));
                }else if (i == 14) {
                    deck.add(new Card(suit, "Ace", i));
                } else {
                    deck.add(new Card(suit, "" + i, i));
                }
            }
        }
        return deck;
    }
}
from typing import List


class Standard52Builder(DeckBuilder):
    suits: List[str] = ["Spades", "Hearts", "Diamonds", "Clubs"]
    
    def build_deck(self):
        deck: Deck = Deck()
        for suit in suits:
            for i in range(2, 15):
                if i == 11:
                    deck.append(Card(suit, "Jack", i))
                elif i == 12:
                    deck.append(Card(suit, "Queen", i))
                elif i == 13:
                    deck.append(Card(suit, "King", i))
                elif i == 14:
                    deck.append(Card(suit, "Ace", i))
                else:
                    deck.append(Card(suit, str(i), i))
        return deck

As we can see, the heavy lifting of actually building the deck is offloaded to the builder class. We can easily use this same framework to create additional Builder classes for the other types of decks listed above.

Using the Builder

Finally, once we’ve created all of the builders that we’ll need, we can use them directly in our code anywhere we need them:

public class CardGame{

    public static void main(String[] args) {
        DeckBuilder builder = new Standard52Builder();
        Deck cards = builder.buildDeck();
        // game code goes here
    }

}
from typing import List


class CardGame:

    @staticmethod
    def main(args: List[str]) -> None:
        builder: DeckBuilder = Standard52Builder()
        cards: Deck = builder.build_deck()
        # game code goes here

From here, if we want to use any other decks of cards, all we have to do is switch out the single line for the type of builder we instantiate, and we’ll be good to go! This is the powerful aspect of the builder pattern - we can move all of the complex code for creating objects to a builder class, and then any class that uses it can quickly and easily construct the objects it needs in order to function.

On the next page, we’ll see how we can expand this pattern by including the factory pattern to help simplify things even further.

Subsections of Builder Pattern

Factory Method Pattern

The next pattern we’ll explore is the factory method pattern. The factory method pattern is used to allow us to construct an object of a desired type without actually having to specify that type explicitly in our code. Instead, we just provide the factory with an input specifying the type of object we need, and it will return an instance of that type. By making use of the factory method pattern, classes that require access to these object don’t need to be updated any time an underlying object type is modified. Instead, they can simply reference the parent or interface data types, and the factory handles creating and returning objects of the correct type whenever needed.

factory method pattern UML factory method pattern UML1

As we can see in the UML diagram for this pattern, it looks very similar to the builder pattern we saw previously. There is a Creator interface, which defines the interface that each factory uses. Then, the concrete Creator1 class is actually used to create the class required.

Let’s continue our deck of cards example from the previous page to include the factory method pattern.

Decks Enum

To simplify this process, we’ll create a quick enumeration of the possible decks available in our system. This makes it easy to expand later and include more decks of cards.

public enum DeckType {
    STANDARD52("Standard 52"),
    STANDARD52ONEJOKER("Standard 52 with One Joker"),
    STANDARD52TWOJOKER("Standard 52 with Two Jokers"),
    PINOCHLE("Pinochle"),
    OLDMAID("Old Maid"),
    UNO("Uno"),
    ROOK("Rook");
}
from enum import Enum


class DeckType(str, Enum):
    STANDARD52 == "Standard 52"
    STANDARD52ONEJOKER == "Standard 52 with One Joker"
    STANDARD52TWOJOKER == "Standard 52 with Two Jokers"
    PINOCHLE == "Pinochle"
    OLDMAID == "Old Maid"
    UNO == "Uno"
    ROOK == "Rook"

Factory Class

Next, we’ll define a simple factory class, which is able to build each type of card deck. We’ll leave out the parent interface for now, since this project will only ever have a single factory object available.

import java.lang.IllegalArgumentException;

public class DeckFactory{

    public Deck getDeck(DeckType deck) {
        if(deck == DeckType.STANDARD52){
            return new Standard52Builder().buildDeck();
        }else if(deck == DeckType.STANDARD52ONEJOKER){
            return new Standard52OneJokerBuilder().buildDeck();
        }else if(deck == DeckType.STANDARD52TWOJOKER){
            return new Standard52TwoJokerBuilder().buildDeck();
        }else if(deck == DeckType.PINOCHLE){
            return new PinochleBuilder().buildDeck();
        }else if(deck == DeckType.OLDMAID){
            return new OldMaidBuilder().buildDeck();
        }else if(deck == DeckType.UNO){
            return new UnoBuilder().buildDeck();
        }else if(deck == DeckType.ROOK){
            return new RookBuilder().buildDeck();
        }else {
            throw new IllegalArgumentException("Unsupported DeckType");
        }
    }
}
class DeckFactory:

    def get_deck(self, deck: DeckType) -> Deck:
        if deck == DeckType.STANDARD52:
            return Standard52Builder().buildDeck()
        elif deck == DeckType.STANDARD52ONEJOKER:
            return Standard52OneJokerBuilder().buildDeck()
        elif deck == DeckType.STANDARD52TWOJOKER:
            return Standard52TwoJokerBuilder().buildDeck()
        elif deck == DeckType.PINOCHLE:
            return Standard52Builder().buildDeck()
        elif deck == DeckType.OLDMAID:
            return OldMaidBuilder().buildDeck()
        elif deck == DeckType.UNO:
            return UnoBuilder().buildDeck()
        elif deck == DeckType.ROOK:
            return RookBuilder().buildDeck()
        else:
            raise ValueError("Unsupported DeckType");

Using the Factory

Now that we’ve created our factory class, we can update our main method to use it instead. In this case, we’ll get the type of deck to be used directly from the user as input:

public class CardGame{

    public static void main(String[] args) {
        // ask user for input and store in `deckType`
        String deckType = "Standard 52";
        Deck cards = DeckFactory().getDeck((DeckType.valueOf(deckType)));
        // game code goes here
    }
}
from typing import List


class CardGame:

    @staticmethod
    def main(args: List[str]) -> None:
        # ask user for input and store in `deck_type`
        deck_type: str = "Standard 52"
        cards: Deck = DeckFactory().get_deck(DeckType(deck_type))
        # game code goes here

This code is actually doing quite a bit in only two lines, so let’s go through it step by step. First, we’re assuming that we are getting user input to determine which deck should be used. This could be done via a GUI, the terminal, or some other means. We’re storing that input in a string, just to demonstrate the power of the factory method pattern. As long as the string matches one of the available deck types in the DeckType enum, it will work. Of course, this may be difficult to do, so our input code might need to verify that the user inputs a valid option.

However, if we have a valid option, we can convert it to the correct enum value, and then pass that as an argument to the getDeck() method of our DeckFactory class. The factory will look at the parameter, construct the correct deck using the appropriate builder class, and then return it back to our application. Pretty handy!

Practical Example: Database Connections

One of the most common places the factory method pattern appears is in the construction of database connections. In theory, we’d like any of our applications to be able to use different types of databases, so many database connection libraries use the factory method pattern to create a database connection. Here’s what that might look like - this code will not actually work, but is representative of what it looks like in practice:

public class DbTest{

    public static void main(String[] args) {
        // connect to Postgres
        DbConnection conn = DbFactory.get("postgres");
        conn.connect("username", "password", "database");
        
        // connect to MySql
        DbConnection conn2 = DbFactory.get("mysql");
        conn2.connect("username", "password", "database");
        
        // connect to Microsoft SQL Server
        DbConnection conn3 = DbFactory.get("mssql");
        conn3.connect("username", "password", "database");
    }
}
class DbTest:

    @staticmethod
    def main(args: List[str]) -> None:
        # connect to Postgres
        conn: DbConnection = DbFactory.get("postgres")
        conn.connect("username", "password", "database")
        
        # connect to MySql
        conn2: DbConnection = DbFactory.get("mysql")
        conn2.connect("username", "password", "database")
        
        # connect to Microsoft SQL Server
        conn3: DbConnection = DbFactory.get("mssql")
        conn3.connect("username", "password", "database")

In each of these examples, we can get the database connection object we need to interface with each type of database by simply providing a string that specifies which type of database we plan to connect to. This makes it quick and easy to switch database types on the fly, and as a developer we don’t have to know any of the underlying details for actually connecting to and interfacing with the database. Overall, this is a great use of the factory method pattern in practice today.

Singleton Pattern

Finally, let’s look at one other common creational pattern: the singleton pattern. The singleton pattern is a simple pattern that allows a program to enforce the limitation that there is only a single instance of a class in use within the entire program. So, when another class needs an instance of this class, instead of instantiating a new one, it will simply get a reference to the single existing object. This allows the entire program to share a single instance of an object, and that instance can be used to coordinate actions across the entire system.

Singleton UML Singleton UML1

The UML diagram for the singleton pattern is super simple. The class implementing the singleton pattern simply defines a private constructor, making sure that no other class can construct it. Instead, it stores a static reference to a single instance of itself, and includes a get method to access that single instance.

Let’s look at how this could work in our ongoing example.

Singleton Factory

Let’s update our DeckFactory class to use the singleton pattern.

public class DeckFactory{
    // private static single reference
    private static DeckFactory instance = null;
    
    // private constructor
    private DeckFactory(){
        // do nothing
    }
    
    public static DeckFactory getInstance() {
        // only instantiate if it is called at least once
        if DeckFactory.instance == null{
            DeckFactory.instance = new DeckFactory();
        }
        return DeckFactory.instance;
    }

    public Deck getDeck(DeckType deck) {
        // existing code omitted
    }
}

There are actually two different ways to implement this in Python. The first is closer to the implementation seen in Java above and in C++ in the original book.

class DeckFactory:

    # private static single reference
    _instance: DeckFactory = None
    
    # constructor that cannot be called
    def __init__(self) -> None:
        raise RuntimeError("Cannot Construct New Object!")
        
    @classmethod
    def get_instance(cls) -> DeckFactory:
        # only instantiate if it is called at least once
        if cls._instance is None:
            # call `__new__()` directly to bypass __init__
            cls._instance = cls.__new__(cls)
        return cls._instance

    def get_deck(self, deck: DeckType) -> Deck:
        # existing code omitted

A more Pythonic way would be to simply make use of the __new__() method itself to create the singleton and return it anytime the __init__() method is called. In Python, when any class is constructed normally, as in DeckFactory(), the __new__() method is called on the class first to create the instance, and then the __init__() method is called to set the instance’s attributes and perform any other initialization. So, by ensuring that the __new__() method consistently returns the same instance, we can guarantee that only a single instance exists.

class DeckFactory:

    # private static single reference
    _instance: DeckFactory = None

    # new method to construct the instance
    def __new__(cls) -> DeckFactory:
        if cls._instance is None:
            # call `__new__()` on the parent `Object` class
            cls._instance = super().__new__(cls)
        return cls._instance

    def get_deck(self, deck: DeckType) -> Deck:
        # existing code omitted

In this way, any calls to construct a DeckInstance() in the traditional way would just return the same object. Very Pythonic!

See Singleton on the excellent Python Design Patterns website for a discussion of these two implementations.

Using a Singleton

Now we can update our main method code to use our singleton DeckFactory instance instead of creating one when it is needed:

public class CardGame{

    public static void main(String[] args) {
        // ask user for input and store in `deckType`
        String deckType = "Standard 52";
        Deck cards = DeckFactory.getInstance().getDeck((DeckType.valueOf(deckType)));
        // game code goes here
    }
}
from typing import List


class CardGame:

    @staticmethod
    def main(args: List[str]) -> None:
        # ask user for input and store in `deck_type`
        deck_type: str = "Standard 52"
        cards: Deck = DeckFactory.get_instance().get_deck(DeckType(deck_type))
        # Python method described above means the code doesn't change!
        # cards: Deck = DeckFactory().get_deck(DeckType(deck_type))
        # game code goes here

Why would we want to do this? Let’s assume we’re writing software for a multiplayer game server. In that case, we may not want to instantiate a new copy of the DeckFactory class for each player. Instead, using the singleton pattern, we can guarantee that only one instance of the class exists in the entire system.

Likewise, if we need a system to assign unique numbers to objects, such as orders in a restaurant, we can create a singleton class that assigns those numbers across all of the point of sale systems in the entire store. This might be useful in your ongoing class project.

Iterator Pattern

YouTube Video

Video Materials

Let’s review three other commonly used software design patterns. These are either patterns that we’ve seen before, or ones that we might end up using soon in our code.

Iterator Pattern

The first pattern is the iterator pattern. The iterator pattern is a behavioral pattern that is used to traverse through a collection of objects stored in a container. We explored this pattern in several of the data structures introduced in earlier data structures courses such as CC 310 and CC 315, as well as CIS 300.

Iterator Pattern Diagram Iterator Pattern Diagram1

In it’s simplest form, the iterator pattern simply includes a hasNext() and next() method, though many implementations may also include a way to reset the iterator back to the beginning of the collection.

Classes that use the iterator can use the hasNext() method to determine if there are additional elements in the collection, and then the next() method is used to actually access that element.

In the examples below, we’ll rely on the built-in collection classes in Java and Python to provide their own iterators, but if we must write our own collection class that doesn’t use the built-in ones, we can easily develop our own iterators using documentation found online.

In Java, classes can implement the Iterable interface, which requires them to return an Iterator object. In doing so, these objects can then be used in the Java enhanced for or for each loop.

import java.lang.Iterable;
import java.util.Iterator;
import java.util.List;
import java.util.LinkedList;

public class Deck implements Iterable<Card> {

    List<Card> deck;
    
    public Deck() {
        deck = new LinkedList<>();
    }
    
    @Override
    public Iterator<Card> iterator() {
        return deck.iterator();
    }
    
    public int size() {
        return this.deck.size();
    }
}

Here, we are making use of the fact that the Java collections classes, such as LinkedList, already implement the Iterable interface, so we can just return the iterator from the collection contained in our object. Even though it is not explicitly required by the Iterable interface, it is also a good idea to implement a size() method to return the size of our collection.

With this code in place, we can iterate through the deck just like any other collection:

public class CardGame{

    public static void main(String[] args) {
        String deckType = "Standard 52";
        Deck cards = DeckFactory.getInstance().getDeck((DeckType.valueOf(deckType)));
        
        for(Card card : cards) {
            // do something with each card
        }
    }
}

In Python, we can simply provide implementation for the __iter__() method in a class to return an iterator object, and that iterator object should implement the __next__() method to get the next item, as well as the __iter__() method, which just returns the iterator itself. Python does not define an equivalent to the has_next() method; instead, the __next__() method should raise a StopIteration exception when the end of the collection is reached.

For the purposes of type checking, we can use the Iterator type and the Iterable parent class (which works similar to an interface).

from typing import Iterable, Iterator


class Deck(Iterable[Card]):

    def __init__(self) -> None:
        self._deck: List[Card] = list()
    
    def __iter__(self) -> Iterator[Card]:
        return iter(self._deck)
        
    def __len__(self) -> int:
        return len(self._deck)
        
    def __getitem__(self, position: int) -> Card:
        return self._deck[position]

Here, we are making use of the fact that the built-in Python data types, such as list and dictionary, already implement the __iter__() method, so we can just return the iterator obtained by calling iter() on the collection.

In addition, we’ve also implemented the __len__() and __getitem__() magic methods, or “dunder methods”, that help our class act more like a container. With these, we can use len(cards) to get the number of cards in a Deck instance, and likewise we can access each individual card using array notation, as in cards[0]. There are several other magic methods we may wish to implement, which are described in the link above.

With this code in place, we can iterate through the deck just like any other collection:

from typing import List


class CardGame:

    @staticmethod
    def main(args: List[str]) -> None:
        deck_type: str = "Standard 52"
        cards: Deck = DeckFactory.get_instance().get_deck(DeckType(deck_type))
            
        for card in cards:
            # do something with each card
Reference

See Iterator on Python Design Patterns for more details.

Subsections of Iterator Pattern

Adapter Pattern

YouTube Video

Video Materials

Another pattern is the adapter pattern. The adapter pattern is a structural pattern that is used to make an existing interface fit within a different interface. Just like we might use an adapter when traveling abroad to allow our appliances to plug in to different electrical outlets around the world, the adapter pattern lets us use one interface in place of another, similar interface.

Adapter Pattern Adapter Pattern1

In the UML diagram above, we see two different approaches to using the adapter pattern. First, we see the object adapter, which simply stores an instance of the object to be adapted, and then translates the incoming method calls (or messages) to match the appropriate ones available in the object it is adapting.

The other approach is the class adapter, which typically works by subclassing or inheriting the class to be adapted, if possible. Then, our code can call the operations on the adapter class, which can then call the appropriate methods in its parent class as needed.

Let’s look at a quick example to see how we can use the adapter pattern in our code.

Example

Let’s assume we have a Pet class that is used to record information about our pets. However, the original class was written to use metric units, and we’d like our program to use the United States customary units system instead. In that case, we could use the adapter pattern to adapt this class for our use.

To make it simple, we’ll assume that our Pet class includes attributes weight, measured in kilograms, as well as age, measured in years. Each of those attributes includes getters and setters in the Pet class.

Object Adapter

First, let’s look at the adapter pattern using the object adapter approach. In this case, our adapter will store an instance of the Pet class as an object, and then use its methods to access methods within the encapsulated object.

import java.lang.Math;

public class PetAdapter{

    private Pet pet;
    
    public PetAdapter() {
        this.pet = new Pet();
    }
    
    public int getWeight() {
        // convert kilograms to pounds
        return Math.round(this.pet.getWeight() * 2.20462);
    }
    
    public void setWeight(int pounds) {
        // convert pounds to kilograms
        this.pet.setWeight(Math.round(pounds * 0.453592));
    }
    
    public int getAge() {
        // no conversion needed
        return this.pet.getAge();
    }
    
    public void setAge(int years) {
        // no conversion needed
        this.pet.setAge(years);
    }

}
class PetAdapter:
    
    def __init__(self) -> None:
        self.__pet = Pet()
        
    @property
    def weight(self) -> int:
        # convert kilograms to pounds
        return round(self.__pet.weight * 2.20462)
    
    @weight.setter
    def weight(self, pounds: int) -> None:
        # convert pounds to kilograms
        self.__pet.weight = round(pounds * 0.453592)
        
    @property
    def age(self) -> int:
        # no conversion needed
        return self.__pet.age
    
    @age.setter
    def age(self, years: int) -> None:
        # no conversion needed
        self.__pet.age = years

As we can see, we can easily write methods in our PetAdapter class that perform the conversions needed and call the appropriate methods in the Pet object contained in the class.

Class Adapter

The other approach we can use is the class adapter approach. Here, we’ll inherit from the Pet class itself, and implement any updated methods.

import java.lang.Math;

public class PetAdapter extends Pet{
    
    public PetAdapter() {
        super();
    }
    
    @Override
    public int getWeight() {
        // convert kilograms to pounds
        return Math.round(super.getWeight() * 2.20462);
    }
    
    @Override
    public void setWeight(int pounds) {
        // convert pounds to kilograms
        super.setWeight(Math.round(pounds * 0.453592));
    }
    
    // Age methods are already inherited and don't need adapted

}
class PetAdapter(Pet):
    
    def __init__(self) -> None:
        super().__init__()
        
    @property
    def weight(self) -> int:
        # convert kilograms to pounds
        return round(super().weight * 2.20462)
    
    @weight.setter
    def weight(self, pounds: int) -> None:
        # convert pounds to kilograms
        super().weight = round(pounds * 0.453592)
        
    # Age methods are already inherited and don't need adapted

In this approach, we override the methods that need adapted in our subclass, but leave the rest of them alone. So, since the age getter and setter can be inherited from the parent Pet class, we don’t need to include them in our adapter class.

Subsections of Adapter Pattern

Template Method Pattern

The last pattern we’ll review in this course is the template method pattern. The template method pattern is a pattern that is used to define the outline or skeleton of a method in an abstract parent class, while leaving the actual details of the implementation to the subclasses that inherit from the parent class. In this way, the parent class can enforce a particular structure or ordering of the steps performed in the method, making sure that any subclass will behave similarly.

In this way, we avoid the problem of the subclass having to include large portions of the code from a method in the parent class when it only needs to change one aspect of the method. If that method is structured as a template method, then the subclass can just override the smaller portion that it needs to change.

Template Method Pattern UML Template Method Pattern UML1

In the UML diagram above, we see that the parent class contains a method called templateMethod(), which will in part call primitive1() and primitive2() as part of its code. In the subclass, the code for the two primative methods can be overridden, changing how parts of the templateMethod() works, but not the fact that primitive1() will be called before primitive2() within the templateMethod().

Example

Let’s look at a quick example. For this, we’ll go back to our prior example involving decks of cards. The process of preparing for most games is the same, and follows these three steps:

  1. Get the deck.
  2. Prepare the deck, usually by shuffling.
  3. Deal the cards to the players.

Then, each individual game can modify that process a bit based on the rules of the game. So, let’s see what that might look like in code.

import java.util.List;

public abstract class CardGame {

    protected int players;
    protected Deck deck;
    protected List<List<Card>> hands;

    public CardGame(int players) {
        this.players = players;
    }

    public void prepareGame() {
        this.getDeck();
        this.prepareDeck();
        this.dealCards(this.players);
    }
    
    protected abstract void getDeck();
    protected abstract void prepareDeck();
    protected abstract void dealCards(int players);

}
from abc import ABC, abstractmethod
from typing import List, Optional


class CardGame(ABC):
    
    def __init__(self, players: int) -> None:
        self._players = players
        self._deck: Optional[Deck] = None
        self._hands: List[List[Card]] = list()
        
    def prepare_game(self) -> None:
        self._get_deck()
        self._prepare_deck()
        self._deal_cards(self._players)
        
    @abstractmethod
    def _get_deck(self) -> None:
        raise NotImplementedError
        
    @abstractmethod
    def _prepare_deck(self) -> None:
        raise NotImplementedError
    
    @abstractmethod
    def _deal_cards(self, players: int) -> None:
        raise NotImplementedError

First, we create the abstract CardGame class that includes the template method prepareGame(). It calls three abstract methods, getDeck(), prepareDeck(), and dealCards(), which need to be overridden by the subclasses.

Next, let’s explore what this subclass might look like for the game Hearts. That game consists of 4 players, uses a standard 52 card deck, and deals 13 cards to each player.

import java.util.LinkedList;

public class Hearts extends CardGame {

    public Hearts() {
        // hearts always has 4 players.
        super(4);
    }
    
    @Override
    public void getDeck() {
        this.deck = DeckFactory.getInstance().getDeck(DeckType.valueOf("Standard 52"));
    }
    
    @Override
    public void prepareDeck() {
        this.deck.suffle();
    }
    
    @Override
    public void dealCards {
        this.hands = new LinkedList<>();
        for (int i = 0; i < this.players; i++) {
            LinkedList<Card> hand = new LinkedList<>();
            for (int i = 0; i < 13; i++) {
                hand.add(this.deck.draw());
            }
            this.hands.add(hand);
        }
    }
}
from typing import List


class Hearts(CardGame):
    
    def __init__(self):
        # hearts always has 4 players
        super().__init__(4)
        
    def _get_deck(self) -> None:
        self._deck: Deck = DeckFactory.get_instance().get_deck(DeckType("Standard 52"))
        
    def _prepare_deck(self) -> None:
        self._deck.shuffle()
    
    def _deal_cards(self, players: int) -> None:
        self._hands: List[List[Card]] = list()
        for i in range(0, players):
            hand: List[Card] = list()
            for i in range(0, 13):
                hand.append(self._deck.draw())
            self._hands.append(hand)

Here, we can see that we implemented the getDeck() method to get a standard 52 card deck. Then, in the prepareDeck() method, we shuffle the deck, and finally in the dealCards() method we populate the hands attribute with 4 lists of 13 cards each. So, whenever anyone uses this Hearts subclass and calls the prepareGame() method that is defined in the parent CardGame class, it will properly prepare the game for a standard game of Hearts.

To adapt this to another type of game, we can simply create a new subclass of CardGame and update the implementations of the getDeck(), prepareDeck() and dealCards() methods to match.

Summary

In this chapter, we explored several software design patterns introduced by the “Gang of Four” in their 1994 book: Design Patterns: Elements of Reusable Object-Oriented Software. Design patterns are a great way to build our code using reusable, standard structures that can solve a particular problem or perform a particular task. By using structures that are familiar to other developers, it makes it easier for them to understand our code.

Software design patterns are loosely grouped into three categories:

  • Creational Patterns
  • Structural Patterns
  • Behavioral Patterns

We studied 6 different design patterns. The first three were creational patterns:

  • Builder Pattern is useful for building complex objects by offloading the work to a special builder class, letting other classes reference the builder as needed.
  • Factory Method Pattern is used to construct objects based on a given parameter, making it easy to get the object you need without needing to even explicitly know the exact type.
  • Singleton Pattern allows us to make sure only a single instance of a class is present on a system at any given time.

We also studied a structural pattern:

  • Adapter Pattern is helpful when we need to make an existing class’s interface match a different desired interface

Finally, we looked at two behavioral patterns:

  • Iterator Pattern allows us to walk through a collection of items, and makes use of the enhanced for or for each loops present in our language.
  • Template Method Pattern allows us to build the internal structure of a method, but then delegate some of the actual computation to methods that can be overridden by subclasses.

In the future, we can use these patterns in our code to solve specific problems and hopefully make our program’s structure easier for other developers to understand.

Review Quiz

Check your understanding of the new content introduced in this chapter below - this quiz is not graded and you can retake it as many times as you want.

Quizdown quiz omitted from print view.