Bin Class

This chapter will present the design for the Bin class. In Bin Analysis we’ll look at the responsibilities and collaborators for a bin.

As part of designing the Bin class, we need to choose what is the most appropriate kind of collection class to use. We’ll show how to do this in Design Decision – Choosing A Collection.

In Wrap vs. Extend a Collection we’ll look at two principle ways to embed a collection class in an application. We’ll clarify a few additional issues in Bin Questions and Answers.

In the Bin Design section we’ll provide the detailed design information. In Bin Deliverables we’ll enumerate what must be built.

Bin Analysis

The Roulette wheel has 38 bins, identified with a number and a color. Each of these bins defines a number of closely related winning Outcome instances.

Each Bin object will have from one to fourteen different Outcome instances. An individual number pays a variety of bets because it’s associated with a number of outcomes. The number 12, for example, is also even. The exact collection of Outcome instances for each Bin is part of a later exercise.

At this time, we’ll define the Bin class, and use this class to contain a number of Outcome objects.

Two of the Bin instances have relatively few Outcome instances. These are the exceptional cases:

  • The zero Bin instance contains a “0” Outcome object and the “00-0-1-2-3” Outcome object.

  • The double-zero Bin instance contains a “00” Outcome object and the “00-0-1-2-3” Outcome object.

The other 36 Bin instances contain a mixture of bets including a straight bet, split bets, street bets, corner bets, line bets and all of the outside bets (column, dozen, even or odd, red or black, high or low). Any of these bits will win if this Bin is selected.

Some Outcome instances, like “red” or “black”, occur in as many as 18 individual Bin instances. Other Outcome instances, like the straight bet numbers, each occur in only a single Bin. We will have to be sure that our Outcome objects are shared appropriately by the Bin instances.

Since a Bin is a collection of individual Outcome objects, we have to select a collection class to contain the objects. In the next section we’ll overview how this choice of a collection can be made.

Design Decision – Choosing A Collection

There are five basic Python types that are a containers for other objects.

  • Immutable Sequence: tuple. This is a good candidate for the kind of collection we need, since the elements of a Bin don’t change. However, a tuple allows duplicates and retains things in a specific order; we can’t tolerate duplicates, and order doesn’t matter.

  • Mutable Sequence: list. While handy for the initial construction of the bin, this isn’t really useful because the contents of a bin don’t change once they have been enumerated.

  • Mutable Mapping: dict. We don’t need the key-to-value mapping feature at all. A map does more than we need for representing a Bin.

  • Mutable Set: set. Duplicates aren’t allowed, membership tests are fast, and there’s no inherent ordering. This looks close to what we need.

  • Immutable Set: frozenset. Duplicates aren’t allowed, membership tests are fast, and there’s no inherent ordering. There’s not changing it after it’s been built. This seems to be precisely what we need.

Having looked at the fundamental collection varieties, we will elect to use a frozenset.

How will we use this collection?

Wrap vs. Extend a Collection

There are two general ways to use a collection.

  • Wrap. Define a class which has an attribute that holds the collection. We’re wrapping an existing data structure in a new class. The type hint suggests we can use any iterable collection of Outcome instances as a source to create the frozenset collection.

    Something like this:

    class Bin:
        def __init__(self, outcomes: Iterable[Outcome]) -> None:
            self.outcomes = frozenset(outcomes)
    
  • Extend. Define a class which is the collection. We’re essentially renaming an existing data structure to provide a more descriptive name for the class.

    Something like this:

    class Bin(frozenset):
        pass
    

Both are widely-used design techniques. The trade off between them isn’t clear at first.

Considerations include the following:

  • When we wrap, we’ll often need to write a lot of additional methods for representation, length, comparisons, inquiries, etc.

    In some cases, we will wrap a collection specifically so that these additional methods are not available. We want to completely conceal the underlying data structure.

  • When we extend, we get all of the base methods of the collection. We can add any unique features.

In this case, extending the existing data structure seems to make more sense than wrapping a frozenset.

Bin Questions and Answers

Why wasn’t Bin in the design overview?

The definition of the Roulette game did mention the 38 bins of the wheel. However, when identifying the nouns, it didn’t seem important. Then, as we started designing the Wheel class, the description of the wheel as 38 bins came more fully into focus. Rework of the preliminary design is part of detailed design. This is the first of several instances of rework.

Why introduce an entire class for the bins of the wheel? Why can’t the wheel be an array of 38 individual arrays?

There are two reasons for introducing Bin as a separate class:

  • A separate class can improve the fidelity of our object model of the problem.

  • A new class will reduce the complexity of the Wheel class.

The definition of the game describes the wheel as having 38 bins, each bin causes a number of individual Outcome instances to win. Without thinking too deeply, we opted to define the Bin class to hold a collection of Outcome instances. At the present time, we can’t foresee a lot of processing that is the responsibility of a Bin. But allocating a class permits us some flexibility in assigning responsibilities there in the future. We can, specifically, change to alternative implementations of the Bin to try to optimize resource use.

Additionally, looking forward, it is clear that the Wheel class will use a random number generator and will pick a winning Bin. In order to keep this crisp definition of responsibilities for the Wheel class, it makes sense to delegate all of the remaining details to another class.

Isn’t an entire class for bins a lot of overhead?

The short answer is no, class definitions are almost no overhead at all. At run-time a class definition is a namespace for methods. It’s the class instances that cause run-time overhead.

How can you introduce Set, List, Dict and other types when these don’t appear in the problem?

We have to make a distinction between the classes that are uncovered during analysis of the problem in general, and classes are that are part of the implementation of this particular solution. This emphasizes the distinction between the problem as described by users and a solution as designed by software developers.

The built-in collections is part of a solution, and only hinted at by the definition of the problem. The whole point of creating a solution is to introduce the right technology choices; the solution will always have classes and modules never mentioned in the problem domain.

Why extend (or rename) a built-in data structure? Why not simply use it?

There are two reasons for extending a built-in data structure:

  • We can easily add methods when extending. In the case of a Bin, there doesn’t seem to be much we want to add. In the future, we many uncover attributes or behaviors that we need to include in our software.

  • We can easily change the underlying data structure when extending. For example, we might have a different set-like collection that also inherits from the collections.abc.Set base class. We can make this change in just one place – our class extension – and the entire application benefits from the alternative set implementation.

What about hash and equality?

We are’t going to be comparing bins against each other. The default rules for equality and hash computation will work out just fine for this class.

Bin Design

class Bin(Collections.frozenset)

Bin contains a collection of Outcome instances which reflect the winning bets that are paid for a particular bin on a Roulette wheel. In Roulette, each spin of the wheel has a number of Outcome instances. Example: A spin of 1, selects the “1” bin with the following winning Outcome instances: “1” , “Red” , “Odd” , “Low” , “Column 1” , “Dozen 1-12” , “Split 1-2” , “Split 1-4” , “Street 1-2-3” , “Corner 1-2-4-5” , “Five Bet” , “Line 1-2-3-4-5-6” , “00-0-1-2-3” , “Dozen 1” , “Low” and “Column 1” . These are collected into a single Bin .

Fields

Since this is an extension to the existing frozenset, we don’t need to define any additional fields.

Constructors

We don’t really need to write any more specialized constructor method.

We’d use this as follows:

Python Bin Construction

five = Outcome("00-0-1-2-3", 6)
zero = Bin([Outcome("0",35), five])
zerozero = Bin([Outcome("00",35), five])
  1. zero is based on references to two objects: the “0” Outcome and the “00-0-1-2-3” Outcome.

  2. zerozero is based on references to two objects: the “00” Outcome and the “00-0-1-2-3” Outcome.

Methods

We don’t really need to write any more specialized methods.

How do we accumulate several outcomes in a single Bin?

  1. Create a simple list, tuple, or set as an interim structure.

  2. Create the Bin from this.

We might have something like this:

>>> bin1 = Bin({outcome1, outcome2, outcome3})

We created an interim set object and built the final Bin from that collection object.

Bin Deliverables

There are two deliverables for this exercise. Both should have Python docstrings.

  • The Bin class. This is part of the roulette.py file, along with the Outcome class.

  • A class which performs a unit test of the Bin class. The unit test should create several instances of Outcome, two instances of Bin and establish that Bin instances can be constructed from the Outcome instances.

    Programmers who are new to OO techniques are sometimes confused when reusing individual Outcome instances. This unit test is a good place to examine the ways in which object references are shared. A single Outcome object can be referenced by several Bin instances. We will make increasing use of this in later sections.

Looking Forward

In the next chapter, we’ll design the Wheel class to contain all of the Bin objects. A Wheel object can select a Bin which has a number of Outcome objects representing winning bets.