niedziela, 19 maja 2013

Design patterns: (Abstract) Factory: some code

This is one of the posts of the design patterns in Python series. In my last post I've written about abstract factory. To read an introduction to design patterns and Gang of Four (GoF) see the first post.

DjangoCon: The One True way to do cons.

Now that I'm back from DjangoCon Europe 2013 (which was awesome), I get
back to writing about design patterns.


A plain maze game


Recently I've promised you a text game in Python, which would implement the Abstract Factory pattern. The example is taken from the GoF book.

The Maze Game
Let's imagine we want to create a simple maze game. A Maze consists of Rooms, which can contain Doors and Walls. Each Room has 4 sides: NORTH, SOUTH, EAST and WEST, which are encapsulated in form of enum type Direction. On each side of a Room there is a Wall or a Door. Rooms, Doors and Walls are all MapSites. A Player is like a pointer to our current position in the maze.

Maze Construction

To construct a Maze we'll need methods to add some rooms and to get them by number:
class Maze(object):

    """Maze is a container for rooms."""

    def __init__(self):
        self.rooms = {}

    def add_room(self, room):
        """Add a room."""
        self.rooms[room.room_number] = room

    def get_room_by_number(self, room_number):
        """Get room by its number."""
        return self.rooms[room_number]
Here's our abstract MapSite:
class MapSite(object):

    """Just an abstract for maze elements."""
    pass
In my implementation the sides of a Room are represented by a _sides dictionary with four keys: Direction.NORTH, SOUTH and so on. A Room has also a room_number.
class Room(MapSite):

    """Represents a room with four sides - north, south, east and west - and a room number."""

    def __init__(self, room_number):
        super(Room, self).__init__()
        self._sides = {
            Direction.NORTH: None,
            Direction.SOUTH: None,
            Direction.EAST: None,
            Direction.WEST: None
        }
        self.room_number = room_number

    def get_side(self, direction):
        """Return a MapSite object for given direction."""
        return self._sides[direction]

    def set_side(self, direction, mapsite_object):
        """Set a MapSite object for given direction."""
        self._sides[direction] = mapsite_object

As said before, each side of a Room can be a Door or a Wall. There's nothing special about the Wall:
class Wall(MapSite):

    """Represents a Wall - you can't do anything with it."""

    pass
The Door class is slightly more complicated. It can be opened or closed, we also want to store the information about the Room objects that it leads to. There's also a helper function other_side_from, which - given a room object - simply returns what's on the other side of the door.
class Door(MapSite):

    """A door joins two rooms and it's got two states: open and closed."""

    def __init__(self, room1, room2, is_open=False):
        super(Door, self).__init__()
        self._open = is_open
        self._rooms = {
            room2.room_number: room1,  # from room 2 we can get to the room 1
            room1.room_number: room2   # from room 1 we can get to the room 2
        }

    def other_side_from(self, room):
        """Return the Room object from the other side of the door."""
        return self._rooms[room.room_number]

    @property
    def is_open(self):
        """Returns True if the door is opened, False otherwise."""
        return self._open

    def unlock(self):
        self._open = True
        print "The door is now unlocked!"

Sample Maze creation

OK, so now let's say we want to create a Maze with two Rooms and one Door. Seems fairly simple, but unfortunately requires quite a lot operations to be made.

# simple Maze, requires lots of code
from maze import Maze, Room, Door, Direction, Wall

def create_maze():
    """Series of operations which create our Maze."""
    maze = Maze()
    room1 = Room(1)
    room2 = Room(2)
    thedoor = Door(room1, room2)

    maze.add_room(room1)
    maze.add_room(room2)

    room1.set_side(Direction.NORTH, Wall())
    room1.set_side(Direction.EAST, thedoor)
    room1.set_side(Direction.SOUTH, Wall())
    room1.set_side(Direction.WEST, Wall())

    room2.set_side(Direction.NORTH, Wall())
    room2.set_side(Direction.EAST, Wall())
    room2.set_side(Direction.SOUTH, Wall())
    room2.set_side(Direction.WEST, thedoor)

    return maze

Moving inside a Maze

To move inside our Maze we could define a method enter(player) in the MapSite class and implement it in all it's inheriting classes. The Player class represents our position in the Maze, so the enter() method would change the player's position. A sample player session could look like this:
$ python basic_creator.py

--> Player Pinky enters the maze in room
 WEST SIDE: maze.Wall object at 0xb744550c
 EAST SIDE: maze.Door object at 0xb744f7cc
 NORTH SIDE: maze.Wall object at 0xb74454cc
 SOUTH SIDE: maze.Wall object at 0xb74454ec
--> Pinky enters room: 2
 WEST SIDE: maze.Wall object at 0xb744550c
 EAST SIDE: maze.Door object at 0xb744f7cc
 NORTH SIDE: maze.Wall object at 0xb74454cc
 SOUTH SIDE: maze.Wall object at 0xb74454ec
Abstract Factory is about creating objects so I won't post details about Player's movement here, but you can find the sample implementation at my github - the link is at the end of this post.

The Enchanted Maze comes in

Wotta handsome guy.

Let's assume that there's also another type of Maze - an enchanted Maze. The Player turns into a Wizard, the Room is now an EnchantedRoom, the Door becomes a DoorNeedingSpell.
The Wizard is an enhanced Player, who can simply cast spells.



EnchantedRoom means that if somebody comes in, then he or she gets under the spell (let's hope there's no Avada Kedavra room!).
class EnchantedRoom(Room):

    """When the room is enchanted, it casts the spell on the player."""

    def __init__(self, room_number, spell):
        super(EnchantedRoom, self).__init__(room_number)
        self.spell = spell

DoorNeedingSpell is a separate thing - it means that a Door cannot be opened until a certain spell is cast by a Wizard. It has nothing to do with the spell in EnchantedRoom.
class DoorNeedingSpell(Door):

    """This door can't be opened manually - a wizard has to cast a spell on it."""

    def __init__(self, room1, room2):
        super(DoorNeedingSpell, self).__init__(room1, room2)

    def unlock(self, with_spell=False):
        """Unlocking without a spell doesn't work."""

        if not with_spell:
            print "You need to cast a spell first."
        else:
            super(DoorNeedingSpell, self).unlock()

Moving inside an enchanted Maze

Similarly to the plain Maze, we could define a method enter(player) in the MapSite class and just like in Maze implement it in all it's inheriting classes. Wizard derives from Player class, so there's no large difference either. A wizard-player session could look like this:
---> Wizard Harry enters the enchanted maze in room 1
 WEST SIDE: maze.Wall object at 0xb74cc96c
 EAST SIDE: enchanted_maze.DoorNeedingSpell object at 0xb74cc92c
 NORTH SIDE: maze.Wall object at 0xb74cc94c
 SOUTH SIDE: maze.Wall object at 0xb74cc8ec
Harry casts a spell: Alohomora!
The door is now unlocked!
--> Harry enters room: 2
Harry is under the spell Expelliarmus
 WEST SIDE: maze.Wall object at 0xb74cc96c
 EAST SIDE: enchanted_maze.DoorNeedingSpell object at 0xb74cc92c
 NORTH SIDE: maze.Wall object at 0xb74cc94c
 SOUTH SIDE: maze.Wall object at 0xb74cc8ec
The sample enchanted Maze movement implementation is also at my github.

Sample enchanted Maze


If you're still here and you're wondering what's my point, then here it is. If we wanted to create tons of different Mazes - an enchanted Maze, a plain Maze, maybe a bombed Maze or underwater Maze with the same structure as the plain Maze, we'd have to almost copy-paste the code from the create_maze() function I've shown you before.

E.g. for enchanted Maze:

# enchanted Maze, copy-paste from plain Maze
from maze import Direction, Wall, Maze
from enchanted_maze import EnchantedRoom, DoorNeedingSpell

def create_enchanted_maze():
    """Series of operations which create our Maze."""
    maze = Maze()
    room1 = EnchantedRoom(1)
    room2 = EnchantedRoom(2)
    themagicdoor = DoorNeedingSpell(room1, room2)

    maze.add_room(room1)
    maze.add_room(room2)

    room1.set_side(Direction.NORTH, Wall())
    room1.set_side(Direction.EAST, themagicdoor)
    room1.set_side(Direction.SOUTH, Wall())
    room1.set_side(Direction.WEST, Wall())

    room2.set_side(Direction.NORTH, Wall())
    room2.set_side(Direction.EAST, Wall())
    room2.set_side(Direction.SOUTH, Wall())
    room2.set_side(Direction.WEST, themagicdoor)

    return maze

And so on.

 

 The meat and potatoes 


To prevent this we'll create some Maze factories to standardise the creation process. The basic structure will be like this:
class BasicMazeFactory(object):

    """Factory producing basic Maze."""

    @staticmethod
    def make_maze():
        return Maze()

    @staticmethod
    def make_wall():
        return Wall()

    @staticmethod
    def make_room(room_number):
        return Room(room_number)

    @staticmethod
    def make_door(room1, room2):
        return Door(room1, room2)

Static methods in BasicMazeFactory

I've made the factory's method static, because the factory itself doesn't use it's contents to instantiate the Maze parts. This can change of course if we want to pass some variable when initializing the BasicMazeFactory, e.g. maximum number of rooms. This could look like:
class LimitedMazeFactory(BasicMazeFactory):

    def __init__(self, max_rooms):
        self._rooms_left = max_rooms

    def make_room(self, room_number):
        if self._rooms_left > 0:
            self._rooms_left -= 1
            return Room(room_number)
        else:
            print "Maximum room number reached - you can't add any more Rooms!"
If we want to produce another type of a Maze, we need to define specialized factory. For enchanted Maze it could look like this:
class EnchantedMazeFactory(BasicMazeFactory):

    """Factory producting Enchanted Maze."""

    @staticmethod
    def make_room(room_number):
        return EnchantedRoom(room_number, spell="Expelliarmus")

    @staticmethod
    def make_door(room1, room2):
        return DoorNeedingSpell(room1, room2)
To use the factories as interchangeable components we need to redefine our create_maze() function. From now on it'll take a factory class as a parameter.
def create_maze(factory):
    """Series of operations which create our Maze."""

    maze = factory.make_maze()
    room1 = factory.make_room(1)
    room2 = factory.make_room(2)
    thedoor = factory.make_door(room1, room2)

    maze.add_room(room1)
    maze.add_room(room2)

    room1.set_side(Direction.NORTH, factory.make_wall())
    room1.set_side(Direction.EAST, thedoor)
    room1.set_side(Direction.SOUTH, factory.make_wall())
    room1.set_side(Direction.WEST, factory.make_wall())

    room2.set_side(Direction.NORTH, factory.make_wall())
    room2.set_side(Direction.EAST, factory.make_wall())
    room2.set_side(Direction.SOUTH, factory.make_wall())
    room2.set_side(Direction.WEST, thedoor)

    return maze

 

The final solution


So finally, how do we create out mazes now? It's very simple:
basic_maze = create_maze(BasicMazeFactory)
enchanted_maze = create_maze(EnchantedMazeFactory)
And that's it! We've created a reusable function to create our mazes, yay!

The code and references


If you want to see my full code, here's my github: abstract factory implementation.

If you're interested with this topic you can see the GoF book (full PDF), which is my main source of examples and explanations. The Abstract Factory pattern is on the page 99.

Any comments, suggestions or corrections are welcome.

niedziela, 12 maja 2013

Design patterns: (Abstract) Factory in Python: some theory

First design pattern by GoF I'll try to implement in Python is Abstract Factory. To read an introduction to design patterns and Gang of Four (GoF) see my previous post.

(Abstract) Factory: some theory

What is it?

A factory, as you can guess by its name, is supposed to produce lots of similar products - and by
products we mean classes. GoF describes this method as good for creating families of related or dependant objects.

Structure


Objects, objects everywhere!

GoF introduces structures like AbstractFactory and ConcreteFactory. AbstractFactory is just an abstract class, which defines some methods for concrete implementations (ConcreteFactory1, ConcreteFactory2, etc.). AbstractFactory should have methods for creating object, e.g. make_object should return instantiated AbstractObject. ConcreteFactory1 and ConcreteFactory2 would return SomeObject1 and SomeObject2, which are AbstractObject's implementation. Such AbstractFactory was introduced so that its implementations could be interchangeable. Let's see how this would look translated directly to Python:
# too much code!
import abc

class AbstractFactory(object):

    __metaclass__ = abc.ABCMeta
    
    @abc.abstractmethod
    def make_object(self):
        return

class ConcreteFactory1(AbstractFactory):
    def make_object(self):
        # do something
        return SomeObject1()

class ConcreteFactory2(AbstractFactory):
    def make_object(self):
        # do something else
        return SomeObject2()

I used the Python's built-in abc module, which allows to define Abstract Base Classes (ABC).
But in Python we don't need a common base class to pass different classes to a function - Python is dynamically typed, you can pass anything everywhere. Here's a more Pythonic approach, which is definitely less verbose:
# better example
class ConcreteFactory1(object):
    def make_object(self):
        # do something
        return SomeObject1()

class ConcreteFactory2(object):
    def make_object(self):
        # do something else
        return SomeObject2()

So actually, in this Abstract Factory example there's no explicit abstract factory class, hence I've put parentheses in  the title.
Abstract Factory is somewhat implicit now

Usage

So now we know how a basic factory looks like, but what can we use it for? The short answer is: when in need of common interface or when you have to do sequences of operations with objects that are created differently. Usage of this abstract factory could look like this
def client_function(factory):
    an_object = factory.make_object()

    # do something with an_object...

    return a_result

def prepare_client():
    factory1 = ConcreteFactory1()
    result1 = client_function(factory1)
    factory2 = ConcreteFactory2()
    result2 = client_function(factory2)
Some real-life examples could be:

  • according to GoF: providing different look-and-feel for operating systems (e.g different looking buttons, scrollbars, etc.)
  • one I can think of: creating similar objects from multiple data sources (e.g. database, XML, JSON, user input etc.)
  • ...? (Any ideas? Share!)

Coming soon: full Python example

I've prepared an example in Python from the GoF book. I want to keep my blog posts concise, so I'll put it in a different post. It'll be a very simple text game. There'll be wizards and magic!

piątek, 10 maja 2013

Introduction to design patterns and GoF

This is my first post about Design Patterns in Python.

What exactly is a design pattern?

"A bowl of spaghetti looks twisted
and tangled, which is where
 the name for spaghetti code
 comes from." - Wikipedia
A design pattern is a potentially reusable solution to many programming problems. In most cases, a programmer is coping with a task, which already had been done by someone else. In my understanding of this term, design patterns were found to help to keep code clear, instead of writing spaghetti code.

Let's face it - everybody writes ugly code sometimes or did that in the past. I think that design patterns may be also a way to write the code you're proud of.




Gang of Four (GOF)


The GoF book.
This is the alias for Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides, who came up with a book Design Patterns: Elements of Reusable Object-Oriented Software. It was published quite a long time ago (1994), but is still widely recognized by programmers - I've even read an opinion on Stackoverflow, stating that it's the only one book in this topic. (That's not true). In case you're interested - here's the full GoF book: PDF.


A word of warning and encouragement: Don't worry if you don’t understand this book completely on the first reading. We didn’t understand it all on the first writing!

 - GoF 

There are 23 desing patterns described, divided into 3 groups: 

  • Creational (C)
  • Structural (S)
  • Behavioral (B)

It would be hard for me to cover all of them design patterns, so for this blog I'll start with the ones described in the book as "simplest and most common". Here's the list:

  • Abstract Factory (C) (theory, full example)
  • Adapter (S)
  • Composite (S)
  • Decorator (S)
  • Factory Method (C)
  • Observer (B)
  • Strategy  (B)
  • Template Method (B)
I'll probably post also about Singleton (C) and Iterator (B), as they are widely used in many programming languages.

Stay tuned, first design pattern examples in Python coming soon!

Welcome!

I'm Patrycja and welcome to my blog. It is supposed to be about coding in Python. Here are some of the topics I want to cover:

  • design patterns in Python - I've learned about Gang of Four recently. Since it's so widely
    mentioned (even in job offers), I'm gonna try to use some of these patterns in Python and see if it makes sense, or not.
  • Django ideas - mainly because I use it in my work. Some Javascript may show up too.
  • pycassa, a Python Cassandra client - I've used it a lot in my previous job, so maybe I can share some thoughts on it too
  • other Python related things - DjangoCon is coming! And I've got a feeling it will be inspirational.
I've been thinking about my own blog in IT-related topics for a quite long time, but I didn't have a formed concept about how it would look like. One day I've figured that I'd love to do something to improve my Python (and English) skills gradually. 'Why not share some thoughts about programming and Python, in effort to learn something? Maybe someone will find it useful too, or will give me his/hers thoughts about this subject'.
So here it is, any comments are really appreciated. And sorry for my bad English - feel free to point out my mistakes.

Here you can see the details about this blog and why python is in the pink.