Inheritance problem

B

Bill

Hi,

This problem is more difficult to explain than the ones in my previous
posts. For this one, I just need some good design practice advice,
from an intuitive point of view.

I have three classes, named Plot1, Plot2 and Plot3. Plot2 derives from
Plot1, and Plot3 derives from Plot2.

I have two more classes, named Cell2 and Cell3. Cell3 derives from
Cell2. So far, so good.


Now, one of the member fields of Plot2 is an array of cells of type
Cell2 (declaration is "public Cell2[,] cell;").
And one of the member fields of Plot3 is an array of cells of type
Cell3 (declaration is "new public Cell3[,] cell;").

Graphically:

Plot1
|
|
Plot2 has member field of type Cell2
| |
| |
Plot3 has member field of type Cell3


Suppose that the kind of plot that I need in my application is an
instance of Plot3.

I separated Plot3 from Plot2 (and Cell3 from Cell2) expecting to
reuse, in Plot3, a lot of code present in Plot2. That is, most of the
operations with cells need only the member fields present in Cell2,
and those operations are done at level 2 (in Plot2 code). I wanted to
reuse that code in level 3, and only add the new code (in Plot3) which
is specific for member fiels of Cell3 _not_ present in Cell2. However,
it looks like I can't reuse anything, because every time I invoke,
from Plot3 (using base.<whatever>()), the code in Plot2 that refers to
cells, it refers to instances of type Cell2, not Cell3. And I haven't
allocated space for the Cell2 cells. Only for the Cell3 cells, of
course. I mean, when I run my application, I don't want any "...=new
Cell2[..,..]" being executed. Only "...=new Cell3[..,..]". Those cells
take a lot of memory each, and the level2 ones would never be used
anyway.

So, how to make the code in Plot2 that does the "common" operations
with cells, refer to the most-specific cells (which, when called from
a Plot3 instance, would be Cell3 cells), instead of to its Cell2
cells?

I mean, I need something like virtual/override, but for member fields.
The compiler doesn't let me use those modifiers for member fiels.

Anyone got the idea of my problem? I think this must be a relatively
usual need. How to proceed in these cases?

Thank you very much,
Bill
 
P

Peter Morris

Do you need to be able to hold a reference somewhere in your app which is
either a Plot1, Plot2, or Plot3. e.g.


public void DoSomething(Plot1 plot)
{
plot.Execute();
}

where you could pass an instance of Plot1, Plot2, or Plot3?

Also, the same for Cell2 and Cell3?


It looks to me like you are inheriting for the purpose of code reuse. It's
hard to help though with such an abstract description of the problem.
 
B

Bill

Do you need to be able to hold a reference somewhere in your app which is
either a Plot1, Plot2, or Plot3. e.g.


public void DoSomething(Plot1 plot)
{
plot.Execute();
}

where you could pass an instance of Plot1, Plot2, or Plot3?

No, I don't need that.
Also, the same for Cell2 and Cell3?


It looks to me like you are inheriting for the purpose of code reuse.

That's one of the main reasons to the existence of inheritance.
Code reuse, and with a good partitioning of the problem.
It's
hard to help though with such an abstract description of the problem.

Plot1 does not subdivide the area in cells. It just initializes
Direct3D to be used on that large canvas. It has two abstract methods
(DX_Resize() and BuildScene()) that must be implemented in a derived
class.

Plot2 derives from Plot1 and adds subdivision of the canvas in an
array of mxn Cell2 cells, each one having axes, cursor and storage for
one y=f(x) 2D plot. It provides the first implementations for
DX_Resize() and BuildScene(), which basically recompute the cell sizes
and draw, for each one of them, the axes and the y=f(x) curve, when
needed. Plot2 does not populate the y=f(x) curves. It just draws them.

Plot3A derives from Plot2, and adds code to populate the y=f(x) curves
which is optimized for real time. The user of Plot3A wants to see the
curves continuously scrolling towards the left, on each one of the
cells. They don't want to do zooms, or measure times or amplitudes.
Just have a quick image of the shape of the curve being shown. Thanks
to the optimization for real time, I can draw hundreds of frames per
second, at 1680x1050x32. Plot3A uses Cell3A cells, which derive from
(extend) Cell2.

Plot3B also derives from Plot2, and adds code to populate the y=f(x)
curves but not to be shown in real time. Instead, to give the user the
ability to zoom (especially with good anti-aliasing), measure times
and amplitudes, move in time either in a synchronous way for all the
cells or in an independent way, etc.

Suppose that the end user needs to include one of my custom controls
in a Form, to show several curves in real time. He/she will
instantiate Plot3A, and will call, among some other things, its Init()
method. Now, Plot3A.Init() should reuse what Plot1.Init() does
(initialize Direc3D), should also ideally reuse what Plot2.Init() does
(initializing the cells), and also add some new code, specific to
Plot3A. I wrote "ideally" because I would like to reuse what
Plot2.Init() does, but I will probably don't do it. Plot2.Init()
executes "...=new Cell2[..,..]" at some point, and I don't need that
new to be executed. I need a "...=new Cell3[..,..]" to be executed,
so, I'll have to repeat that code in Plot3.Init(), but pointing to the
Cell3 type variable. Remember that the user is instantiating Plot3A. I
would like Plot2.Init() refer to Cell3 cells, but it can't (that I
know).

I have all this code altogether, without inheritance, and works
neatly. The "altogether" equivalent of Plot3A draws 8x8 subplots in
real time at more than 200 frames per second. My problem is not having
the hierarchy Plot1 -> Plot2 -> Plot3 or the hierarchy Cell2 -> Cell3.
It's having them linked to each other. Yes, I'm missing something, or
I'm too sleepy, but the partitioning does make sense to me, and I
don't see an elegant solution to implement it, without repeating much
code from level to level.

I don't know if that clarified what my problem is.
Thanks.
 
B

Ben Voigt [C++ MVP]

Suppose that the end user needs to include one of my custom controls
in a Form, to show several curves in real time. He/she will
instantiate Plot3A, and will call, among some other things, its Init()
method. Now, Plot3A.Init() should reuse what Plot1.Init() does
(initialize Direc3D), should also ideally reuse what Plot2.Init() does
(initializing the cells), and also add some new code, specific to
Plot3A. I wrote "ideally" because I would like to reuse what
Plot2.Init() does, but I will probably don't do it. Plot2.Init()
executes "...=new Cell2[..,..]" at some point, and I don't need that
new to be executed. I need a "...=new Cell3[..,..]" to be executed,
so, I'll have to repeat that code in Plot3.Init(), but pointing to the
Cell3 type variable. Remember that the user is instantiating Plot3A. I
would like Plot2.Init() refer to Cell3 cells, but it can't (that I
know).

I have all this code altogether, without inheritance, and works
neatly. The "altogether" equivalent of Plot3A draws 8x8 subplots in
real time at more than 200 frames per second. My problem is not having
the hierarchy Plot1 -> Plot2 -> Plot3 or the hierarchy Cell2 -> Cell3.
It's having them linked to each other. Yes, I'm missing something, or
I'm too sleepy, but the partitioning does make sense to me, and I
don't see an elegant solution to implement it, without repeating much
code from level to level.

You'll need the newest C# which adds support for covariance in order to
really do this properly, then something like:

interface ILookup<in S, out T> { T operator[](S index); }

Then Plot2 needs an abstract property of type ILookup<int, Cell2> which is
implemented in Plot3a and Plot3b using collections of Cell3a and Cell3b
respectively.

OTOH, you can do it with any version of C# by adding an abstract Cell2
GetCell(int index) method to Plot2 and implementing that in Plot3a and
Plot3b to use the actual arrays of Cell3a and Cell3b as needed.

What you shouldn't do is use the broken covariance implemented on arrays,
which will add runtime type checks everywhere and kill performance.

If you don't want Plot2 to become abstract, you'll need Plot1Base and
Plot2Base classes which are abstract, Plot3a extends Plot2Base but not
Plot2. The part of the Init functions that create the arrays go in the leaf
classes, the reusable parts into the PlotNBase classes.
 
P

Peter Morris

where you could pass an instance of Plot1, Plot2, or Plot3?
No, I don't need that.

Then you don't need inheritance.

That's one of the main reasons to the existence of inheritance.

It might be a good argument for using objects (a single object implements
certain functionality) but that doesn't mean you must descend from the
object in order to use that functionality.

Code reuse, and with a good partitioning of the problem.

That's objects, not inheritance.

Plot1 does not subdivide the area in cells. It just initializes
Direct3D to be used on that large canvas. It has two abstract methods
(DX_Resize() and BuildScene()) that must be implemented in a derived
class.

Okay, *now* it looks like you need inheritance :) You need slight
variations in the implementation.

I still find it hard to understand your model but I will give you a couple
of ideas and hopefully you will see a fit for one (or both).

01: An abstract way to get a list of cells

Problem:
Plot2 has a property Cell2[] and Plot3[] has a property Cell3[], but you
need a way to get a Cell[] property.

Solution:
Have an abstract method on Plot1

protected Cell[] GetCells();

override it in descendants, convert your Cell3[] or Cell2[] to Cell[]

protected Cell[] GetCells()
{
List<Cell> cells = new List<Cell>(this.Cells);
return cells.ConvertAll(value => (Cell)value).ToArray();
}


02: An abstract way of creating an instance of the correct cell kind

Problem:
The base class needs to create instances of Cell, but you don't know which
class type to create instances of.

Solution:
Have a creation method on your Point class

protected abstract Cell CreateCell();

and override it in your descendant Point3

protected override Cell CreateCell()
{
return new Cell3();
}

//Code that uses it

public void Cell AddCell()
{
return CreateCell();
}


If neither of these are relevant then past a couple of lines that show what
you are trying to execute, what result you are experiencing, and what result
you desire.
 
B

Bill

It might be a good argument for using objects (a single object implements
certain functionality) but that doesn't mean you must descend from the
object in order to use that functionality.

I didn't say that. I never said that code reuse implies inheritance. I
said that inheritance provides code reuse, among other things.
That's objects, not inheritance.

Partitioning does not necessarily mean horizontal partitioning. It may
mean vertical partitioning, which was my case.

Come on :) I couldn't have written what I wrote if I didn't know the
difference between objects and inheritance.
I still find it hard to understand your model but I will give you a couple
of ideas and hopefully you will see a fit for one (or both).
01: An abstract way to get a list of cells
02: An abstract way of creating an instance of the correct cell kind

Your two ideas were good. I did use your second one, and could make
the partitioned model work. However, a new problem (now with indexers)
forces me to do not very elegant casts at the application level (the
one that instantiates Plot3A, for instance). I'll try to solve it, but
if I can't, I'll paste some code.

Thanks a lot for your time.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top