I) Progressive Adoption and Customization
The framework offers total flexibility in both its deployment strategy and its terminology.
I.1) Step-by-Step Adoption
Integrating Clprolf into a project can be done incrementally. There is no need to enforce every rule from day one.
-
Step 1: Classes First. You can focus exclusively on the core separation between
@ClAgent(orClSystem) and@ClWorker. This allows the team to master the class splitting without initial friction. -
Step 2: Interfaces Later (Optional). The interface model (
ClFamily,ClTrait) can be introduced at a later stage.
If you find the Clprolf interface system too disruptive compared to your habits or traditional OOP practices, you can choose to ignore it entirely. The framework remains fully functional and highly effective just for your classes.
I.2) Tailoring the Terminology
If the default vocabulary of Clprolf does not fit your team's nomenclature, the framework is completely agnostic regarding name choices. Since the ArchUnit checker targets the actual declaration types, a simple automated refactoring (Rename) in your IDE is all it takes to adapt the framework to your company's culture.
Here are a few examples of alternative naming conventions:
| Default Role | Conceptual Alternative | Technical Alternative |
|---|---|---|
@ClAgent |
@ClConcept |
@ClDomain |
@ClWorker |
@ClMechanism |
@ClInfrastructure |
@ClSystem |
@ClBridge |
@ClLowLevel |
II) Interfaces
In Clprolf, interfaces are viewed as:
abstract forms of inheritance.
They therefore participate in the structural continuity of the system.
ClFamily = primary family interface
ClTrait = trait, shared capability between families
ClFree = unrestricted interface
In Clprolf, interfaces are not viewed as simple technical contracts.
Both extends and implements relationships are considered genuine conceptual inheritance relationships.
II.1) ClFamily
An interface representing an abstract family.
Used for:
- polymorphism,
- decoupling,
- implementation variants.
Family interfaces also have a target role:
ClAgent- or
ClWorker
Example
@ClAgent
@ClFamily
public interface Animal {
void eat(int quantity);
}
The hierarchy of ClFamily interfaces naturally reflects the hierarchy of concrete classes.
@ClAgent
@ClFamily
public interface Horse extends Animal {
void jump(int height);
}
Which may lead to:
@ClAgent
public class AnimalImpl implements Animal { (...) }
@ClAgent
public class HorseImpl extends AnimalImpl implements Horse { (...) }
II.2) ClTrait
An interface representing a shared capability across multiple ClFamily.
Traits also use a target role:
ClAgentClWorker
Note: a
@ClTraitmay be annotated with both@ClAgentand@ClWorker.This exception is reserved for genuinely cross-cutting traits that can be used by both agents and workers.
Business Example
@ClAgent
@ClTrait
public interface Payable {
void pay();
}
Technical Example
@ClWorker
@ClTrait
public interface Persistable {
void save();
}
II.3) Illustration of the Interface Family / Implementation Parallel
[ABSTRACT WORLD / INTERFACES] │ [CONCRETE WORLD / CLASSES]
│
@ClAgent @ClFamily │ @ClAgent
interface Animal │ class AnimalImpl
▲ │ ▲
│ (extends) │ │ (extends)
│ │ │
@ClAgent @ClFamily │ @ClAgent
interface Horse │ class HorseImpl
▲ │ ▼ (implements)
│ │ 👉 implements Horse
└───────────────────────┼─────── (and extends AnimalImpl)
(Structural Inheritance)│
│
──────────────────────────────────────┴─────────────────────────────────
👉 THE TRAIT (Cross-cutting):
@ClAgent @ClTrait │
interface Jumpable │
▲ │
│ (inherited by Family) │
│ │
Horse extends Jumpable │
II.4) ClFree
A generic interface without a specific role. It shouldn't be necessary.
Allows flexibility.
Example
@ClFree
public interface ExternalApi {
}
II.5) Using Interfaces
- In Clprolf,
Familyinterfaces closely resemble pure abstract classes.
They are intended to be implemented by one or more future Clprolf classes. Therefore, they have a target role (agent or worker).
A class can only implement a single main Family at a time, and the class role must match the target role of the interface. Clprolf thus uses single implementation for interfaces, in the same way that Java uses single inheritance for classes. Indeed, a Family is always the structural reflection of its implementation. This notably allows for systematic loose coupling.
However, multiple implementation is not removed, but rather shifted to the Family implemented by the class.
This might seem restrictive and unconventional, but it only applies in strict mode, which is not the default. This prevents multiple implementations of the same family from repeating a multi-contract declaration, and helps developers quickly understand what the classes actually implement.
-
Traitinterfaces express a common functionality across multipleFamilyinterfaces.
A Trait therefore represents a cross-cutting trait shared among several families.
Normally, a Trait can only be inherited by a Family interface, and not directly implemented by a class. This prevents a trait from being separated from its family interface, and clarifies class implementations (especially when a family has multiple implementations).
However, this only applies in strict mode, which is not the default.
Concrete class
↓ implements
ClFamily
↓ inherits from
ClTrait
Note: A Family interface can inherit from multiple Family or Trait interfaces.
A Trait interface can only inherit from other Traits, because a trait remains a trait.
Interface inheritance can still be forced using
@ClInterfaceBypassabove the interface (or@ClBypassto force inheritance between different target roles). However, these bypasses should be rare.
II.6) A Real-World Example of ClFamily and ClTrait Interfaces
Let's look at this real-world example, which utilizes a design perfectly applicable to the Clprolf Framework:
package com.tngtech.archunit.lang;
@ClAgent @ClFamily
public interface ArchRule extends CanBeEvaluated, CanOverrideDescription<ArchRule> {
//(…)
}
package com.tngtech.archunit.library;
public final class Architectures {
//(…)
@ClAgent
public static final class LayeredArchitecture implements ArchRule {
// (…)
}
}
In this example,
CanBeEvaluatedandCanOverrideDescriptionact as@ClTraitinterfaces, whileArchRuleformalizes the@ClFamily.
II.7) Note on Clprolf and the Interface Segregation Principle (ISP)
Clprolf inherently respects the ISP; it is simply a matter of adapting the design of your classes and interfaces using the appropriate families and traits:
@ClAgent
@ClTrait
public interface Scanner {
void scan(Document doc);
}
@ClAgent
@ClTrait
public interface Fax {
void fax(Document doc);
}
@ClAgent
@ClTrait
public interface Printer {
void print(Document doc);
}
@ClAgent
@ClFamily
public interface OldPrinter extends Printer {
}
@ClAgent
@ClFamily
public interface ModernPrinter extends OldPrinter, Scanner, Fax {
}
@ClAgent
public class OldPrinterImpl implements OldPrinter {
// (...)
}
@ClAgent
public class ModernPrinterImpl implements ModernPrinter {
// (...)
}
Top comments (0)