Features and Feature Holders
The basic idea behind the Classmod framework is derived from the normal OOP model. This page introduces three new concepts that are essential for understanding the principles of Classmod. Please note that this page only provides documentation for the com.quartercode.classmod.base
package.
Features
In Java, every class can have members of two different types:
- Member Variables
- Member Methods
Please note that static members of a class are not covered by Classmod. The framework entirely on object members.
Classmod emulates all members as features. In the base
package, there is no differentiation between variables and methods. All of these are just called features. The actual functionality of features is implemented by classes that are located in the extra
package (see Properties and Functions). That means that you can extend the framework and add new types of class members that were not possible in Java before.
Features only have two core requirements. They must have a name for recognizing them later on and they must keep track of the feature holder that is using them (see the next section).
So far, features are pretty much just closed objects that serve a specific functionality, like providing a member variable (property) or member method (function):
------------------ ------------------ | Feature: | | Feature: | | | | | | Property | | Function | ------------------ ------------------
Feature Holders
Features are the core concept of Classmod. However, what can we do with these features? There needs to be some kind of "class emulation" that keeps track of all the features the emulated class is using, like a normal Java class knows the members it has. The answer for that is the feature holder.
Feature holders store the features they are using as they want them to be stored. The normal use case is a simple collection. However, the features of a feature holder could even be stored inside a database or an xml file. That makes persistence and serialization very easy.
A feature holder also needs to make the features it is storing available somehow. For that purpose, each feature holder implements a get() method that retrieves a feature by its name. Actually, it's a bit more complicated than, but we'll take care of the exact get() process in the next section.
So far, feature holders are containers that store features. These features can be accessed through the feature holder's get() method. How exactly that works is the topic of the next section.
-------------------------------------------- | | | Feature Holder | | | -------------------- | ::::::: get() <::::::::::::::::::::::::::::::: | Feature User | | : | -------------------- | V | | --------------- --------------- | | | Feature | | Feature | | | --------------- --------------- | | | --------------------------------------------
Feature Definitions
So far we know that there are feature holders and the features they contain can be accessed through the mysterious get() method. However, some problems may pop into your mind when you read this:
- Say I want to retrieve a property through the get(String name) method; do I have to cast the resulting feature object to a property object? Who guarantees type safety?
- What happens when I want to retrieve a property that isn't yet present in the feature holder? How do I even add features to the feature holder?
The answer to these two questions is the feature definition. As the name probably suggests, feature definitions describe features. The idea behind them is that whenever the get() method on feature holder is called with a feature definition describing a feature that isn't yet there, the feature definition object generates the new feature. Moreover, feature definitions provide a generic parameter so you can retrieve an actual property object by using the correct feature definition. For recognizing feature objects inside the feature holder, every feature definition also has a name that applies to every newly created feature.
-------------------------------------------- | | | Feature Holder | | | -------------------------- -------------------- | ::::::: get() <::::::::::::::::::::::::::: | Feature Definition | <:::::::::: | Feature User | | : | -------------------------- -------------------- | V | | --------------- --------------- | | | Feature | | Feature | | | --------------- --------------- | | | --------------------------------------------
Let's quickly recap the function of a feature definition:
- Storing the name of the feature that should be accessed
- Providing type safety during accesss through a generic parameter
- Creating new features for feature holders that do not yet hold a specific feature
Because the feature definition concept is often hard to understand, here's a little bit of non-java pseudocode demostrating the principle:
definition = new FeatureDefinition<Property<String>>() definition.name = "featureName" // Set the name of the feature definition.createMethod = { return new Property<String>() // Method for creating new instances of the feature } property = featureHolder.get(definition) property.value = "Test" property.value = 10 // Compiler error: we retrieved a string property
In conclusion, we can say that feature definitions are helpers for preserving type safety and abstracting the necessary process of creating new features if they don't yet exist.
Feature Definition Pattern
You might ask: Well, feature definitions are a nice thing, but do I really have to create a new definition every time I want do retrieve a feature from a feature holder? The answer to that problem is the feature definition pattern.
It's just a design pattern for storing feature definitions so they don't need to be created for each feature holder request. Since the definition for a feature always is exactly the same we can declare a constant that contains that definition. It's also a good practice to always create own classes for feature holders. These subclasses inherit from the parent feature holder class. You can put your feature definitions there. Here's some more non-java pseudocode demonstrating the pattern:
class Square extends FeatureHolder { constant SIDE_LENGTH; static { SIDE_LENGTH = new FeatureDefinition<Property<Integer>>() SIDE_LENGTH.name = "sideLength" SIDE_LENGTH.createMethod = { return new Property<Integer>() } } }
You can see that the feature definition constant has a valid constant name (uppercase letters and underscores instead of spaces). That's a good practice because it keeps the code clean and should be done instead of something like sideLength
as constant name. You might also observe that we use a static
block for initializing the constant. In actual Java code, that wouldn't be necessary apart from some exceptions, so for the sake of consistency we initialize all of our feature definitions in static blocks.
Default Implementations
You might have noticed that all classes in the com.quartercode.classmod.base
package are interfaces. Classmod provides default implementations of those interfaces in com.quartercode.classmod.base.def
. Here's a little code example on how to use these default implementations:
class Square extends DefaultFeatureHolder {
public static final FeatureDefinition<Property<Integer>> SIDE_LENGTH;
static {
SIDE_LENGTH = new AbstractFeatureDefinition<Property<Integer>>("sideLength") {
public Property<Integer> create(FeatureHolder holder) {
return new Property<Integer>(getName(), holder);
}
}
}
}
class SquareUser {
public static void setSideLengthToOne(Square square) {
square.get(Square.SIDE_LENGTH).set(1F);
}
}
Please note that there's no simple Property
feature (see Properties). However, the other classes exist. DefaultFeatureHolder
implements the get(FeatureDefinition)
method and stores features. AbstractFeatureDefinition
implements the name requirement for feature definitions. And AbstractFeature
(not shown in this example) implements some default feature functionality so it can be used for creating features.