Python OOP – One Boilerplate Class vs Many Similar Classes

class-designinheritanceobject-orientedobject-oriented-designpython

Lets say I'm trying to model a variety of objects that are virtually identical, the only difference being their class variables. Am I better off creating one boilerplate class and just calling the constructor with the specific variables, or creating a lot (in this case 100+) of essentially identical classes and hard coding their class variables (which will never change)?

To be clear:

  • Identical methods
  • Identical class variable names but different values
  • Identical intended functionality

To be clear, my language (Python) doesn't support interfaces that I'm aware of – some of the other similar questions seemed to get answers recommending that. The other option that seems to make sense to me would be to create a base class with each field and method and then create a bunch of unique child classes that contain their unique values.

The amount of work to create all the child classes isn't significant to me – I've written a program that quickly parses a text file (that has all the unique values) and can write all the classes to a .py file quickly and easily. Making changes down the road is rather easy for the same reason.

(somewhat) Related questions

Best Answer

100+ almost identical classes is crazy. Classes are organizational structures. Having dozens of them with almost no variation does't seem useful. It's over-specialization.

It sounds very much like you need a single class with a shared lookup-table that defines the shared info. Something like:

from collections import namedtuple

Lang = namedtuple('Lang', 'style points creator')

LANG_FORM = {
    'Python': Lang('dynamic', 84, 'Guido van Rossum'),
    'Java':   Lang('static', 65, 'James Gosling'),
    # ...
    'Pascal': Lang('static', 33, 'Niklaus Wirth')
}

class Protean(object):

    def __init__(self, name, form):
        self.name = name
        self.form = form
        self.lang = LANG_FORM[form] # optional

    # methods here

Here LANG_FORM can be a global variable (quasi-constant), or it could be a class variable. Either way, each instance gets basically just a pointer back to its shared info (what you refer to as class variables). It doesn't even strictly need that, if you're willing to index LANG_FORM whenever you need it, rather than memorizing it in each instance.

If you want to have each of the instances depict itself based on its form/kind/quasi-class, add a __repr__ method:

def __repr__(self):
    classname = self.__class__.__name__
    return "{0}-{1}({2!r}, {3})".format(classname, self.form, self.name, self.lang.points)

Then:

p = Protean('CPython', 'Python')

print p

Yields:

Protean-Python('CPython', 84)