It looks like you are saying that you have content that goes something like this:
+ -------- +
| part 1 |
| type A |
+ -------- +
| part 2 |
| type C |
+ -------- +
| part 3 |
| type F |
+ -------- +
| part 4 |
| type D |
+ -------- +
and you have readers for each type of detail. That is, AScanner knows how to process data in type A part (for example, part 1 above), BScanner knows how to process data in type B part, and so on. Am i still right?
Now, if I understand you, the problem you are facing is that type readers ( IScanner implementations) do not know how to find the part (s) that they recognize in your composite container.
Can your composite container correctly list the individual parts (i.e. does it know where one part ends and the other begins), and if so, does each part have some kind of identification that the scanner or container can distinguish?
What I mean is data laid out something like this?
+ ------------- +
| part 1 |
| length: 100 |
| type: "A" |
| data: ... |
+ ------------- +
| part 2 |
| length: 460 |
| type: "C" |
| data: ... |
+ ------------- +
| part 3 |
| length: 26 |
| type: "F" |
| data: ... |
+ ------------- +
| part 4 |
| length: 790 |
| type: "D" |
| data: ... |
+ ------------- +
If your data layout is similar to this, can scanners not ask the container for all parts with an identifier that matches this template? Something like:
class Container : IContainer{ IEnumerable IContainer.GetParts(string type){ foreach(IPart part in internalPartsList) if(part.TypeID == type) yield return part; } } class AScanner : IScanner{ void IScanner.ProcessContainer(IContainer c){ foreach(IPart part in c.GetParts("A")) ProcessPart(part); } }
Or, if possible, the container will not be able to recognize the type of part, but the scanner will be able to recognize its own type of part, perhaps something like:
delegate void PartCallback(IPart part); class Container : IContainer{ void IContainer.GetParts(PartCallback callback){ foreach(IPart part in internalPartsList) callback(part); } } class AScanner : IScanner{ void IScanner.ProcessContainer(IContainer c){ c.GetParts(delegate(IPart part){ if(IsTypeA(part)) ProcessPart(part); }); } bool IsTypeA(IPart part){
Perhaps I misunderstood your content and / or your architecture. If so, check and I will update.
Comment by OP:
- Scanners do not need to know the type of container.
- The type of container does not have built-in intelligence. It is as close to old data as possible since you can get into C #.
- I cannot change the type of container; This is part of the existing architecture.
My answers are too long for comments:
From your pseudo-code in your edited question, it seems that you really do not get any advantages from the interfaces and tightly connect your plugins with your main application, since each type of scanner has a unique IScanner output that defines a unique “scan” method, and the class CompositeScanner has a unique "parse" method for each type of part.
I would say that this is your main problem. You need to separate the plugins that I believe are the developers of the IScanner interface — from the main application, which I assume lives in the CompositeScanner class. One of my previous suggestions is how I will implement this, but the exact details depend on how your parseType X functions parseType . Is it possible to abstract and generalize them?
Presumably, your parseType X functions interact with an object of the Composite class to get the data they need. Can they not be transferred to the Parse method on the IScanner interface, which is proxied through the CompositeScanner class, to get this data from the Composite object? Something like that:
delegate byte[] GetDataHandler(int offset, int length); interface IScanner{ void Scan(byte[] data); byte[] Parse(GetDataHandler getData); } class Composite{ public byte[] GetData(int offset, int length){} } class CompositeScanner{} IScanner realScanner; public void ScanComposite(Composite c){ realScanner.Scan(realScanner.Parse(delegate(int offset, int length){ return c.GetData(offset, length); }); } }
Of course, this could be simplified by removing a separate Parse method on IScanner and simply passing the GetDataHandler delegate directly to Scan (the implementation of which could call a private Parse , if necessary). The code looks very similar to my previous examples.
This design provides the greatest possible separation of problems and solutions that I can think of.
I just thought of something else that you might find more acceptable and, indeed, can provide a better separation of concerns.
If you can have each "register" plug-in with the application, you can leave the parsing in the application if the plug-in can tell the application how to extract its data. The following are examples, but since I don’t know how your parts are identified, I implemented two possibilities: one for indexed parts and one for named parts:
// parts identified by their offset within the file class MainApp{ struct BlockBounds{ public int offset; public int length; public BlockBounds(int offset, int length){ this.offset = offset; this.length = length; } } Dictionary<Type, BlockBounds> plugins = new Dictionary<Type, BlockBounds>(); public void RegisterPlugin(Type type, int offset, int length){ plugins[type] = new BlockBounds(offset, length); } public void ScanContent(Container c){ foreach(KeyValuePair<Type, int> pair in plugins) ((IScanner)Activator.CreateInstance(pair.Key)).Scan( c.GetData(pair.Value.offset, pair.Value.length); } }
or
// parts identified by name, block length stored within content (as in diagram above) class MainApp{ Dictionary<string, Type> plugins = new Dictionary<string, Type>(); public void RegisterPlugin(Type type, string partID){ plugins[partID] = type; } public void ScanContent(Container c){ foreach(IPart part in c.GetParts()){ Type type; if(plugins.TryGetValue(part.ID, out type)) ((IScanner)Activator.CreateInstance(type)).Scan(part.Data); } } }
Obviously, I have greatly simplified these examples, but I hope you understand this idea. Also, instead of using Activator.CreateInstance it would be nice if you could pass the factory (or factory delegate) to the RegisterPlugin method.