Link to home
Start Free TrialLog in
Avatar of __alex
__alex

asked on

Pattern required

I want to provide myself with a little framework that looks something like:
  A = class
  A1 = class (A)
  A2 = class(A)
A1 and A2 are still abstract and I have to derive some Bs from either A1 or A2. A1 and A2 implement a method Foo that I want to call from A but I don’t want any B to be able to override Foo. I know I can put A, A1 and A2 into one single file and access private methods but I want to avoid it (I like small units). Tricky?
Avatar of Wim ten Brink
Wim ten Brink
Flag of Netherlands image

If A1 and A2 implement new functions that are not defined as abstract in A then A will not be able to call them. If you define function Foo in A as virtual and abstract then any class that inherits from A will be able to override the Foo method.

As an alternative, consider using interfaces instead. An interface is like an object with no data fields and only abstract methods. Thus you would get:

type
  A = interface
    procedure Foo;
  end;
  A1 = class( TInterfacedObject, A )
    procedure Foo;
  end;
  A2 = class( TInterfacedObject, A )
    procedure Foo;
  end;
  B = class( TInterfacedObject, A )
    FA: A;
    constructor Create( Foo: A );
    property ImplementFoo: A read FA write FA implements A;
  end;

procedure A1.Foo;
begin
  MessageBox( GetDesktopWindow, 'A1', 'A1', MB_OK );
end;

procedure A2.Foo;
begin
  MessageBox( GetDesktopWindow, 'A2', 'A2', MB_OK );
end;

constructor B.Create( Foo: A );
begin
  inherited Create;
  ImplementFoo := Foo;
end;

procedure CallFoo;
var
  FooB: A;
begin
  FooB := B.Create( A1.Create );
  FooB.Foo;
  FooB := B.Create( A2.Create );
  FooB.Foo;
end;

See, the methods aren't even virtual! :-)
Now, the fun part is that B can use any class that supports the A interface. (Thus, even itself!) But B is NOT able to overwrite the Foo method in whatever interface that is passed to it.
Interfaces don't really add much overhead either. We're talking about a few bytes of overhead. Then again, if you want B to only support the Foo method of class A2 then use this instead:

  B = class( TInterfacedObject, A )
    FA: A2;
    constructor Create( Foo: A2 );
    destructor Destroy; override;
    property ImplementFoo: A2 read FA write FA implements A;
  end;

Remember though that you need a destructor now to free the class that is now linked to your class.

Working with interfaces is difficult at first, but extremely useful for using patterns.
Avatar of __alex
__alex

ASKER

Nice, but...
The only reason for A1 and A2 being derived from A is because I want to reuse code. It’s not the kind of A1/A2-is-an-A relationship, semantically speaking. If I’ll go for interfaces like your approach implementation is shifted from A to A1/A2. My actual workaround is a procedural type property fnFoo in A that can be set only once. It is set in the constructor of A1 or A2. => A can call fnFoo but Bs can’t alter it. But as I said, it’s just a workaround.
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
Hi Alex,

I don't understand your interface thingy above... why do A1 and A2 have to declare the code for Foo whereas B doesn't? Since B uses the A interface as well...
B doesn't have to implement the Foo method since the implementation fo the interface is passed onwards to the ImplementFoo property. You assign some object to this property and it will handle the Foo function for you.
Basically this is the only way to get close to what you're trying to do, though. Once you have created a method that could be overridden, then you can't prevent that some new object will override the method even further. But by using interfaces, you just force developers to use them in a certain way.
... and if they passed in a nil?
Avatar of __alex

ASKER

Thanks again!
If they pass nil, well... If that is possible then you have to check for it. Or create some default object if they pass nil. But since you can control this value from the Create() method, you can decide what you want to fill it with.
The use of interfaces might be what you are looking for but it is a bit complex.