I have some code for solving a puzzle game called nurikabe, recently I’ve been rewriting it to OOP (still learning) and have the following structure:
# CNurikabe.py
from includes import Board, Validation, Heuristics
class CNurikabe(object):
...
# CValidation.py
from includes import Board, Heuristics
class CValidation(object):
...
# CHeuristics.py
from includes import Board
class CHeuristics(object):
...
# CBoard.py
class CBoard(object):
def __init__(self, filename):
# Vars shared by every class
self.x, self.y, self.z, self.t = self.parseData(filename)
# run.py
from CNurikabe import CNurikabe
nurikabe = CNurikabe()
nurikabe.solve('output')
# includes.py
from CBoard import CBoard
Board = CBoard('data.dat')
from CHeuristics import CHeuristics
Heuristics = CHeuristics()
from CValidation import CValidation
Validation = CValidation()
CBoard class has info which has to be shared among all the other classes (such as board dimensions, number coordinates, etc), also I want it to be instantiated once, if possible preventing dependency injection (unnecessarily passing the filename to each class init method, for example)
The classes are needed to access the following:
CValidation class use: CBoard and CHeuristics
CHeuristics class use: CBoard
CNurikabe class use: CBoard, CValidation and CHeuristics
The code I have, works just as expected. I can call other class’ methods within the other classes just the way I want it, for example:
# CNurikabe.py:
class CNurikabe(object):
def someFunc(self):
for i in range(Board.dimensionx):
Heuristics.doSomeStuff()
Validation.doSomeMore()
But I’ve read maybe too much about how globals are evil. Also the code inside includes.py is a bit hackish, because if I change the order of the imports the program won’t run, complaining about being unable to import some names.
I also tried another way, only instantiating globally the CBoard class and then, for the other classes, creating an instance of the classes I need. But I felt that was kinda repetitive, creating an unique global instance of CHeuristics inside each class, for example, and that still wouldn’t solve the CBoard global problem.
I also thought about creating an instance inside each class’s init, but then the code would be very verbose, having to call for example: self.Heuristics.doSomeStuff()
So my question is what would be a better approach to structure this? I’ve read about singleton patterns (which may be overkill, since it’s a small project), and endless ways of doing it for multiple languages like C++ and PHP. Actually The way I’m doing it resembles that of the “extern Class instance;” way of doing it in C++, long time ago I was working on a C++ project that had that style and I liked it, didn’t see any problems with it, although the class instances were global.
Globals are evil. However, you probably need a singleton pattern that encapsulate some things together. My experience from C++ and Python is that you can nicely use the hybrid character of the language and use a module in the role of singleton. (If you think more about it, module variables play the role of a singleton member variables, plain functions in the module resemble methods of a singleton.)
This way I suggest to put the board functionality into
board.pyheuristic functionality intoheuristic.py,…, convert the methods to functions,self.variabletovariableand use:Think about the
import boardas about getting the reference to the singleton instance — which actually is, because there is a single instance of the module object. And it will be syntactically the same as your older code — except getting the instance (the module) will be easier.Update: Once you need more instances, you should think in classes again. However, passing objects is very cheap operation in Python — you only copy reference values (technically the address, i.e. 4 or 8 bytes). Once you get the reference value, you can easily assign it to a local variable. (Every assignment in Python means copying the reference value, hence sharing the access to the assigned object. This way, there is actually no need for global variables, and no excuse to use global variables.
Using local variables, the syntax again remains the same.