Understanding Interfaces: what changed
Revision 1 to 2. Removed lines have a minus sign; added lines have a plus sign.
| Old | New | Line | |
|---|---|---|---|
| 1 | 1 | ## The reason for interfaces | |
| 2 | 2 | ||
| 3 | 3 | Classes that extend other classes can be called subclasses. For example, the bicycle class could be extended to have a mountain sub-class, and a child's bicycle (Monareta) sub-class. They inherit many of the common functions of the bicycle class, but unique options are added as stabilizer wheels for the child's bike. You can call bicycle methods, knowing that they generally apply to all types of bicycle. | |
| 4 | 4 | ||
| 5 | 5 | This is a traditional and standard use for classes, since sub-classes are only variations of a theme. But assuming you had an application where you had bike-class objects, cars, car washes and others and in this application you want each class to have the following method: | |
| 6 | 6 | ||
| 7 | 7 | ``` pascal | |
| 8 | 8 | function isRecyclable : Boolean; | |
| 9 | 9 | ``` | |
| 10 | 10 | ||
| 11 | 11 | This would let you know if the object contains recyclable materials. You could define a high level class that contained this method, and define all the classes derived from it. Or you could simply add the method to each sub-class. Or I could use interfaces. The interfaces would simply tidy up this situation. Defining an interface that groups these methods together, sets a standard for all classes that implement the interface. They make it easier for the programmer to discipline himself in developing the application; All classes have a set of methods that are identical and the compiler insists that all methods in an interface be implemented. | |
| 12 | 12 | ||
| 13 | 13 | Returning to the scenario of the recyclable object, each sub-class inherits common functions from its particular parent class. This describes the basic structure of the sub-class. It also inherits (by implementation) common functions that span all classes. | |
| 14 | 14 | ||
| 15 | 15 | ## Example of an interface | |
| 16 | 16 | ||
| 17 | 17 | Like many ideas, an example is the best way to illustrate the concepts. We will do it in stages to keep things as simple as possible. First, let's define a car class: | |
| 18 | 18 | ||
| 19 | 19 | ``` pascal | |
| 20 | 20 | type | |
| 21 | 21 | // We define our class Car | |
| 22 | 22 | TCar = class | |
| 23 | 23 | ||
| 24 | 24 | private | |
| 25 | 25 | carMake : String; | |
| 26 | 26 | carAge : Byte; | |
| 27 | 27 | ||
| 28 | 28 | public | |
| 29 | 29 | // Car constructor | |
| 30 | 30 | constructor Create(Make : String); | |
| 31 | 31 | ||
| 32 | 32 | published | |
| 33 | 33 | // Car Properties | |
| 34 | 34 | property Make : String read carMake; | |
| 35 | 35 | property Age : Byte read carAge write carAge; | |
| 36 | 36 | end; | |
| 37 | 37 | ``` | |
| 38 | 38 | ||
| 39 | 39 | This class is defined by default based on TObject since we do not specify a base class type. This class has two properties, and a constructor, which are shown here: | |
| 40 | 40 | ||
| 41 | 41 | ``` pascal | |
| 42 | 42 | // Implementation of constructor for the Car class | |
| 43 | 43 | TCar.Create(Make : String); | |
| 44 | 44 | begin | |
| 45 | 45 | // Save the car make and default age | |
| 46 | 46 | carMake := Make; | |
| 47 | 47 | carAge := 0; | |
| 48 | 48 | end; | |
| 49 | 49 | ``` | |
| 50 | 50 | ||
| 51 | 51 | Here we assign the two properties of the car: its make and age. The make is passed as a parameter and we assign a default age of 0. Now we will define another class - a bicycle class: | |
| 52 | 52 | ||
| 53 | 53 | ``` pascal | |
| 54 | 54 | type | |
| 55 | 55 | // We define our class Bicycle | |
| 56 | 56 | TBbicycle = class | |
| 57 | 57 | ||
| 58 | 58 | private | |
| 59 | 59 | cycleMale : Boolean; | |
| 60 | 60 | cycleAge : Byte ; | |
| 61 | 61 | ||
| 62 | 62 | published | |
| 63 | 63 | // Bicycle Properties | |
| 64 | 64 | property isMale: Boolean read cycleIsMale; | |
| 65 | 65 | property Age : Byte read cycleAge write cycleAge; | |
| 66 | 66 | ||
| 67 | 67 | // Bicycle constructor | |
| 68 | 68 | constructor Create(isMale : Boolean; Age : Byte); | |
| 69 | 69 | end; | |
| 70 | 70 | ``` | |
| 71 | 71 | ||
| 72 | 72 | This class has two properties and a constructor as shown here: | |
| 73 | 73 | ||
| 74 | 74 | ``` pascal | |
| 75 | 75 | // Constructor implementation for the bicycle class | |
| 76 | 76 | constructor TBicycle.Create(isMale : Boolean; Age : Byte); | |
| 77 | 77 | begin | |
| 78 | 78 | // Save the passed parameters | |
| 79 | 79 | CycleIsMale := isMale; | |
| 80 | 80 | CycleAge := Age; | |
| 81 | 81 | end; | |
| 82 | 82 | ``` | |
| 83 | 83 | ||
| 84 | 84 | This class is a bit different from the car class, enough to see that we might not have based the car on the bicycle or the bicycle on the car. Now we will define an interface that says if a class object is recyclable: | |
| 85 | 85 | ||
| 86 | 86 | ``` pascal | |
| 87 | 87 | type | |
| 88 | 88 | // An interface definition | |
| 89 | 89 | IRecyclable = Interface (IInterface) | |
| 90 | 90 | ||
| 91 | 91 | // A single function that supports the property | |
| 92 | 92 | function materialIsRecyclable : Boolean; | |
| 93 | 93 | ||
| 94 | 94 | // A single property | |
| 95 | 95 | property isRecyclable : Boolean read materialIsRecyclable ; | |
| 96 | 96 | end; | |
| 97 | 97 | ``` | |
| 98 | 98 | ||
| 99 | 99 | Our interface uses the standard Iinterface definition as the basis. Interface definitions are like class definitions with all abstract elements, so we don't have to declare them as abstract - by default they are. | |
| 100 | 100 | ||
| 101 | 101 | This interface adds a Recyclable property to each class that implements it. Each class that implements it will have to be guaranteed to have exactly the same way of asking if it is recyclable. This is the power and benefit of interfaces - uniformity through potentially very different classes. | |
| 102 | 102 | ||
| 103 | 103 | Any class can implement as many interfaces as it wants - it can conform to any global standard in effect. Note that we must define the property using a function - we cannot declare a Boolean data field in the interface as long as the interfaces do not contain data. | |
| 104 | 104 | ||
| 105 | 105 | Now let's change our classes to support this interface definition: | |
| 106 | 106 | ||
| 107 | 107 | ``` pascal | |
| 108 | 108 | type | |
| 109 | 109 | // We define our class Car | |
| 110 | 110 | TCar = class(TInterfacedObject, IRecyclable) | |
| 111 | 111 | ||
| 112 | 112 | private | |
| 113 | 113 | carMake : String; | |
| 114 | 114 | carAge : Byte; | |
| 115 | 115 | carIsRecyclable : Boolean; // Added to support IRecyclable | |
| 116 | 116 | function materialIsRecyclable : Boolean; // Added for IRecyclable | |
| 117 | 117 | ||
| 118 | 118 | public | |
| 119 | 119 | // Car constructor | |
| 120 | 120 | constructor Create(Make : String); | |
| 121 | 121 | ||
| 122 | 122 | published | |
| 123 | 123 | // Car Properties | |
| 124 | 124 | property Make : String read carMake; | |
| 125 | 125 | property Age : Byte read carAge write carAge; | |
| 126 | 126 | // Added for IRecyclable | |
| 127 | 127 | property isRecyclable : Boolean read materialIsRecyclable; | |
| 128 | 128 | end; | |
| 129 | 129 | ``` | |
| 130 | 130 | ||
| 131 | 131 | Note that we put the function used by the interface property isRecyclable in the private section - we want the property to be used only by the caller. | |
| 132 | 132 | ||
| 133 | 133 | \[Author's note: when compiled, the compiler insists on the presence of the materialIsRecyclable function, but not on the most important part - the property. As the author can see, this does not force the predominant interface option - the property!\] | |
| 134 | 134 | ||
| 135 | 135 | We have now based our class on the TInterfaceObject class, which provides some standard support for the classes that implement interfaces. And we have also based our class on IRecyclable, our new interface. | |
| 136 | 136 | ||
| 137 | 137 | But we must also declare the new materialIsRecyclable function: | |
| 138 | 138 | ||
| 139 | 139 | ``` pascal | |
| 140 | 140 | // Car function required by the isRecyclable attribute | |
| 141 | 141 | function TCar.materialIsRecyclable : Boolean; | |
| 142 | 142 | begin | |
| 143 | 143 | Result : = carIsRecyclable; | |
| 144 | 144 | end; | |
| 145 | 145 | ``` | |
| 146 | 146 | ||
| 147 | 147 | And we must not forget to assign this recyclable value. We will do it crudely here in the constructor: | |
| 148 | 148 | ||
| 149 | 149 | ``` pascal | |
| 150 | 150 | // Constructor implmentation for the car class | |
| 151 | 151 | constructor TCar.Create(Make : String); | |
| 152 | 152 | begin | |
| 153 | 153 | // Save the car brand and assign a default age | |
| 154 | 154 | carName : = Make; | |
| 155 | 155 | carAge : = 0 ; | |
| 156 | 156 | carIsRecyclable : = true; // Assign that it is recyclable | |
| 157 | 157 | end; | |
| 158 | 158 | ``` | |
| 159 | 159 | ||
| 160 | 160 | Wow! But we must do the same for the Bicycle class to show the real effect. We will see the complete code to define and use these classes, which you can copy and paste into the Code Editor: | |
| 161 | 161 | ||
| 162 | 162 | ``` pascal | |
| 163 | 163 | unit Unit1 ; | |
| 164 | 164 | ||
| 165 | 165 | {$ mode objfpc} {$ H +} | |
| 166 | 166 | ||
| 167 | 167 | interface | |
| 168 | 168 | ||
| 169 | 169 | use | |
| 170 | 170 | Classes , SysUtils , FileUtil , Forms , Controls , Graphics , Dialogs ; | |
| 171 | 171 | ||
| 172 | 172 | type | |
| 173 | 173 | // An interface definition | |
| 174 | 174 | IRecyclable = Interface(IInterface) | |
| 175 | 175 | // A single function that supports the property | |
| 176 | 176 | function materialIsRecyclable : Boolean; | |
| 177 | 177 | // A single property | |
| 178 | 178 | property isRecyclable : Boolean read materialIsRecyclable; | |
| 179 | 179 | end; | |
| 180 | 180 | ||
| 181 | 181 | // We define our Car class | |
| 182 | 182 | TCar = class(TInterfacedObject, IRecyclable) | |
| 183 | 183 | ||
| 184 | 184 | private | |
| 185 | 185 | carMake : String ; | |
| 186 | 186 | carAge : Byte ; | |
| 187 | 187 | carIsRecyclable : Boolean ; | |
| 188 | 188 | function materialIsRecyclable : Boolean ; // Added for IRecyclable | |
| 189 | 189 | ||
| 190 | 190 | public | |
| 191 | 191 | // Car constructor | |
| 192 | 192 | constructor Create(Make : String); | |
| 193 | 193 | ||
| 194 | 194 | published | |
| 195 | 195 | // Car Properties | |
| 196 | 196 | property Make : String read carMake; | |
| 197 | 197 | property Age : Byte read carAge write carAge; | |
| 198 | 198 | // Added for IRecyclable | |
| 199 | 199 | property isRecyclable : Boolean read materialIsRecyclable ; | |
| 200 | 200 | end; | |
| 201 | 201 | ||
| 202 | 202 | // We define our Bicycle class | |
| 203 | 203 | TBicycle = class(TInterfacedObject, IRecyclable) | |
| 204 | 204 | ||
| 205 | 205 | private | |
| 206 | 206 | cycleIsMale : Boolean; | |
| 207 | 207 | cycleAge : Byte; | |
| 208 | 208 | function materialIsRecyclable : Boolean; // Added for IRecyclable | |
| 209 | 209 | public | |
| 210 | 210 | // Bicycle constructor | |
| 211 | 211 | constructor Create(isMale : Boolean; numWheels : Byte); | |
| 212 | 212 | ||
| 213 | 213 | published | |
| 214 | 214 | // Bicycle Properties | |
| 215 | 215 | property isMale : Boolean read cycleIsMale; | |
| 216 | 216 | property Age : Byte read cycleAge write cycleAge; | |
| 217 | 217 | // Added for IRecyclable | |
| 218 | 218 | property isRecyclable : Boolean read materialIsRecyclable; | |
| 219 | 219 | end; | |
| 220 | 220 | ||
| 221 | 221 | {TForm1} | |
| 222 | 222 | ||
| 223 | 223 | TForm1 = class (TForm) | |
| 224 | 224 | procedure FormCreate(Sender : TObject); | |
| 225 | 225 | private | |
| 226 | 226 | {private declarations} | |
| 227 | 227 | public | |
| 228 | 228 | {public declarations} | |
| 229 | 229 | end; | |
| 230 | 230 | ||
| 231 | 231 | var | |
| 232 | 232 | Form1 : TForm1; | |
| 233 | 233 | ||
| 234 | 234 | implementation | |
| 235 | 235 | ||
| 236 | 236 | {$R *.lfm} | |
| 237 | 237 | ||
| 238 | 238 | {TForm1} | |
| 239 | 239 | ||
| 240 | 240 | procedure TForm1.FormCreate(Sender : TObject); | |
| 241 | 241 | var | |
| 242 | 242 | cycleMother : TBicycle; | |
| 243 | 243 | carFather : TCar; | |
| 244 | 244 | begin | |
| 245 | 245 | // Instance our bicycle and car objects | |
| 246 | 246 | cycleMother := TBicycle.Create(false, 36); | |
| 247 | 247 | carFather := TCar.Create('Toyota Hilux'); | |
| 248 | 248 | ||
| 249 | 249 | // Ask if each one is recyclable | |
| 250 | 250 | if carFather.isRecyclable then | |
| 251 | 251 | ShowMessage ('Father's car is recyclable') | |
| 252 | 252 | else ShowMessage ('Father's car is not recyclable'); | |
| 253 | 253 | ||
| 254 | 254 | if cycleMother.isRecyclable then | |
| 255 | 255 | ShowMessage ('Mother's bicycle is recyclable') | |
| 256 | 256 | else ShowMessage ('Mother's bicycle is not recyclable'); | |
| 257 | 257 | end; | |
| 258 | 258 | ||
| 259 | 259 | // Implementation constructor for the Car class | |
| 260 | 260 | constructor TCar.Create(Make : String); | |
| 261 | 261 | begin | |
| 262 | 262 | // Save the car make and assign the default age | |
| 263 | 263 | carMake := Make; | |
| 264 | 264 | carAge := 0 ; | |
| 265 | 265 | carIsRecyclable := true // Assign that it is recyclable | |
| 266 | 266 | end; | |
| 267 | 267 | ||
| 268 | 268 | // Car function required for the isRecyclable attribute | |
| 269 | 269 | function TCar.materialIsRecyclable : Boolean; | |
| 270 | 270 | begin | |
| 271 | 271 | Result := carIsRecyclable; | |
| 272 | 272 | end; | |
| 273 | 273 | ||
| 274 | 274 | // Implementation constructor for the bicycle class | |
| 275 | 275 | constructor TBicycle.Create(isMale : Boolean; numWheels : Byte); | |
| 276 | 276 | begin | |
| 277 | 277 | // Save the parameters that were passed | |
| 278 | 278 | cycleIsMale : = isMale; | |
| 279 | 279 | cycleAge : = Age; | |
| 280 | 280 | end; | |
| 281 | 281 | ||
| 282 | 282 | // Bicycle function required for isRecyclable | |
| 283 | 283 | Function TBicycle.materialIsRecyclable : Boolean; | |
| 284 | 284 | begin | |
| 285 | 285 | // We will assume that only Male bicycles are recyclable | |
| 286 | 286 | Result := self.isMale; | |
| 287 | 287 | end; | |
| 288 | 288 | end. | |
| 289 | 289 | ``` | |
| 290 | 290 | ||
| 291 | 291 | When executing ... the ShowMessage function shows the following: | |
| 292 | 292 | ||
| 293 | - | ||
| 293 | + | ```text | |
| 294 | + | Father's car is recyclable Mother's bicycle is not recyclable | |
| 295 | + | ``` | |
| 294 | 296 | ||
| 295 | 297 | This (originally Spanish) post was taken from an Internet article that I found very interesting to modify and understand the interfaces. It has always been a taboo topic to ask and when they are used, it can give your code more clarity. | |
| 296 | 298 | ||
| 297 | 299 | ## See also | |
| 298 | 300 | ||
| 299 | 301 | - [[Interfaces]] | |
| 300 | 302 | - [[How To Use Interfaces]] | |
| 301 | 303 | - [[Accessing the Interfaces directly]] | |
| 302 | 304 | ||
| 303 | 305 | --- | |
| 304 | 306 | *From the Free Pascal wiki, page "Understanding Interfaces", as it stood on 2021-11-26 after a change by Nummer8, out of the wiki's dump of 2024-05-26. Copyright stays with the author who wrote it, according to the old wiki's [[Licensing]] page. If you have concerns about this material, [message the admin](/wiki-contact).* |
