Principles

  • A very important step in OOP is to think beforehand what kind of classes may need to be created based on the problem you’re going to solve. To decide what classes to create and how to make classes communicate and collaborate well (i.e. responsibilities of each object), we can follow some principles:
    • The central principle behind the allocation of responsibility is encapsulation; we do this by attempting to isolate the information or isolate the processing that must be done.
    • Write down the classes you’re going to create as well as their responsibilities and collaborators.
    • Try simulate the co-working process of these objects under a specific scenario and see whether there’s any frictions in current design of classes and process.
    • Eliminate major fractions, and keep the design simple. Premature optimization is the root of all evil.
    • An example here - Roulette Solution Overview.
  • Creating a class to handle appropriate jobs will decomplex the original big class and meanwhile provide ability to embrace future changes (gain flexibilities).
  • Sometimes we found a built-in class useful and our class is pretty much the same as it. We can consider extending this built-in class instead of wrapping it. Because,
    • we can easily add methods by extending.
    • we can easily change the underlying data structure by extending.
  • Deferred Dinding - Sometimes we can a very basic version first and work on the detailed algorithms later. It’s often simplest to build a class incrementally. This is an example where the overall structure is pretty simple, but some details are rather complex.

Python Language Features

format_map

1
2
"{name:s} ({odds:d}:1)".format_map({'name': 'Red', 'odds': 17})
# output: Red (17:1)

The format string uses :s and :d as detailed specifications for the values to interpolate into the string. There’s a lot of flexbility in how numbers are formatted.

vars

1
2
3
4
5
6
7
8
9
10
class Outcome:
def __init__(self, name, odds):
self.name = name
self.odds = odds

def __str__( self ):
return vars(self)

# by calling vars(self), you'll get
{'name': self.name, 'odds': self.odds}

This uses the built-in vars() function to expose the attributes of an object as a simple dictionary that maps attribute names to values. This is similar to using the self.__dict__ internal dictionary.

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. Howver, 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.