[Tutor] Null Object Design Pattern Question [sentinels and Minesweeper]

Danny Yoo dyoo at hkn.eecs.berkeley.edu
Mon Jun 14 04:01:23 EDT 2004


On Sun, 13 Jun 2004, [ISO-8859-1] Gon=E7alo Rodrigues wrote:

> >I have been looking at
> >
> >http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/68205
> >
> >which has an example of using a Null() object.
> >
> >Unfortunately, I'm having a problem wrapping my brain around the
> >usefulness of this object compared to just using None.
> >
> >Does anyone have some further examples of the 'superiority' of this
> >approach over *None*.
>
> Imagine a null object as a kind of sink: it responds to all messages
> without barfing.


Hi Tim,

If you're familiar with the use of a "sentinel" to simplify a program's
logic, then you can think of a "Null Object" as an OOP analogy to a
sentinel.


I like using concrete examples, and one of the nice examples where
sentinels come in really handy is something like a Minesweeper-like game
program.  Here's a quick link to one:

http://www.programming-challenges.com/pg.php?page=3Ddownloadproblem&probid=
=3D110102&format=3Dhtml

In the game of Minesweeper, we might want to report the number of mines
adjacent to a space.  For example, if we have:

*...
=2E...
=2E*..
=2E...

we'd like to get back something like:

*100
2210
1*10
1110


If we represent a minefield as a list of rows, like this:

minefield =3D ['*...',
             '....',
             '.*..',
             '....']

and if we wanted to get the number of mines at an
arbitrary row and col, we might write something like:

###
def countMines(row, col):
    """Returns the number of mines around position(row, col)."""
    count =3D 0
    for i in [-1, 0, 1]:
        for j in [-1, 0, 1]:
            if minefield[row + i][col +j] =3D=3D '*':
                count +=3D 1
    return count
###


The only problem is that this code doesn't work.  *grin*

###
>>> countMines(3, 3)
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
  File "<stdin>", line 6, in countMines
IndexError: string index out of range
###

The problem is that there's this special logic around the edges of the
board, so our beautiful countMines() function malfunctions around the
board edges.


One way to fix countMines() is to add special-case logic into countMines()
for counting mines around the edges of the board, maybe something like:

###
if not (0 <=3D row + i < 4): continue
###


But there's another way of fixing the problem, and it involves using
sentinels.  In effect, we take something like:

*...
=2E...
=2E*..
=2E...


and wrap a sentinel border of '=3D' characters around the whole game board:

=3D=3D=3D=3D=3D=3D
=3D*...=3D
=3D....=3D
=3D.*..=3D
=3D....=3D
=3D=3D=3D=3D=3D=3D

With this sentinel border, the code doesn't have to worry about the edge
of the board so much.  If I ask for the number of mines on the
bottom-right corner --- countMines(4, 4) --- then we're perfectly ok,
since we don't go off the board.  Does this make sense?


Null Objects perform a similar function: they are "sentinels" that allow
us to simplify our main code, so that we don't have to worry so much about
the special case of handling a reference to None.


Hope this helps!




More information about the Tutor mailing list