The basic idea of the Interface Segregation Principle (ISP) has strong similarities with the Single Responsibility Principle (SRP): Modules with too many responsibilities can negatively influence the maintenance and maintainability of a software system. The Interface Segregation Principle (ISP) focuses on the module’s interface. A module should implement only those interfaces that are needed for its task. The following shows how this design principle can be implemented.
In the last post (IEC 61131-3: SOLID – The Liskov Substitution Principle), the example was extended by another lamp type (FB_LampSetDirectDALI). The special feature of this lamp type is the scaling of the output value. While the other lamp types output 0-100%, the new lamp type outputs a value from 0 to 254.
Just like all other lamp types, the new lamp type (DALI lamp) has an adapter (FB_LampSetDirectDALIAdapter). The adapters have been added during the implementation of the Single Responsibility Principle (SRP) and ensure that the function blocks of the individual lamp types are only responsible for a single function (see IEC 61131-3: SOLID – The Single Responsibility Principle).
The sample program was last adapted so that the output value from the new lamp type (FB_LampSetDirectDALI) is scaled within the adapter from 0-254 to 0-100 %. This makes the DALI lamp behave exactly like the other lamp types without violating the Liskov Substitution Principle (LSP).
This will serve as a starting point for explaining the Interface Segregation Principle (ISP).
Extension of the implementation
Also this time, the application has to be extended. However, it is not a new lamp type that is defined, but an existing lamp type is extended by a functionality. The DALI lamp should be able to count the operating hours. For this purpose, the function block FB_LampSetDirectDALI is extended by the property nOperatingTime.
PROPERTY PUBLIC nOperatingTime : DINT
The setter can be used to set the operating hours counter to any value, while the getter returns the current state of the operating hours counter.
Since FB_Controller represents the individual lamp types, this function block is also extended by nOperatingTime.
The operating hours are recorded in the FB_LampSetDirectDALI function block. If the output value is > 0, the operating hours counter is incremented by 1 every second:
IF (nLightLevel > 0) THEN tonDelay(IN := TRUE, PT := T#1S); IF (tonDelay.Q) THEN tonDelay(IN := FALSE); _nOperatingTime := _nOperatingTime + 1; END_IF ELSE tonDelay(IN := FALSE); END_IF
The variable _nOperatingTime is the backing variable for the new property nOperatingTime and is declared in the function block.
What possibilities are there to transfer the value of nOperatingTime from FB_LampSetDirectDALI to the property nOperatingTime of FB_Controller? Here, too, there are now various approaches of integrating the required extension into the given software structure.
Approach 1: Extension of I_Lamp
The property for the new feature is integrated into the I_Lamp interface. Thus, the abstract function block FB_Lamp also receives the nOperatingTime property. Since all adapters inherit from FB_Lamp, the adapters of all lamp types receive this property, regardless of whether the lamp type supports an operating hours counter or not.
The getter and the setter of nOperatingTime in FB_Controller can thus directly access nOperatingTime of the individual adapters of the lamp types. The getter of FB_Lamp (abstract function block from which all adapters inherit) returns the value -1. The absence of the operating hours counter can thus be detected.
IF (fbController.nOperatingTime >= 0) THEN nOperatingTime := fbController.nOperatingTime; ELSE // service not supported END_IF
Since FB_LampSetDirectDALI supports the operating hours counter, the adapter (FB_LampSetDirectDALIAdapter) overwrites the nOperatingTime property. The getter and the setter from the adapter access nOperatingTime from FB_LampSetDirectDALI. In this way, the value of the operating hours counter is passed on to FB_Controller.
Sample 1 (TwinCAT 3.1.4024) on GitHub
This approach implements the feature as desired. Also, none of the SOLID principles shown so far are violated.
However, the central interface I_Lamp is extended only to add another feature for one lamp type. All other adapters of the lamp types, even those that do not support the new feature, also receive the nOperatingTime property via the abstract base FB_Lamp.
With each feature that is added in this way, the interface I_Lamp increases and so does the abstract base FB_Lamp.
Approach 2: Additional Interface
In this approach, the I_Lamp interface is not extended, but a new interface (I_OperatingTime) is added for the desired functionality. I_OperatingTime contains only the property necessary for providing the operating hours counter:
PROPERTY PUBLIC nOperatingTime : DINT
This interface is implemented by the adapter FB_LampSetDirectDALIAdapter.
FUNCTION_BLOCK PUBLIC FB_LampSetDirectDALIAdapter EXTENDS FB_Lamp IMPLEMENTS I_OperatingTime
Thus, FB_LampSetDirectDALIAdapter receives the property nOperationTime not via FB_Lamp or I_Lamp, but via the new interface I_OperatingTime.
If FB_Controller accesses the active lamp type in the getter of nOperationTime, it is checked before the access whether the selected lamp type implements the I_OperatingTime interface. If this is the case, the property is accessed via I_OperatingTime. If the lamp type does not implement the interface, -1 is returned.
VAR ipOperatingTime : I_OperatingTime; END_VAR IF (__ISVALIDREF(_refActiveLamp)) THEN IF (__QUERYINTERFACE(_refActiveLamp, ipOperatingTime)) THEN nOperatingTime := ipOperatingTime.nOperatingTime; ELSE nOperatingTime := -1; // service not supported END_IF END_IF
The setter of nOperationTime is structured similarly. After the successful check whether I_OperatingTime is implemented by the active lamp, the property is accessed via the interface.
VAR ipOperatingTime : I_OperatingTime; END_VAR IF (__ISVALIDREF(_refActiveLamp)) THEN IF (__QUERYINTERFACE(_refActiveLamp, ipOperatingTime)) THEN ipOperatingTime.nOperatingTime := nOperatingTime; END_IF END_IF
Sample 2 (TwinCAT 3.1.4024) on GitHub
The use of a separate interface for the additional feature corresponds to the ‘optionality’ from IEC 61131-3: SOLID – The Liskov Substitution Principle. In the above example, it can be checked at runtime of the program (with __QUERYINTERFACE()) whether a specific interface is implemented and thus the respective feature is supported. Further features, like bIsDALIDevice from the ‘Optionality’ example, are not necessary with this solution approach.
If a separate interface is offered for each feature or functionality, other lamp types can also implement this in order to implement the desired feature. If FB_LampSetDirect also has to receive an operating hours counter, FB_LampSetDirect must be extended by the property nOperatingTime. In addition, FB_LampSetDirectAdapter must implement the I_OperatingTime interface. All other function blocks, including FB_Controller, remain unchanged.
If the functionality of the operating hours counter changes and I_OperatingTime receives additional methods, only the function blocks that also support the feature must be adapted.
Examples of the Interface Segregation Principle (ISP) can also be found in .NET. For example, .NET has the interface IList. This interface contains methods and properties for creating, modifying and reading listings. However, depending on the use case, it may be sufficient for the user to only read a listing. However, passing a listing through IList in this case would also provide methods to modify the listing. One can use the IReadOnlyList interface for these use cases. With this interface, a listing can only be read. Accidental modification of the data is therefore not possible.
Dividing functionalities into individual interfaces thus increases not only the maintainability but also the security of a software system.
The definition of the Interface Segregation Principle
This brings us to the definition of the Interface Segregation Principle (ISP):
A module that uses an interface should be presented with only those methods that the interface really needs.
Or to put it another way:
Clients should not be forced to depend on methods they do not need.
A common argument against the Interface Segregation Principle (ISP) is the increased number of interfaces. A software design can still be adapted at any time during its development cycles. So, if you feel that an interface contains too many functionalities, check whether segregation is possible. Of course, overengineering should always be avoided. A certain amount of experience can be helpful here.
Abstract function blocks also represent an interface (see FB_Lamp). An abstract function block can contain basic functions to which the user only adds the necessary details. It is not necessary to implement all the methods or properties yourself. Here also it is important not to burden the user with technicalities which are not necessary for his tasks. The set of abstract methods and properties should be as small as possible.
Adherence to the Interface Segregation Principle (ISP) keeps interfaces between functional blocks as small as possible, reducing coupling between each functional block.
If a software system has to cover further performance features, reflect the new requirements and do not hastily extend existing interfaces. Check whether separate interfaces are not a better decision. The reward is a software system that is easier to maintain, to test and to extend.
In the last pending part, the Open/Closed Principle (OCP) will be explained in more detail.
5 thoughts on “IEC 61131-3: SOLID – The Interface Segregation Principle”
Hello, thank you for your work, waiting for more publications…😉
In my free time I am also developing OOP IEC61131-3 documentation and videos, you are welcome if you want to contribute:
move the documentation to other languages to make it available in other languages, etc…
If you give me your permission I will add your links, so that they are part of the documentation…
You are welcome to link to my blog.
Stefan, thank you for the knowledge you’ve shared. Really great stuff.
Thank you very much.