The Liskov Substitution Principle is a fundamental principle in object-oriented programming that defines the behavior of subtypes in relation to their base types. It was introduced by Barbara Liskov in 1987 and is an essential principle for ensuring the correctness and maintainability of object-oriented systems. and was originally defined as follows:
Let q(x) be a provable property of objects x of type T. Then q(y) should be provable for objects y of type S. Where S is a subtype of T.
The Liskov substitution principle defines the following:
- It must be possible to use an object of a derived class instead of an object of the base class without causing errors or unexpected behavior.
- The derived class must be replaceable by the base class without affecting the integrity of the program.
In other words, if a class B inherits from a class A, then class B must be able to be used anywhere an instance of class A is expected, without requiring modification to the code that uses that class A.
Table of contents
Open Table of contents
How to apply the principle
The Liskov substitution principle can also be applied to the design of a Digital Twin. A Digital Twin is a virtual representation of a physical object or system in real-time. By applying the Liskov substitution principle in this context, we seek to ensure that Digital Twins are substitutable and consistent in their behavior with the physical objects they represent.
Implementation of this principle ensures that Digital Twins is replaceable in the system without affecting the behavior or integrity of the system. This is especially important in applications where Digital Twins are used to simulating and control complex systems, such as in manufacturing or infrastructure management, where consistency and coherence between Digital Twins and physical objects are critical for safe and efficient operations.
Some guidelines for applying the Liskov substitution principle in a Digital Twin:
- If inheritance is used in the implementation of the Digital Twins, make sure that the derived Digital Twins adhere to the contract established by the base Digital Twin. This means that the methods and properties of derived Digital Twins must have the same meaning and behavior as those of the base Digital Twin.
- If instead of inheritance a common interface is used for Digital Twins, make sure that all Digital Twins implement the interface consistently. This implies that the methods defined in the interface must have the same purpose and behavior in all Digital Twins that implement it.
- A Digital Twin must be able to receive and process the same types of data and events as the physical objects they represent. In this way, when a physical object is replaced by its corresponding Digital Twin, the system should not have problems providing the necessary data for its correct operation.
Building a DTDL example appliying Liskov principle
DTDL (Digital Twins Definition Language) is a definition language used to describe models of Digital Twins. DTDL is a standard developed by Microsoft and is used to define the structure, attributes, and behavior of digital twins.
Suppose we are building an Industrial plant management system and we want to model Digital Twins for different types of assets used in industrial automation. We have a base model called “IAsset” that defines the basic properties and behavior of a generic industrial asset. Next, we create two derived models, “Machine” and “Robot”, which represent different industrial models. Each derived model inherits the properties and behavior defined in the base model.
Base model: IAsset
{
"@context": "dtmi:dtdl:context;2",
"@id": "dtmi:com:example:IAsset;1",
"@type": "Interface",
"displayName": "IAsset",
"contents": [
{
"@type": "Property",
"name": "status",
"schema": "string"
},
{
"@type": "Command",
"name": "start"
},
{
"@type": "Command",
"name": "stop"
},
{
"@type": "Property",
"name": "powerConsumption",
"schema": "double"
}
]
}
Derivated models: Machine and Robot
{
"@context": "dtmi:dtdl:context;2",
"@id": "dtmi:com:example:Machine;1",
"@type": "Interface",
"displayName": "Machine",
"extends": "dtmi:com:example:IAsset;1"
}
{
"@context": "dtmi:dtdl:context;2",
"@id": "dtmi:com:example:Robot;1",
"@type": "Interface",
"displayName": "Robot",
"extends": "dtmi:com:example:IAsset;1"
}
Testing Liskov principle in DTDL
You can check this simple example in my github of testing DTDL interfaces to ensure compliance with the Liskov principle. By running the tests, you can verify that the derived interface does not violate the principle and that it does not introduce new properties, promoting interoperability and code reusability in the context of Industry 4.0.
describe("Machine - Liskov Principle", () => {
let machineModel;
let iAsset;
beforeAll(async () => {
const models = await getAllModels();
machineModel = models["dtmi:com:example:Machine;1"];
iAsset = models["dtmi:com:example:IAsset;1"];
});
it("Interface should exists", () => {
expect(machineModel.sourceObject["@type"]).toBe("Interface");
});
it("Should extend IAsset Base Model", () => {
const iAsset = machineModel.extends.filter(
id => id === "dtmi:com:example:IAsset;1"
);
expect(iAsset).toBeDefined();
});
it("Verifies does not have new properties", () => {
const machineProperties = Object.keys(machineModel.contents);
const iAssetProperties = Object.keys(iAsset.contents);
const newProperties = machineProperties.filter(
property => !iAssetProperties.includes(property)
);
expect(newProperties.length).toBe(0);
});
});
The test results will be displayed in the terminal/console, indicating whether the DTDL models comply with the Liskov principle and the absence of new properties in the derived interface.