Solved

how to make classes that are referencing circular?

Posted on 2003-12-02
14
287 Views
Last Modified: 2010-04-05
is there a way to let container classes have a reference to their "childclasses" (the object they are containing)
and these objects have a pointer to their container.

deeper explanation:
im just making a set of components for our own sprite class.
these components should all be in a Controlpanel that is only a
container for the things that can be shown in it.
so the controlpanel knows all objects that can be shown in it.
for easier use it would be nice to only set the properties of the
controlpanel and all other objects can access these properties.
because of that i want to give the objects a parent property.
and so i have a circle.


unit Unit1;
interface
uses Unit2;
type
  TControlpanel = class(TObject)
  private
    ControlpanelObject1:TControlpanelObject;
    ....

implementation
....
end.



unit Unit2;
interface
uses Unit1;
type
  TControlpanelObject = class(TObject)
  private
    ParentContainer:TTControlpanel;
    ....

implementation
end.


as you can see this code will not compile.

the units will get quite big so i want to put classes in different units (to be exact there will be
more than one unit with controlpanel objects for instance: feedback)

forward declarations work only in the same unit?

i know that i can use the unit1 in the implementation part of the unit 2 and can typecast the
parentcontainer but i hoped that there is a nicer solution.

i also know that i could put all classes in the same unit
but i dont like units that have much more than 1000 lines

gandalf
0
Comment
Question by:gandalf_the_white
  • 5
  • 4
  • 3
  • +1
14 Comments
 
LVL 12

Expert Comment

by:andrewjb
ID: 9857767
You have to define a generic pointer in one of the classes, then cast or check every time you use it.

e.g.

TControlPanelObject = class( TObject )
private:
  ParentContainer : TObject;
...


implementation

uses Unit1;  <---- This is allowed here. It's not a circular reference, then.

then everytime you use it, you need to

TControlPanel(ParentContainer).DoSomething()

or ( ParentContainer as TControlPanel ).DoSomething() if you want to confirm it's set correctly.
0
 
LVL 3

Author Comment

by:gandalf_the_white
ID: 9857855
thanks for your effort

as is said i know this way to solve the problem but i dont find that very pretty :-)
0
 
LVL 12

Expert Comment

by:andrewjb
ID: 9858095
Oppsie. Didn't read that carefully enough!

No - that's your only option (or put them in the same unit, as you say).


The final possibility is to have an abstract base class for both parent and child. These definition go in the same unit. Then two more units separately implement them. The base classes then only need to define the public interface that the child/parent needs to know about. That may or may not help you, but isn't particularly nice, either.
0
 
LVL 17

Expert Comment

by:Wim ten Brink
ID: 9858179
A simple option would be the use of interfaces. Add a third unit with the following interface classes:

type
  IControlpanelObject = interfac;
  IControlpanel = interface
    function GetControlpanelObject: IControlpanelObject;
    procedure SetControlpanelObject(const Value: IControlpanelObject);
    property ControlpanelObject:IControlpanelObject;
  end;
  IControlpanelObject = interface
    function GetParentContainer: ITControlpanel;
    procedure SetParentContainer(const Value: ITControlpanel);
    ParentContainer:ITControlpanel;
  end;

This does not require any other code. But you might want to add more methods and properties to these interfaces.
Now, your units, define them as:

unit Unit1;
interface
uses UnitInterface;
type
  TControlpanel = class(TInterfacedObject, IControlpanel)
  private
    function GetControlpanelObject: IControlpanelObject;
    procedure SetControlpanelObject(const Value: IControlpanelObject);
    property ControlpanelObject:IControlpanelObject;
    ....

implementation
....
end.



unit Unit2;
interface
uses UnitInterface;
type
  TControlpanelObject = class(TInterfacedObject, TControlpanelObject)
  private
    function GetParentContainer: ITControlpanel;
    procedure SetParentContainer(const Value: ITControlpanel);
    ParentContainer:ITControlpanel;
    ....

implementation
end.

And yes, I know. It is a bit complex technique but it is a very good technique if you want two classes to be related to one another. But remember, they can only call the methods and events that are exposed by the interface definitions.

Another way would be to create two base classes in a third unit which both have abstract methods only. Then inherit your new classes from these abstract classes.

The problem with circular references between classes is that this can only be created within a single unit. This because you can forward declarations in a single unit. For multiple units you can only refer to a base class. Meaning either refer to TObject or to your own abstract class. Or refer back to the interface. I prefer the last option since it keeps things quite clean.
0
 
LVL 1

Expert Comment

by:roknjohn
ID: 9858930
if the units are too big for one file - you could use separate source files and combine them with an $INCLUDE directive.   In actuality, you would end up with one unit, thus avoiding the circular reference problem.

Remember to use forward declarations like:

type
  TFigure = class;  // forward declaration
  TDrawing = class
    Figure: TFigure;
     ...
  end;

  TFigure = class  // defining declaration

    Drawing: TDrawing;
     ...
  end;

Search for help on  "Forward declarations" & "mutually dependent classes"
0
 
LVL 17

Expert Comment

by:Wim ten Brink
ID: 9865864
Oh, no! Not the $Include thing!

Okay, Delphi allows you to use it but I consider it as dangerous as using Goto's and labels in your Delphi code. It is possible but the result is less readable. Which means that if next year you have to make changes to your source it will take a lot more time to understand your code again.

Besides, Delphi doesn't have much problems with large units. It's mostly the developer who considers them unreadable. Breaking large classes up in smaller classes and combine their functionality by either inheritance or encapsulation. That way, every class is specialized for a single task and it's easier to modify a specific task.
0
 
LVL 3

Author Comment

by:gandalf_the_white
ID: 9866709
thanks for the idea with the $include
i have seen this command but have not realised that it can be used in this way


i have programmed more than 5 cds and every now and then i am
forced to get back to my old CDs to make them run under XP
and such things.

i first started programming visually (my first cd) and my next evolution
was to make everything non visual. problem was that i did not divide
my units so i have units with 2000 to 4000 lines of code.

my last cd (a learning mathematical learning programm for children with the basic
mathematical operations up to the number 100) has 12 different lessontypes
and each lessontype has up to 15 stages of difficulty.
the programm has more than 140 units and i really like it that way.
a few lesson difficulties are inherited to a depth of 7.
(for instance the Step 12 of Lesson 4
TTask->TTL4->TTL4St3->TTL4ST1->TTL4St6->TTL4St11->TTL4St12)

i really like it that way, as i quite exactly know where the things belong to.
most of this unit have less than 600 lines and i hope that not a single one
is above 1000.
i never again want to have so big units as they are real horror to navigate through them and
i found many errors in the code by just looking at it.

i think if you use it careful (and only for the inheritance matter) it can be a good an
really easy to understand tool.

btw
i like the code with the interfaces as i only have seen complex examples of its
usage but never saw a real usable one. but as i have not enough time
at the moment to get deeper into it i will put it on the shelf for
later usage
0
Highfive Gives IT Their Time Back

Highfive is so simple that setting up every meeting room takes just minutes and every employee will be able to start or join a call from any room with ease. Never be called into a meeting just to get it started again. This is how video conferencing should work!

 
LVL 17

Accepted Solution

by:
Wim ten Brink earned 125 total points
ID: 9866904
The Interface code looks complicated, yes. And the examples you've probably seem often inclode usage for COM+ and ActiveX controls. Many people seem to forget that interfaces can be used for much simpler tasks too.

But it might be best to put it on the shelf if you don't have much time to play around with it. In the beginning it is quite time-consuming to understand the whole technique around it. Basically, an interface just makes a class more abstract. A class might have hundreds of methods but the interface might only provide you 10 or 20 of these methods, making all other methods invisible.

But one thing you have to get used to with interfaces is the garbage collection within Delphi. If you use interfaces you don't have to free them anymore, Delphi does this for you when an interface goes out of scope or when you assign a new value to an interface. This is actually the most complicated part of interfaces.

Personally, I like working with interfaces. I often use it when I divide a project in one main executable with several DLL's. These DLL's then export a function the EXE can call and this function will return an interface. The Exe only knows the interface definition while the DLL knows both the definition and the related class. If you have multiple DLL's that all export the same interface then the Exe can actually choose between these DLL's and decide which functionality it wants.

Which brings me to another way to make your project smaller... Divide it into DLL's... In general quite useful unless you also have to deal with many forms. In general you don't want forms inside DLL's because that makes things a bit more complicated.
Or consider the use of packages. They don't have to be runtime packages but just group some units together in a package. Your project has 140 units? Well, divide it into e.g. 20 packages of about 5 units each, each related to it's own functionality. The result is still the same, one large executable. But the packages allow you to group things together in a more logical order.

If you like the idea of interfaces but don't feel ready for it, then create a third unit which has the abstract versions of your two classes. Something like:

unit UnitCustom;
interface
type
  TCustomControlpanelObject = class(TObject);
  TCustomControlpanel = class(TObject)
  private
    ControlpanelObject1:TCustomControlpanelObject;
    procedure Method1; virtual; abstract;
    procedure Method2; virtual; abstract;
    procedure Method3; virtual; abstract;
    ....
  TCustomControlpanelObject = class(TObject)
  private
    ParentContainer:TCustomControlpanel;
    procedure Method4; virtual; abstract;
    procedure Method5; virtual; abstract;
    procedure Method6; virtual; abstract;
    ....

implementation
end.

Followed by:

unit Unit1;
interface
uses UnitCustom;
type
  TControlpanel = class(TCustomControlpanelObject)
  private
    procedure Method1; override;
    procedure Method2; override;
    procedure Method3; override;
    ....

implementation
....
end.



unit Unit2;
interface
uses UnitCustom;
type
  TControlpanelObject = class(TCustomControlpanel)
  private
    procedure Method4; override;
    procedure Method5; override;
    procedure Method6; override;
    ....

implementation
end.

This method is a bit similar to the interfaced technique. However, it should be less complicated and it allows you to divide code over three units.
0
 
LVL 17

Expert Comment

by:Wim ten Brink
ID: 9866927
Silly me...

  TControlpanel = class(TCustomControlpanelObject)
  TControlpanelObject = class(TCustomControlpanel)

I mixed them up :D It should be:
  TControlpanel = class(TCustomControlpanel)
  TControlpanelObject = class(TCustomControlpanelObject)

But I guess you guessed that already... :D
0
 
LVL 1

Expert Comment

by:roknjohn
ID: 9867103
gandalf,

Just a thought.  I am sure that I'm speaking out of school here, since I don't know the inter-workings of your program.  If so, I apologize.  Why not store your lessons in a database, and create the lesson form from that data when needed?  Surely, there is some similarity between lessons.  I written much testing and learning software, and this is the method I used.  

It makes it easy to alter the lessons/questions/answers without recompiling your project.  It also allows you to easily  distribute (or sell) additional lessons (databases).

Then again, you may already be doing that.  140 units just sounds like overkill to me.

Might I ask, also, why you elected to do it non-visually?
0
 
LVL 3

Author Comment

by:gandalf_the_white
ID: 9867555
its a program that looks like a game with many different stages and moving
objects and that kind of stuff so we were not able to find a good generic
approach to all the lessons.

the program took about 1 1/2 years to code.

if you program visually you have a problem if you want to change things.
we are a team of psychologists and technicians
(www.lifetool.at - sorry only in german but english will follow in spring)
that are programming software for children with special needs.
we get concepts of the program from the psychologists that see
what type of software would be good or could have a good use
(especially for children with disabilities but often also for "normal" children).
these concept are exactly the way you would think a concept would look
alike if written by a psychologists ;-)

that means we have to make many discussions about how the lessons should
really work. things like: if we solve the first number wrong will there be feedback?
how long will it last? what about the second try? and so on

we are used to change about 20 to 80 % of the program during its
creation. that is the reason we have developed a special framework
that takes care about the surroundings of the cd
( intro, extro , useroption,...) and the lessons themself.
so we can reuse many of our code from one cd to another because
many things stay the same.
in this project there are about 80 units from the lessons about
50 units are components that are used in other programs and
about 40 units are only for this cd.

one reason for not visually developing is the code sharing.
even if i use visual components they normally dont come with
the functionality i want or i need more functionality. for instance if i
have a helpmemofield where the function of a combobox is explained
in depth then i have to connect the mousemove of the combobox to the
helpfield if i do it visually. non visual i inherit a MyComboBox that has a
property HelpMemoField:TMemoField and then this Box knows
where it will write its help text.


To Workshop_Alex:
the idea with the abstract classes is real cool
but it reminds me a bit of the include :-)

0
 
LVL 1

Expert Comment

by:roknjohn
ID: 9867810
gandalf,

Thank you for explaining your project, its sounds very interesting and worthwhile.  Now, I understand your situation better.  Good luck in your endevour.

FYI: You do realize, of course, that you can install your MyComboBox component on the VCL component pallette and use it visually.  ;-)
0
 
LVL 3

Author Comment

by:gandalf_the_white
ID: 9881313
i was totally wrong with the include

i tried it and now i know what you mean Alex

as the code is only inserted you'll have write units that don't look like normal units anymore
so i really don't like this way of coding.

0
 
LVL 3

Author Comment

by:gandalf_the_white
ID: 10975259
@Workshop_Alex: i implemented the version with the abstract virtual classes
it works quite well and its part of our actual cd

for me it was a kind of test balloon to see if i'm able to make
some kind of components with my spriteclass

the controlpanel consists now of four units each between 400 and 700 lines of code.
thats the way i like them :-)

0

Featured Post

Enabling OSINT in Activity Based Intelligence

Activity based intelligence (ABI) requires access to all available sources of data. Recorded Future allows analysts to observe structured data on the open, deep, and dark web.

Join & Write a Comment

Introduction Raise your hands if you were as upset with FireMonkey as I was when I discovered that there was no TListview.  I use TListView in almost all of my applications I've written, and I was not going to compromise by resorting to TStringGrid…
In my programming career I have only very rarely run into situations where operator overloading would be of any use in my work.  Normally those situations involved math with either overly large numbers (hundreds of thousands of digits or accuracy re…
Excel styles will make formatting consistent and let you apply and change formatting faster. In this tutorial, you'll learn how to use Excel's built-in styles, how to modify styles, and how to create your own. You'll also learn how to use your custo…
In this seventh video of the Xpdf series, we discuss and demonstrate the PDFfonts utility, which lists all the fonts used in a PDF file. It does this via a command line interface, making it suitable for use in programs, scripts, batch files — any pl…

706 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

19 Experts available now in Live!

Get 1:1 Help Now