Implements vs Instantiates vs Inheritance: A Best Practices Guide¶
LinkML offers several mechanisms for relating schema elements to one another:
is_a + mixins (inheritance), implements (structural conformance),
and instantiates (metamodel extension). This guide clarifies when and why
to use each one.
Quick Reference¶
Mechanism |
Relationship |
Analogy |
What it governs |
|---|---|---|---|
|
“is a kind of” |
Class inheritance (OOP) |
Slot inheritance, type hierarchy |
|
“also behaves like” |
Traits / interfaces (OOP) |
Slot inheritance without single-parent constraint |
|
“conforms to the structure of” |
Interface implementation |
Declares structural conformance to a template |
|
“is an instance of (at the meta level)” |
Metaclass instantiation |
Governs which annotations are valid on a schema element |
is_a and mixins — Classical Inheritance¶
Use is_a and mixins when your classes form a genuine type hierarchy —
where a child class truly is a kind of the parent.
classes:
NamedThing:
abstract: true
slots:
- id
- name
WithProvenance:
mixin: true
slots:
- created_by
- created_on
Person:
is_a: NamedThing
mixins:
- WithProvenance
slots:
- birth_date
Key properties:
is_aforms a tree — each class has at most oneis_aparent.mixinsform a DAG — a class can mix in multiple mixin classes, avoiding the diamond problem.Slots are inherited.
Personabove inheritsid,name,created_by, andcreated_onwithout redeclaring them.slot_usagerefines inherited slots in the context of the child class (e.g. making an inherited slot required).
When to use: You are modeling a domain ontology where child classes are genuinely subtypes of parent classes. Data validated against the child class should also be valid against the parent class (Liskov substitution).
implements — Structural Conformance¶
Use implements when a class conforms to a structural template without
being a subtype of it. Think of it like implementing an interface or satisfying
a contract.
classes:
FieldObservation:
description: >-
Contract for field observation classes.
Any class that implements this must provide these slots.
slots:
- location
- timestamp
- observer
RadonObservation:
implements:
- geochem_profile:FieldObservation
slots:
- location
- timestamp
- observer
- radon_activity
Key properties:
No slot inheritance. Unlike
is_a,implementsdoes not automatically pull slots into the implementing class. You must declare them yourself.Cross-schema references. The value of
implementsis auriorcurie, so you can reference classes from external schemas without importing them.Documentative intent.
implementsdeclares that your class is intended to conform to a contract. Tooling support for enforcement is evolving.
When to use: You want to declare that your class or slot satisfies a structural contract defined elsewhere, but it is not a subtype of that contract class. This is especially useful when:
The contract comes from an external schema you do not want to import wholesale.
Your class belongs to a different type hierarchy but must meet certain structural requirements.
You are working with schema profiles (see Schema Profiles below).
A slot implements a well-known semantic property (see below).
implements for Semantic Properties¶
implements can also be used at the slot level to declare that a slot
represents a well-known semantic property. For example, to declare that a
name attribute implements rdfs:label:
classes:
OntologyTerm:
attributes:
id:
identifier: true
name:
implements:
- rdfs:label
This is the recommended approach for binding validation in the linkml-term-validator, where it signals which field holds the canonical label for ontology term lookups.
instantiates — Metamodel Extension¶
Use instantiates when a schema element is an instance of a metaclass that
governs what annotations (metadata about the schema element itself) are valid.
This operates at the meta level — it does not affect the data instances, but
rather the schema element’s own metadata.
classes:
RadonObservation:
instantiates:
- geochem_profile:GeochemClass
annotations:
provenance_status: "validated" # governed by GeochemClass
data_steward: "smoxon" # governed by GeochemClass
slots:
- radon_activity
Where GeochemClass is defined as:
# In geochem_profile schema
classes:
GeochemClass:
description: "Any class in a geochem schema must have provenance"
class_uri: geochem_profile:GeochemClass
attributes:
provenance_status:
range: string
required: true
data_steward:
range: string
Key properties:
Governs annotations, not data slots.
instantiatescontrols which annotation tags are valid on the schema element.Metaclass relationship. The instantiated class acts as a metaclass — its slots define the valid annotation keys for the instantiating element.
Works on any schema element. Classes, slots, enums, and even schemas themselves can use
instantiates.Validation is declarative. As of LinkML 1.6+,
instantiatesdeclares intent. Full validation enforcement is under active development.
When to use: You want to constrain or extend the metadata that schema authors must provide on their schema elements (not the data those elements describe).
Putting It All Together¶
Here is a complete example showing all three mechanisms working in concert, adapted from a geochemistry schema profile:
# geochem_profile.yaml — defines rules for geochem schemas
id: https://example.org/geochem-profile/
name: geochem_profile
prefixes:
geochem_profile: https://example.org/geochem-profile/
linkml: https://w3id.org/linkml/
classes:
# Metaclass: governs annotations on classes
GeochemClass:
description: "Any class in a geochem schema must have provenance metadata"
class_uri: geochem_profile:GeochemClass
attributes:
provenance_status:
range: string
required: true
data_steward:
range: string
# Metaclass: governs annotations on slots
GeochemSlot:
description: "Any slot in a geochem schema must declare units"
class_uri: geochem_profile:GeochemSlot
attributes:
unit_ontology_term:
range: uriorcurie
# Structural contract: defines required slots for field observations
FieldObservation:
description: "Contract for field observation classes"
slots:
- location
- timestamp
- observer
Then in a downstream schema:
# radon_schema.yaml — a domain schema that uses the profile
id: https://example.org/radon/
name: radon_schema
prefixes:
geochem_profile: https://example.org/geochem-profile/
classes:
RadonObservation:
# META-LEVEL: "I am an instance of your metaclass"
# → governs which annotations are valid on this class
instantiates:
- geochem_profile:GeochemClass
# STRUCTURAL: "I conform to your structural contract"
# → declares that this class provides the slots required by FieldObservation
implements:
- geochem_profile:FieldObservation
# Annotations governed by instantiates (GeochemClass attributes)
annotations:
provenance_status: "validated"
data_steward: "smoxon"
# Data slots — some from implements contract, some domain-specific
slots:
- location # ← required by FieldObservation contract
- timestamp # ← required by FieldObservation contract
- observer # ← required by FieldObservation contract
- radon_activity # ← domain-specific
slots:
radon_activity:
# META-LEVEL: "I am an instance of your slot metaclass"
instantiates:
- geochem_profile:GeochemSlot
annotations:
unit_ontology_term: "UO:0000134" # governed by GeochemSlot
range: float
location:
range: string
timestamp:
range: datetime
observer:
range: string
Notice how:
instantiatesoperates at the meta level, governing annotations (provenance_status,data_steward,unit_ontology_term).implementsoperates at the structural level, declaring conformance to a slot contract (location,timestamp,observer).Neither
instantiatesnorimplementsinherits slots — that is the job ofis_aandmixins.
Decision Flowchart¶
When deciding which mechanism to use, ask yourself:
Is the child a genuine subtype of the parent? → Use
is_a(andmixinsfor cross-cutting concerns).Does the class need to satisfy a structural contract from another schema? → Use
implements.Do I need to govern what metadata (annotations) schema authors can attach to a schema element? → Use
instantiates.Do I need more than one? → Yes! These mechanisms are complementary. A single class can use
is_a,mixins,implements, andinstantiatessimultaneously.
Schema Profiles¶
The implements and instantiates mechanisms are foundational to the concept
of schema profiles — reusable sets of constraints and conventions that
downstream schemas can adopt.
The linkml-microschema-profile project provides an example of this pattern. It defines a small, generic profile that schema authors can reference to ensure their schemas follow common conventions (e.g. requiring descriptions on all classes, enforcing naming patterns).
Profiles are useful when:
An organization wants to enforce governance rules across many schemas without requiring full schema imports.
Domain-specific conventions (like the geochemistry example above) need to be separated from generic best practices.
You want to layer constraints: a generic profile for all schemas, plus a domain profile for your specific field.
For instance, generic constraints like “all classes must have descriptions”
belong in a base profile, while domain-specific metaclasses like
GeochemClass belong in a domain-specific child profile. See
issue #3282 for discussion on
separating generic from domain-specific profile components.
Common Pitfalls¶
Pitfall 1: Using is_a when you mean implements.
If your class is not genuinely a subtype — it just happens to share some
slots — prefer implements. Misusing is_a creates misleading type
hierarchies.
Pitfall 2: Expecting slot inheritance from implements.
Unlike is_a, implements does not pull in slots. You must declare the
required slots explicitly in your class.
Pitfall 3: Confusing instantiates with implements.
instantiates governs annotations on the schema element itself (metadata
about the class/slot). implements governs the structure of data instances
(which slots the class provides). They operate at different levels.
Pitfall 4: Using instantiates to try to add data slots.
instantiates controls annotation validity, not data structure. If you need
to add data slots, use is_a, mixins, or declare them directly.
Summary¶
Question |
Use |
|---|---|
“Is RadonObservation a kind of Observation?” |
|
“Should RadonObservation also behave like a Locatable thing?” |
|
“Does RadonObservation conform to the FieldObservation contract?” |
|
“What metadata must the RadonObservation class definition carry?” |
|