areas = json.stores.map(function (x) {
return x.storeArea
});
dates = json.stores.map(function (x) {
return x.totalStore.map(function (y) {
return y.date
})
});
results = [areas,dates.flat()]
Результаты - -
(2) [Array(6), Array(18)]
0: (6) ["area1", "area2", "area3", "area4", "area5", "area6"]
1: (18) ["2018-10-01", "2018-11-01", "2018-12-01", "2018-10-01", "2018-11-01", "2018-12-01", "2018-10-01", "2018-11-01", "2018-12-01", "2018-10-01", "2018-11-01", "2018-12-01", "2018-10-01", "2018-11-01", "2018-12-01", "2018-10-01", "2018-11-01", "2018-12-01"]
Я собираюсь попробовать... ;-) Если в Вашей системе существует фаза при заполнении "каталога" IScanner
объекты Вы могли думать об украшении Вашего IScanner
s с атрибутом, указывающим, который Part
они интересуются. Затем можно отобразить эту информацию и управлять сканированием Вашего Composite
с картой. Это не полный ответ: если у меня будет немного времени, то я попытаюсь уточнить...
Править: немного псевдокода для поддержки моего запутанного объяснения
public interface IScanner
{
void Scan(IPart part);
}
public interface IPart
{
string ID { get; }
}
[ScannedPart("your-id-for-A")]
public class AlphaScanner : IScanner
{
public void Scan(IPart part)
{
throw new NotImplementedException();
}
}
[ScannedPart("your-id-for-B")]
public class BetaScanner : IScanner
{
public void Scan(IPart part)
{
throw new NotImplementedException();
}
}
public interface IComposite
{
List<IPart> Parts { get; }
}
public class ScannerDriver
{
public Dictionary<string, IScanner> Scanners { get; private set; }
public void DoScan(IComposite composite)
{
foreach (IPart part in composite.Parts)
{
IScanner scanner = Scanners[part.ID];
scanner.Scan(part);
}
}
}
Не берите его как есть: это для цели объяснения.
Править: ответьте на комментарии полковника Kernel. Я рад, что Вы находите это интересным. :-) В этом простом эскизе кода отражение должно быть включено только во время инициализации Словаря (или, когда необходимый), и во время этой фазы можно "осуществить" присутствие атрибута (или даже использовать другие способы отобразить сканеры и части). Я говорю, "осуществляют", потому что, даже если это не ограничение времени компиляции, я думаю, что Вы выполните свой код, по крайней мере однажды вводящий его в эксплуатацию ;-) таким образом, это могло быть ограничение во время выполнения в случае необходимости. Я сказал бы, что вдохновение - что-то (очень очень слегка) подобный MEF или другим подобным платформам. Просто мои 2 цента.
Это походит на то, что Вы говорите, то, что у Вас есть содержание, разметил что-то вроде этого:
+--------+ | 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
}
}
Возможно, я неправильно понял Ваше содержание и/или Вашу архитектуру. Если так, разъяснитесь, и я обновлю.
Комментарий от OP:
- Сканеры не должны иметь никакого знания контейнерного типа.
- Контейнерный тип не обладает никаким встроенным интеллектом. Это как близко к простым данным, поскольку можно войти в C#.
- Я не могу изменить контейнерный тип; Это - часть существующей архитектуры.
Мои ответы являются слишком длинными для комментариев:
Сканеры должны иметь некоторый способ получить часть (части), которую они обрабатывают. Если Ваше беспокойство то, что IScanner
интерфейс не должен знать IContainer
взаимодействуйте через интерфейс так, чтобы у Вас была свобода измениться IContainer
интерфейс в будущем, затем Вы могли пойти на компромисс одним из нескольких способов:
IPartProvider
соедините интерфейсом с этим IContainer
полученный из (или содержавший). Это IPartProvider
только обеспечил бы функциональность подавания частей, таким образом, это должно быть довольно стабильно, и это могло быть определено в том же блоке как IScanner
, так, чтобы Ваши плагины не должны были бы ссылаться на блок где IContainer
был определен.IScanner
, конечно), только делегата.и
Из Вашего псевдокода в Вашем отредактированном вопросе похоже, что Вы действительно не получаете выгоды от интерфейсов и сильно связываете Ваши плагины к Вашему главному приложению, так как каждый тип сканера имеет уникальную деривацию IScanner
это определяет уникальный метод "сканирования" и CompositeScanner
класс имеет уникальный метод "синтаксического анализа" для каждого типа детали.
Я сказал бы, что это - Ваша основная проблема. Необходимо отделить плагины — который я принимаю, конструкторы IScanner
интерфейс — из главного приложения — который я принимаю, то, где CompositeScanner
жизни класса. Одно из моих более ранних предложений - то, как я реализовал бы это, но точные детали зависят от как Ваш parseType
X работ функций. Они могут быть абстрагированы и обобщены?
По-видимому, Ваш parseType
X функций общаются с Composite
объект класса для получения данных им нужно. Могли они не быть перемещенными в a 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
метод.