How to start creating custom classes using Delphi?

I posted a question a few days ago and the answers told me to create my own classes.

I am a programmer from an old school from pre-op days, my programs are well-structured, efficient and organized, but do not have any custom OOPing other than using Delphi and third-party objects.

I looked at how Delphi's object-oriented classes worked when I started using Delphi 2, but they seemed alien to me for my programming. I understand how they were and are great for developers to develop components and visually control the user interface. But I never found the need to use them in coding my program itself.

So now I look again, 15 years later, in the Delphi and OOPing classes. If I take, for example, the structure that I have, for example:

type TPeopleIncluded = record IndiPtr: pointer; Relationship: string; end; var PeopleIncluded: TList<TPeopleIncluded>; 

Then the OOP advocate will probably tell me to make it a class. Logically, I would think that this would be a class inherited from a common TList. I would suggest that this would be done as follows:

 TPeopleIncluded<T: class> = class(TList<T>) 

But where I get stuck, and I don't have good instructions on how to do the rest.

When I look at some class that Delphi has as an example in the Generics.Collections module, I see:

 TObjectList<T: class> = class(TList<T>) private FOwnsObjects: Boolean; protected procedure Notify(const Value: T; Action: TCollectionNotification); override; public constructor Create(AOwnsObjects: Boolean = True); overload; constructor Create(const AComparer: IComparer<T>; AOwnsObjects: Boolean = True); overload; constructor Create(Collection: TEnumerable<T>; AOwnsObjects: Boolean = True); overload; property OwnsObjects: Boolean read FOwnsObjects write FOwnsObjects; end; 

and then their definitions of constructors and procedures:

 { TObjectList<T> } constructor TObjectList<T>.Create(AOwnsObjects: Boolean); begin inherited; FOwnsObjects := AOwnsObjects; end; constructor TObjectList<T>.Create(const AComparer: IComparer<T>; AOwnsObjects: Boolean); begin inherited Create(AComparer); FOwnsObjects := AOwnsObjects; end; constructor TObjectList<T>.Create(Collection: TEnumerable<T>; AOwnsObjects: Boolean); begin inherited Create(Collection); FOwnsObjects := AOwnsObjects; end; procedure TObjectList<T>.Notify(const Value: T; Action: TCollectionNotification); begin inherited; if OwnsObjects and (Action = cnRemoved) then Value.Free; end; 

Let me tell you that this “simple” class definition may be obvious to those of you who have used OOP in Delphi for many years, but for me it provides me with hundreds of unanswered questions about what I use and how I use it.

It seems to me that this is not a science. This seems to be the art of how to best structure your information in objects.

So this question, and I hope it does not close, because I really need help with this, where and how can I get the best instruction on using Delphi to create classes - and how to do it in the proper way Delphi,

+4
source share
1 answer

To me this does not seem like science. It seems to be the art of how to best structure your information in objects.

Good, yes. There really aren't many formal requirements. This is really just a set of tools to help you organize your ideas and eliminate a lot of duplication along the way.

Then the OOP advocate will probably tell me to make it a class. Logically, I would think that this would be a class inherited from a common TList.

In fact, the common point of universal containers is that you do not need to create a new container class for each type of object. Instead, you will create a new content class, and then create a TList<TWhatever> .

Remember the class instance as a pointer to a record.

Now: why use a class if you can use a pointer to a record? A couple of reasons:

  • encapsulation . You can hide some aspects of the implementation using the private keyword so that other developers (including your future) know that they are not dependent on implementation details that may change or that it’s just not important to understand the concept.
  • polymorphism . You can avoid a lot of special sending logic by providing each of your records with a set of function pointers. Then, instead of having a large case where you do different things for each type of object, you look at your list and send each object the same message, then it follows the function pointer to decide what to do.
  • Inheritance When you start making entries with pointers to functions and procedures, you will find that you often have cases when you need a new function manager entry that is very similar to the one you already have, except you need to change one or two procedures . A subclass is just a convenient way to do this.

So, in your other post, you indicated that your general program is as follows:

 procedure PrintIndiEntry(JumpID: string); var PeopleIncluded : TList<...>; begin PeopleIncluded := result_of_some_loop; DoSomeProcess(PeopleIncluded); end; 

It’s not clear to me what Indi or JumpID , so I’ll pretend that your company does parachute weddings and that Indi means “individual” and JumpID is the primary key to the database indicating the flight where all these people are at the wedding and plan to jump out of the same plane ... And it’s vital to know their Relationship happy couple so that you can give them the right color parachute.

Obviously this doesn’t exactly match your domain, but since you are asking a general question here, the details do not really matter.

What people in another post tried to tell you (in my opinion, anyway) does not replace your list with a class, but replaces JumpID with one.

In other words, instead of passing the JumpID to the procedure and using it to retrieve the list of people from the database, you create the Jump class.

And if your JumpID actually indicates a jump, as in goto , then you are probably actually a bunch of classes, that all subclasses are the same and override the same method in different ways.

In fact, let's say that you are doing a few parties that are not weddings, in which case you do not need a relationship, but only a simple list of people:

 type TPassenger = record FirstName, LastName: string; end; type TJump = class private JumpID : string; manifest : TList< TPassenger >; public constructor Init( JumpID: string ); function GetManifest( ) : TList< TPassenger >; procedure PrintManifest( ); virtual; end; 

So now PrintManifest() does the work of your PrintIndyEntry() , but instead of evaluating the inline string, it calls Self.GetManifest() .

Now, perhaps your database has not changed much, and your TJump instance TJump always short-lived, so you decided to just populate Self.manifest in the constructor. In this case, GetManifest() simply returns this list.

Or maybe your database changes often, or TJump works long enough for the database to change under it. In this case, GetManifest() rebuilds the list every time it is called ... Or, maybe you add another private value that indicates the last time you requested, and only updates after the information expires.

The fact is that PrintManifest does not care about how GetManifest works, because you have hidden this information.

Of course, in Delphi, you could do the same with unit , hiding the list of cached passenger lists in the implementation section.

But the class will bring a little more to the table when the time comes to realize the features typical of wedding parties:

 type TWeddingGuest = record public passenger : TPassenger; Relationship : string; end; type TWeddingJump = class ( TJump ) private procedure GetWeddingManifest( ) : TList< TWeddingGuest >; procedure PrintManifest( ); override; end; 

So TWeddingJump inherits Init and GetManifest from TJump , but also adds GetWeddingManifest( ); , and it will override PrintManifest() behavior with some custom implementation. (You know that this is done because of the override marker, which corresponds to the virtual marker in TJump .

But now suppose that PrintManifest is actually a rather complicated procedure, and you don’t want to duplicate all this code when all you want to do is add one column in the header and another column in the list of objects in the relationship field. You can do it like this:

 type TJump = class // ... same as earlier, but add: procedure PrintManfestHeader(); virtual; procedure PrintManfiestRow(passenger:TPassenger); virtual; end; type TWeddingJump = class (TJump) // ... same as earlier, but: // * remove the PrintManifest override // * add: procedure PrintManfestHeader(); override; procedure PrintManfiestRow(passenger:TPassenger); override; end; 

Now you want to do this:

 procedure TJump.PrintManifest( ) var passenger: TPassenger; begin; // ... Self.PrintManifestHeader(); for guest in Self.GetManifest() do begin Self.PrintManifestRow(); end; // ... end; 

But you still cannot, because GetManifest() returns a TList< TPassenger >; and for TWeddingJump , you need to return its TList< TWeddingGuest > .

Ok, how can you handle this?

In the source code, you have the following:

 IndiPtr: pointer 

Pointer to what? I assume that, as in this example, you have different types of individuals, and you need them to do different things, so you just use a common pointer and let it point to different types of records and hope you apply it to the right thing later. But classes give you some better ways to solve this problem:

  • You can make a TPassenger class and add the GetRelationship() method. This eliminates the need for TWeddingGuest , but that means the GetRelationship method is always around, even if you're not talking about weddings.
  • You can add GetRelationship(guest:TPassenger) to the TWeddingGuest class and just call it inside TWeddingGuest.PrintManifestRow() .

But suppose you need to query a database to populate this information. Using the above methods, you issue a new request for each passenger, and this can lead to a malfunction of your database. You really want to get everything in one go, in GetManifest() .

So instead you apply inheritance again:

 type TPassenger = class public firstname, lastname: string; end; type TWeddingGuest = class (TPassenger) public relationship: string; end; 

Because GetManifest() returns a list of passengers, and all wedding guests are passengers, now you can do this:

 type TWeddingJump = class (TJump) // ... same as before, but: // replace: procedure GetWeddingManfiest... // with: procedure GetManifest( ) : TList<TPassenger>; override; // (remember to add the corresponding 'virtual' in TJump) end; 

And now you fill in the details for TWeddingJump.PrintManifestRow , and the same version of PrintManifest works for both TJump and TWeddingJump .

There is another problem: we declared PrintManifestRow(passenger:TPassenger) , but we are actually moving on to TWeddingGuest . This is legal because TWeddingGuest is a subclass of TPassenger ... But we need to get .relationship in the field, and TPassenger does not have this field.

How can the compiler trust that inside TWeddingJump , you will always go to TWeddingGuest , and not just to regular TPassenger ? You must ensure that the Relationship field actually exists.

You can't just declare it as TWeddingJupmp.(passenger:TWeddingGuest) , because by subclass you basically promise to do whatever the parent class can do, and the parent class can handle any TPassenger .

So, you can go back to type checking manually and say it, just like an untyped pointer, but again, there are more efficient ways to handle this:

  • Polymorphism method: move the PrintManifestRow() method to the PrintManifestRow() class (by removing the passenger:TPassenger , since now it is an implicit Self parameter), override this method in TWeddingGuest , and then just call TJump.PrintManifest passenger.PrintManifestRow() .
  • The general approach of the class: TJump makes the generic class itself (type TJump<T:TPassenger> = class ), and instead of GetManifest() returns a TList<TPassenger> , you return TList<T> . Similarly, PrintManifestRow(passenger:TPassenger) becomes PrintManifestRow(passenger:T) Now you can say: TWeddingJump = class(TJump<TWeddingGuest>) , and now you can declare the overridden version as PrintManifestRow(passenger:TWeddingGuest) .

In any case, this way is more than I expected to write about all this. Hope this helps. :)

+9
source

All Articles