0

first of all, sorry if the title is a bit confusing.

i'm planning to develop a small erp application. this apllication will use plugins/addons. this addon might add or extend some modules of the base application.

for example, i have TCustomer class with property "id", and "name". addon 1 will add a property "dateofbirth". addon 2 will add a property "balance" and a method "GetBalance".

addon 1 and addon 2 are not aware of each other. addon 1 might be installed and not addon 2 or vice versa. so both addons must inherit the base tcustomer class.

the problem is when both addon are installed. how do i get extended properties in both addons? i will also have to extend the form to add controls to show the new properties.

can it be done using delphi? what is the best way to achieve this? maybe you can point me to some articles of examples?

thanks and sorry for my poor english Reynaldi

Reynaldi
  • 1,125
  • 2
  • 19
  • 41
  • your addon-classes should not be `TCustomer`s descendant. – teran May 12 '12 at 15:43
  • I mean that `name` or `id` is _property_, so make it `TCustomerProperty`. each property should have name,type(string,numeric,date) and value. when you are loading plugin, you should register loeded property in `TCustomer` class, to do it your `TCustomer` should have collection of `TCustomerPrperty`. If you are going to create plugins as DLL (may also in C++ or other language), then it is simply impossible to use inheritence (as you said). in this case you should use _Interfaces_ (`ICustomerProeprty`). so, Iheritance is not the way to solve this problem, property should be a separate class – teran May 12 '12 at 15:55
  • You may considere ORM for Delphi like here : http://stackoverflow.com/questions/2942409/is-there-any-new-orm-for-delphi-2010 – philnext May 12 '12 at 20:10
  • i will definately use orm. i'm evaluating tms aurelius, mormot and dorm. but i think only tms aurelius provide binding to data aware, since i'm gonna need it for devexpress grid – Reynaldi May 14 '12 at 13:21
  • @teran. good idea. so the tcustomer only contains basic properties, and all addons will register their custom props to tcustomer. am i correct? – Reynaldi May 14 '12 at 13:32

2 Answers2

5

Well, as you are already aware, you can't have more than one plug-in extend an existing class by inheritance. It would confuse the heck out of any app, including any programmer's dealing with the code.

What you need is some type of register mechanism in your TCustomer class where every plugin can register its specific properties, or provide a couple of call back functions for when a TCustomer instance is created (initialized), loaded, stored, or deleted. The core TCustomer after all really doesn't need to know more about the plug-in's than the fact that they might exist.

Depending on how you intend to load/store your data, the core TCustomer class doesn't even have to be aware of the extensions. It would be quite sufficient to make the persistence mechanism aware of plug-ins and provide a way for them to register a call back function to be called whenever a TCustomer / TOrder / TWhatever is initialized / loaded / saved / deleted.

You will also have to make the GUI aware of the plugins and provide the means for them to register pieces of UI to have the main GUI create an extra tab or some such for each plug-in's specific controls.

Sorry no code example. Haven't yet implemented this myself, though I thought about the design and it is on my list of things to play around with.

That said, to give you an idea, the basic mechanism could look something like this:

TBaseObject = class; // forward declaration

// Class that each plug-in's extensions of core classes needs to inherit from.
TExtension = class(TObject)
public
  procedure Initialize(aInstance: TBaseObject);
  procedure Load(aInstance: TBaseObject);
  procedure Store(aInstance: TBaseObject);
  procedure Delete(aInstance: TBaseObject);
end;

// Base class for all domain classes
TBaseObject = class(TObject)
private
  MyExtensions: TList<TExtension>;
public
  procedure RegisterExtension(const aExtension: TExtension);
  procedure Store;
end;

procedure TBaseObject.RegisterExtension(const aExtension: TExtension);
begin
  MyExtensions.Add(aExtension);
end;

procedure TBaseObject.Store;
var
  Extension: TExtension;
begin
  // Normal store code for the core properties of a class.
  InternalStore; 
  // Give each extension the opportunity to store their specific properties.
  for Extension in MyExtensions do
    Extension.Store(Self);
end;
Marjan Venema
  • 19,136
  • 6
  • 65
  • 79
  • thanks for your clear sample. i guess this is one way to do this, or perhaps the only way. the most difficult part is on the gui i think. adding a tab for each plugin might not be an elegant or beautiful way i think. i'm thinking to generate all gui at runtime, using xml. or do you have a better or simpler solution? – Reynaldi May 14 '12 at 12:48
  • @Reynaldi: Nope, I don't. The plug-ins I know usually don't extend existing classes, but add entirely new functionality. In these cases a separate tab per plug-in is pretty acceptable. Generating GUI at run-time could also be done using templates (dfm, xaml or html based). Automatically merging templates from different plug-ins would be one heck of a challenge though and I would cop out and have a "consultant" do it manually/visually. You could look into a way for the plug-ins to register additional controls with the main ui based on a "left of xxx" or "below yyy" and handle spacing yourself. – Marjan Venema May 14 '12 at 13:12
1

Such an "evolving" class is some kind of multi-inheritance.

I think you shall better use interfaces instead of classes.

That is, each plug-in will serve a class implementation, but you will work with interfaces, and an interface factory.

See this article about "why we need interfaces", or this article from our blog about interfaces and a comparison with classes.

You can test if a class implements an interface: for a plug-in system like yours, this is probably the best way to implement an open implementation. Duck typing is very suitable for plug-ins. The whole Delphi IDE (and Windows itself) is using interfaces for its plug-in systems (via COM for Windows). For a less strong implementation pattern, you can use not interfaces, but late bindings: see the answer of this SO question.

Take a look at the SOLID principles, especially the single responsibility principle. Your question directly breaks this principle: you attempt to mix client personal information (like name) and accounting (like a balance). If your project grows up, you'll probable be stuck by such a design.

Community
  • 1
  • 1
Arnaud Bouchez
  • 42,305
  • 3
  • 71
  • 159
  • yes, i think it would be better to use interfaces here. i'm not familiar with duck typing. it looks interesting. can you please provide me with a simple sample for my situation? – Reynaldi May 14 '12 at 13:08