Похоже, вы говорите, что у вас есть контент, выложенный примерно так:
+--------+
| part 1 |
| type A |
+--------+
| part 2 |
| type C |
+--------+
| part 3 |
| type F |
+--------+
| part 4 |
| type D |
+--------+
и у вас есть читатели для каждого типа детали. То есть AScanner знает, как обрабатывать данные в части типа A (например, в части 1 выше), BScanner знает, как обрабатывать данные в части типа B и так далее. Я прав до сих пор?
Теперь, если я вас понимаю, проблема в том, что читатели типов (реализации IScanner
) не знают, как найти части, которые они распознают, в вашем составном контейнере.
Может ли ваш составной контейнер правильно перечислить отдельные части (т. Е. Знает ли он, где заканчивается одна часть, а другая начинается), и если да, имеет ли каждая часть какую-то идентификацию, которую может различить сканер или контейнер?
Что я имею в виду, данные выложены примерно так?
+-------------+
| 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: ... |
+-------------+
Если ваш макет данных похож на это, могут ли сканеры не запрашивать у контейнера все детали с идентификатором, соответствующим заданному шаблону? Что-то вроде:
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);
}
}
Или, если, возможно, контейнер не сможет распознать тип детали, но сканер сможет распознать свой собственный тип детали, возможно, что-то вроде:
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){
// determine if part is of type A
}
}
Возможно, я неправильно понял ваш контент и / или вашу архитектуру. Если да, уточни, а я обновлю.
Комментарий от ОП:
- Сканеры не должны знать тип контейнера.
- Тип контейнера не имеет встроенного интеллекта. Это так близко к простым старым данным, как вы можете получить в C #.
- Я не могу изменить тип контейнера; Это часть существующей архитектуры.
Мои ответы слишком длинны для комментариев:
Сканеры должны иметь некоторый способ извлечения части (ей), которые они обрабатывают. Если вас беспокоит то, что интерфейс IScanner
не должен знать об интерфейсе IContainer
, чтобы у вас была свобода изменять интерфейс IContainer
в будущем, тогда вы можете пойти на компромисс одним из следующих способов:
- Вы можете передать сканерам интерфейс
IPartProvider
, который IContainer
получен (или содержится). Этот IPartProvider
будет обеспечивать только функциональность обслуживания деталей, поэтому он должен быть довольно стабильным и может быть определен в той же сборке, что и IScanner
, так что вашим плагинам не нужно будет ссылаться на сборку, где IContainer
было определено.
- Вы можете передать делегат сканерам, которые они могли бы использовать для извлечения деталей. Тогда сканерам не понадобится знание какого-либо интерфейса (кроме, конечно,
IScanner
), только делегата.
и
- Возможно, вам нужен суррогатный класс, который знает, как взаимодействовать как с контейнером, так и со сканерами. Любая из упомянутых выше функциональных возможностей может быть реализована в любом старом классе, если контейнер уже предоставляет достаточно функциональных возможностей публично (или защищенно [это слово?]), Что внешний / производный класс сможет получить доступ к соответствующим данным .
Исходя из вашего псевдокода в отредактированном вопросе, похоже, что вы на самом деле не получаете никакой выгоды от интерфейсов и тесно связываете свои плагины с основным приложением, поскольку каждый тип сканера имеет уникальный производный IScanner
который определяет уникальный метод "scan", а класс CompositeScanner
имеет уникальный метод "parse" для каждого типа детали.
Я бы сказал, что это ваша основная проблема. Вам необходимо отделить подключаемые модули, которые, как я полагаю, являются разработчиками интерфейса IScanner
, от основного приложения, которое, как я полагаю, находится там, где живет класс CompositeScanner
. Одно из моих ранних предложений заключается в том, как I будет реализовывать это, но точные детали зависят от того, как работают ваши parseType
X функции. Можно ли их обобщить и обобщить?
Предположительно, ваши функции parseType
X связываются с объектом класса Composite
для получения необходимых данных. Не могли ли они быть перемещены в метод Parse
на интерфейсе IScanner
, который проксировал через класс CompositeScanner
для получения этих данных от объекта Composite
? Примерно так:
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);
});
}
}
Конечно, это можно упростить, удалив отдельный метод Parse
в IScanner
и просто передав делегат GetDataHandler
непосредственно в Scan
(реализация которого может вызвать приватный Parse
, если это необходимо). Код выглядит очень похоже на мои предыдущие примеры.
Этот дизайн обеспечивает настолько большое разделение проблем и разделение, насколько я могу себе представить.
Я просто подумал о чем-то еще, что вы могли бы найти более приемлемым, и, действительно, может обеспечить лучшее разделение интересов.
Если вы можете сделать так, чтобы каждый плагин "регистрировался" в приложении, вы можете оставить анализ в приложении, если плагин может сообщить приложению, как извлечь его данные. Примеры приведены ниже, но, поскольку я не знаю, как идентифицируются ваши детали, я реализовал две возможности - одну для проиндексированных частей и одну для именованных частей:
// 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);
}
}
или
// 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);
}
}
}
Очевидно, я чрезвычайно упростил эти примеры, но, надеюсь, вы поняли идею. Кроме того, вместо использования Activator.CreateInstance
было бы неплохо передать фабрику (или делегат фабрики) методу RegisterPlugin
.