Link to home
Start Free TrialLog in
Avatar of gandalf_the_white
gandalf_the_whiteFlag for Austria

asked on

how to make classes that are referencing circular?

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
Avatar of andrewjb
andrewjb
Flag of United Kingdom of Great Britain and Northern Ireland image

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.
Avatar of gandalf_the_white

ASKER

thanks for your effort

as is said i know this way to solve the problem but i dont find that very pretty :-)
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.
Avatar of Wim ten Brink
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.
Avatar of roknjohn
roknjohn

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"
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.
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
ASKER CERTIFIED SOLUTION
Avatar of Wim ten Brink
Wim ten Brink
Flag of Netherlands image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
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
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?
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 :-)

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.  ;-)
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.

@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 :-)