Instantiations of the Object Pattern
In this post:
- An elaborate piss-take of some kind.
- Legitimate thoughts.
Working on Homunculus and the new script for publishing this blog, I ran across two different "classless" uses of the object pattern in Python. To be specific about what the object pattern is:
- Pattern Name and Classification
- Object
- Intent
- Given (only) a cohesive collection of program state, have a means of obtaining functions that alter their behavior based on the state, or alter the state.
- Also Known As
- Traits and typeclasses, probably.
- Motivation (Forces)
- A collection of variables or a data structure can provide all of the data needed for some computational task, but does not afford any particular means of interacting with the data. It cannot enforce invariants, and if different collections of data could be put to the same purpose, consumers of the data must be aware of which kind of collection they are acting on, to use the proper functions. By providing a means to obtain or create the specific functions meant for a collection of state, that collection of state assumes the responsibility of maintaining invariants and implementing behavior; all callers have merely to use the object protocols to obtain the desired behavior.
- Applicability
In some languages, the use of this pattern is basically mandatory to accomplish anything. In freer contexts, the criteria for applicability are:
- There are multiple underlying implementations that could conceivably be provided to the same consuming code, OR at least one implementation involves state subject to invariants.
- This form of dispatch is actually supported by the language.
- Structure
A survey of some possible implementations:
- Module structure:
- Closure structure:
- Class structure:
- Module structure:
- Participants
- Module structure:
- Module
- Values
- Functions
- Closure structure:
- Function scope
- Values
- Closures
- Class structure:
- Class
- Instance
- Values
- Methods
- Module structure:
- Collaboration
- Module structure: The module contains values and functions. The functions are scoped to or passed the module, which allows them to read and mutate the variables.
- Closure structure: The closures close over the function scope, which contains the values. This allows them to read and mutate the variables.
- Class structure: The object is an instance of the class. The class exposes methods, which define the interface to the object's state.
- Consequences
- Module structure: All data is directly accessible.
- Closure structure: To be accessible, data must be explicitly exposed through closures.
- Class structure: The language can provide privacy controls.
- Implementation
- The language support for these patterns varies considerably. Some languages support all versions, and some support none, while others are in-between. The module structure is possible to implement "by accident", and may be a candidate for refactoring into another model.
- Sample Code
- Module structure:
1 2 3 4 5 6 7 8 9 10 11 12 13
"""An object-shaped module.""" _SECRET_LIST = [] def put_to_list(item): """A method-shaped function.""" _SECRET_LIST.append(item) def pop_from_list(): """Another method-shaped function.""" return _SECRET_LIST.pop(0) # Use by importing the module.
- Closure structure:
def constructor(): """A constructor-shaped function.""" a = 0 def increment(): """A method-shaped function. Increment a and return its value.""" nonlocal a a += 1 return a return increment # Usage increment1 = constructor() increment2 = constructor() assert increment1() == 1 assert increment1() == 2 assert increment2() == 1
- Class structure:
class Class: """A class.""" def __init__(self, secret): self._secret = secret @property def secret(self): """An accessor function/method.""" return self._secret assert Class(1).secret == 1
- Known Uses
- Like... any bit of Python code, technically? Rust does this in a statically-dispatched fashion.
- Related Patterns
- ... All of them?
Where is the lie?
Anyway, point is, Homunculus's main loop should be an "update" function or several, a method on some kind of "Game" object.
Meanwhile, the "blog.py" script is really showing its roots. I need to divide the global variables up by problem domain, and make classes for stuff like Mercurial repositories, Pelican projects, Neocities sites, and LDoc.