10 — EXTENDS
Inheritance — a base machine plus the factory options, and the depth that kills you.
Estimated 24 minutes.
The base machine and the factory options
Every machine builder has a base model and an options list. The same 4 kW motor skid leaves the factory as a plain direct-on-line starter, or with a drive, or with a holding brake, or with an encoder. The base machine is unchanged in all of them — the options are bolted on top. EXTENDS is that idea in code: a new function block type that is the old one, plus something.
Build a machine from the options list
Tick options on the order form. Watch the hardware appear on the skid, and watch the block face fill up with pins you never declared.
Base and derived, in both languages
One line of declaration bought seven pins. That is the whole sales pitch for inheritance, and it is a real one.
What EXTENDS actually gives the derived block
FUNCTION_BLOCK FB_VfdMotor EXTENDS FB_Motor means the derived type contains every variable of the base — inputs, outputs and private VAR alike — plus every method and property, plus whatever it declares itself. An instance of FB_VfdMotor is one block of instance memory with the base's members at the front of it. You do not redeclare a single inherited name, and you cannot remove one.
What is in the instance?
FB_Motor has 4 inputs, 3 outputs and 1 private VAR. FB_VfdMotor EXTENDS FB_Motor and adds 1 input and 2 outputs. You declare M450 : FB_VfdMotor. How many members does M450 have?
- 3 — only what FB_VfdMotor declared
- 11 — everything from both, private VAR included
- 10 — everything except the base's private VAR
- 8 — the base only; the new pins live in a second instance
What runs when you call a derived instance
This is the part people guess wrong. On CODESYS and TwinCAT — the toolchains where EXTENDS actually exists — calling an instance of a derived block runs the derived body, and the base body runs only where you write SUPER^(). It is a call you make, at a position you choose. Other implementations of the 3rd edition have made other choices, so treat automatic base execution as something to verify on your target, never to assume.
Move the SUPER^ call and watch the scan change
One scan of an extruder drive with an earth fault on the motor. The base body latches the fault; the derived body ramps the speed. Where you put SUPER^() decides which one wins.
Overriding a method, and reaching back up
An override with the same name and signature takes over completely. SUPER^.MethodName() is how you keep the base behaviour and add to it instead of replacing it.
The reset that does nothing
FB_Motor.Reset clears Fault. FB_VfdMotor overrides Reset to zero the speed reference, and does not call SUPER^.Reset(). An operator presses reset on the extruder. What happens?
- Both run — the base method is called automatically first
- The speed zeroes and Fault stays latched
- The compiler refuses the override without SUPER^
- Fault clears; the override only adds behaviour
ABSTRACT, FINAL and the access specifiers
ABSTRACT says "this exists only to be extended"; FINAL says "this line stops here". Both are worth using on the day you write the base, because nobody adds them later.
The one hierarchy that earns its keep
There is a shape that works, and most plants converge on it: a base FB_Device holding the things every device on the plant genuinely shares — a fault word, a reset, a status for the HMI, a mode — extended once by FB_Valve, FB_Motor and FB_Analog. One level deep, one obvious IS-A, and a change at the top is a change every device on the plant actually wants.
Edit the base and watch the plant
Three ordinary change requests against FB_Device. Four machines derive from it, one of them two levels down. Apply an edit, then click a machine to see what really happened to it. Everything here is reversible.
The fragile base class, on a line that never stops
The base class problem in software is an inconvenience: something recompiles and a test goes red. On a plant it is different. The base is deployed on machines in four buildings, on firmware you did not choose, with overrides written by contractors who left in 2021, and the thing that reveals the change is a night shift. The rule that follows is not "never inherit" — it is that the depth of a hierarchy is a liability you pay for every time you touch the top of it.
Which change is the dangerous one?
You must make one of these four changes to a base block that 300 machines derive from. Which one is most likely to cause a fault at 3 a.m. rather than at compile time?
- Rename a public member of the base
- Add a new VAR_OUTPUT to the base
- Change the default value of an existing base variable
- Delete a method the base no longer uses
The same block, composed instead of derived
Composition costs you the copy-out lines. It buys you a base you can change without wondering who overrode what.
Run the composed machine on the real engine
The extruder drive and the feed pump, executed scan by scan by the in-browser ST engine. Same behaviour the EXTENDS version would give you, with the base held as a member you can point at.
Inherit for an IS-A that will never change
The working rule is short. Inherit when the derived thing genuinely IS the base thing, forever — a valve IS a device — and when the base is small, stable and owned by you. Compose for everything else: options, capabilities, vendor differences, anything that might be true of a machine today and false next year. Lesson 12 develops the composition side properly; this is the sentence that decides which lesson you need.
Inherit or compose?
A machine builder ships conveyors. Some have a weigh cell, some have a metal detector, some have both, some have neither, and marketing adds a new option most years. How should the conveyor block be built?
- FB_Conveyor extended once per option combination
- One FB_Conveyor holding optional instances, enabled per machine
- A five-level hierarchy, one level per option
- One FB_Conveyor with every option's code inside an IF
What you learned
- FUNCTION_BLOCK B EXTENDS A gives B every variable, method and property of A, private VAR included.
- On CODESYS-family compilers the base body runs only where you write SUPER^(); treat automatic execution as vendor-specific until you have checked.
- An override replaces the base method — SUPER^.MethodName() is how you extend it instead.
- ABSTRACT marks a base that exists only to be extended; FINAL stops the chain. Both are worth setting on day one.
- The hierarchy that pays is one level deep: FB_Device extended by FB_Valve, FB_Motor and FB_Analog.
- A renamed or deleted member fails to compile; a changed default does not, and that is the change that finds you at 3 a.m.
- Inherit for an IS-A that will never change. Compose for options, capabilities and anything a salesperson can add.
EXTENDS — done
- You can read and write a derived function block, and place SUPER^ deliberately rather than by habit.
- You can predict which base-class edits the compiler will catch and which ones will reach the plant floor.
- You can argue the inherit-or-compose decision on a specific machine, with a specific failure mode in mind.