Hi Lex,
Here’s my brief explanations.
Classes & Objects
An analogy for a class is a house design/plans and for objects the actual physical houses. The plans (class) just describe what properties the house has (dimensions, colours, materials, …) and inherent in that the operation (the garage door starts in the closed state, when the Brivis heater is turned on the element heats up when the temp is low and stops when it reaches the set temp, etc…). With the plans (class) only - without a physical house (object) nothing is happening. You can build many houses (objects) from the same plans (class). Each house (object) is independent and has features that can be in different states (garage door open/closed, heaters on/off and set to different temps, …) because it has its own values for each variable declared in the class.
So the class doesn’t tell the compiler to do anything (I’m ignoring class variables and class constructors - different to instance variables and regular constructors) - it just has details about how much memory to allocate if you were to create an object, the memory offsets and types of the variables, and the executable instructions for the procedures and functions and their memory addresses. At the point where you create an object from the class the compiler generates code to allocate the memory to store the variables for that instance, does some initialisation, calls the constructor, and saves the address of that memory to the variable to specified. When you call a method on a specific object it operates on that instance only (the procedures/functions know which set of variables to use for that object instance). The other objects are not affected.
You might create a class for the heater, and through composition define a variable of type heater in the house class. When the house object is created you create the heater object (e.g. in the house constructor). In that way each heater within each house is an independent object (as above - different on/off/set temp etc). You might also use that heater class elsewhere, like within a car class.
Interfaces
These are a bit like a blueprint of the procedures, functions and properties (but not variables - properties must use get/set methods) for classes, and can be shared across different classes. Using them with your own classes is optional. A simple analogy would be a temperature control interface with methods like TurnOn/TurnOff/SetTemperature. The heater class could implement that interface (which means the class defines the methods specified in the interface and implements their behaviour specific to the heater):
ITempControl = interface
// There is no implementation of these
procedure TurnOn;
procedure TurnOff;
procedure SetTemp(const Temp: Double);
end;
THeater = class(TObject, ITempControl)
public
// This class implements these methods
procedure TurnOn;
procedure TurnOff;
procedure SetTemp(const Temp: Double);
end;
You could also create a TAirCon class that implements the same temperature control interface, with different behaviour (maybe the aircon has a start-up period before the fan turns on). Then you could control the heater or aircon objects using the methods defined through the interface. e.g. Have a generic temperature control UI with an on/off checkbox and a set temp edit box and button. You pass the temp control interface of either the heater or the aircon object to the form - it doesn’t know anything about heaters or aircons, only the temp control interface. The user can turn the device on and set the temp and the form just calls the methods of the interface.
var
device: ITempControl;
tempForm: TTempControlForm;
begin
device := THeater.Create; // or TAirCon.Create
tempForm := TTempControlForm.Create(device);
Just like you can omit the ancestor TObject
when defining the class, you can omit the IInterface ancestor when defining an interface, or specify it explicitly like IDesignerNotify = interface(IInterface)
.
Interfaces feature reference counting. This can be used to automatically free an object instance when the interface variable goes out of scope. Instead of declaring a variabe using the class type you declare it using (one of) the interface type that the class implements. The THeater class can descend from TInterfacedObject instead of TObject to get automatic free, and implement ITempControl. You create the object in the same way but the instance must be assigned to an interface variable. When that variable goes out of scope (e.g. the procedure that it is defined within returns) the object is automatically freed - you don’t call variable.Free.
var
device: ITempControl;
heater: THeater;
begin
device := THeater.Create;
heater := THeater.Create;
// The heater object must be manually freed:
heater.Free;
// When device goes out of scope (function returns) its THeater object is auto freed
The GUID uniquely identifies the interface and is only required if you use COM interop, which is unlikely.
Class Type
TComponentClass = class of TComponent
defines an ancestor class type. It is all classes of type TComponent and all descendant classes of TComponent. Normally when you create an object you specify the class type, such as TButton
in the code button := TButton.Create
. There are cases where the code creating the object doesn’t know the class to create. Because TButton
and other controls descend from TComponent
(indirectly), you can do something like this:
var
componentClass: TComponentClass;
component: TComponent;
begin
if <something> then
componentClass := TButton
else
componentClass := TCheckbox;
// Later - we can create any TComponent
component := componentClass.Create(...);
In a real app that componentClass.Create()
is likely in some other part of your code that doesn’t need to know the logic behind which component needs to be created, it’s just a generic component factory. This helps to separate the rules around which object to create from the actual creation (which in this case could be general rules about associating the component with a form, default params, loading/saving the property values, …).
I hope that helps.