Reputation: 2349
I think I'm misusing the concept of subclass. I'm working on a hobby project with Grids and Cells.
What I have, is the implementation of a Cell
class, and its subclass HexCell
which basically redefines many of the attributes/methods like so:
class Cell:
def __init__(self, row_loc, col_loc):
self.row = row_loc
self.col = col_loc
self.links = set()
self.neighbors = 4*[None]
def __repr__(self):
return f'Cell @({self.row},{self.col})'
def link(self, other, bidir = True):
self.links.add(other)
if bidir: other.links.add(self)
Then I have a subclass that is the HexGrid
which follows a similar structure with new parameters.
class HexCell(Cell):
def __init__(self, r_out, th_around):
# I'm indexing Hex cells around a center cell
# instead of by rows and columns; Prefixed hex
# as they follow the hexagon, and not regular polar coordinates.
self.hex_r = r_out
self.hex_th = th_around
self.neighbors = 6*[None]
self.links = set()
def __repr__(self):
return f"HexCell @[{self.hex_r}, {self.hex_th}]"
def bind(self, other, to_dir):
to_dir = to_dir % 6
if (self.neighbors[to_dir] is None):
self.neighbors[to_dir] = other
other.neighbors[to_dir - 3] = self
# Hexagonal grids share neighbors.
other_1 = other.neighbors[to_dir - 2]
if (self.neighbors[to_dir - 1] is None) & (other_1 is not None):
self.bind(other_1, to_dir - 1)
other_5 = other.neighbors[to_dir - 4]
if (self.neighbors[to_dir - 5] is None) & (other_5 is not None):
self.bind(other_5, to_dir - 5)
In this case, the method self.link(other)
is shared, but other attributes change from rectangular grid to hexagonal like the locaion from (row, col)
to (hex_r, hex_th)
, or neighbors
as a 4-list or 6-list. Thus I'd like these attributes to be dependent on a another cell-type attribute and transferred down to the subclass.
Upvotes: 0
Views: 72
Reputation: 22324
Correct use of subclassing needs to obey the following substitution principle:
If there are some objects
x_1
of typeT_1
andx_2
of typeT_2
such thatissubclass(T_2, T_1) == True
, then any property that applies tox_1
must also apply forx_2
.
In other words, you expect subclassing to implement new behaviours, not to change existing behaviours.
In you example, the change of coordinate system itself is a change of behaviour and thus HexCell
should not inherit from Cell
.
What you can do is create a base class BaseCell
that encapsulates the common behaviour between Cell
and HexCell
and inherit from it.
class BaseCell:
def __init__(self):
self.links = set()
self.neighbors = []
def add_neighbor(self, other):
self.neighbors.append(other)
def link(self, other, bidirectional=True):
self.links.add(other)
if bidirectional:
other.link(self, bidirectional=False)
class Cell(BaseCell):
def __init__(self, row_loc, col_loc):
self.row = row_loc
self.col = col_loc
super().__init__()
def __repr__(self):
return f'Cell @({self.row},{self.col})'
class HexCell(Cell):
def __init__(self, r_out, th_around):
self.hex_r = r_out
self.hex_th = th_around
super().__init__()
def __repr__(self):
return f"HexCell @[{self.hex_r}, {self.hex_th}]"
def bind(self, other, to_dir):
...
Upvotes: 3
Reputation: 492
Your Cell class is in fact not an abstract "Cell", but a square cell in two-dimensional space (has exactly 4 neighbours, has "row" and "col" position). Such cell may not be subclassed by a hex cell, because hex cell is just a different type of cell : )
As you noticed, the only common things are link() method and links attribute. If you insist on subclassing, you could create something like:
class LinkedObject():
def __init__(self):
self.links = set()
def link(self, other, bidir = True):
self.links.add(other)
if bidir: other.links.add(self)
class SquareCell(LinkedObject):
# "Cell" class here
class HexCell(LinkedObject):
# HexCell here
Upvotes: 1