Здравствуйте, Doc, Вы писали:
Doc>Мне кажется это те же яйца что и были, только over-engineered.
Ну это зависит от того, как будут создаваться экземпляры IUserStorageInfo. Может выйти и так...
Красота — наивысшая степень целесообразности. (c) И. Ефремов
Здравствуйте, Sharov, Вы писали:
S>>>TDD я не использую. S>·>Так начинай. S>Нет, у меня есть интеграционные тесты. Над проектом я работаю один, много возни, чтобы их писать и поддерживать.
Хорошие юнит-тесты как хорошие дети. Вначале ты поддерживаешь их, а потом они тебя.
S>>>Я тут для себя сейчас таблицу составил, где у меня три способа генерации пути (общий, по типу, по дате) и 2 провайдера -- aws s3 и фс. Всего шесть вариантов. S>·>Но эти способы генерации используются независимо, как я понял. Зачем их пихать в один метод|интерфейс? S>Независимо, но работу выполняют одну и ту же -- генерируют путь к файлу в зависимости от [общий, по типу, по датеъ + тип хранилища -- фс или s3. Т.е. 2 степени свободы.
то что выполняют одну и ту же работу это слишком расплывчато. В общем случае все методы выполняют одну и ту же работу — изменяют данные.
S>>>Ну если я использую s3, то там на самом деле всего 4 (3 для фс и один s3 для всех остальных). S>·>Это предложение я не смог распарсить. S>Два типа хранилища (см. выше) и три типа генерации пути. Всего шесть. Для aws s3 по сути у всех будет одно и тоже -- имя файла + гуид. Значит всего четыре способа.
В классах-пользователях что будет удобнее всего?
У меня проще вариант был.
Раз эти реализации не вазаимозаменяемые, а работающие паралельно то что-то вроде
public abstract class ParamBase { }
public class ParamType1 : ParamBase { ... }
public class ParamType2 : ParamBase { ... }
public class UserStorageInfo : IUserStorageInfo
{
private readonly IDictionary _impl;
public UserStorageInfo ()
{
_impl = new ConcurrentDictionary<Type, Lazy<IUserStorageInfoProvider>>();
_impl.Add(typeof(ParamType1), new Lazy<UserStorageInfoProvider1>);
_impl.Add(typeof(ParamType2), new Lazy<UserStorageInfoProvider2>);
}
public string GenerateStoragePathForFile(ParamBase p)
{
return _impl[p.GetType].Value.GenerateStoragePathForFile(p);
}
}
А уже каждый UserStorageInfoProviderN приводит пк своему типу.
Здравствуйте, Doc, Вы писали:
Doc>Здравствуйте, Sharov, Вы писали:
S>>Так?
Doc>У меня проще вариант был. Doc>Раз эти реализации не вазаимозаменяемые, а работающие паралельно то что-то вроде
Doc>
Doc>public abstract class ParamBase { }
Doc>public class ParamType1 : ParamBase { ... }
Doc>public class ParamType2 : ParamBase { ... }
Doc>public class UserStorageInfo : IUserStorageInfo
Doc>{
Doc> private readonly IDictionary _impl;
Doc> public UserStorageInfo ()
Doc> {
Doc> _impl = new ConcurrentDictionary<Type, Lazy<IUserStorageInfoProvider>>();
Doc> _impl.Add(typeof(ParamType1), new Lazy<UserStorageInfoProvider1>);
Doc> _impl.Add(typeof(ParamType2), new Lazy<UserStorageInfoProvider2>);
Doc> }
Doc> public string GenerateStoragePathForFile(ParamBase p)
Doc> {
Doc> return _impl[p.GetType].Value.GenerateStoragePathForFile(p);
Doc> }
Doc>}
Doc>
Doc>А уже каждый UserStorageInfoProviderN приводит пк своему типу.
Это может быть универсальный, но явно сложней. Я же стараюсь по максимуму использовать такие инструменты как наследование и параметрические типы.
Здравствуйте, ·, Вы писали: ·>Здравствуйте, Sharov, Вы писали: S>>>>TDD я не использую. S>>·>Так начинай. S>>Нет, у меня есть интеграционные тесты. Над проектом я работаю один, много возни, чтобы их писать и поддерживать. ·>Хорошие юнит-тесты как хорошие дети. Вначале ты поддерживаешь их, а потом они тебя.
Не спорю, но не сложилось на этом проекте. В другом, небольшом, есть. S>>>>Ну если я использую s3, то там на самом деле всего 4 (3 для фс и один s3 для всех остальных). S>>·>Это предложение я не смог распарсить. S>>Два типа хранилища (см. выше) и три типа генерации пути. Всего шесть. Для aws s3 по сути у всех будет одно и тоже -- имя файла + гуид. Значит всего четыре способа. ·>В классах-пользователях что будет удобнее всего? ·>
·>или в разных классах по разному? Какие именно сценарии _использования_ ты ожидаешь?
пока использую так:
код
public interface IUserStorageInfo<T> where T:StorageData
{
#region Properties
StorageProvider CurrentStorageProvider { get; }
#endregion
#region Methods
string GenerateDataLinkForFile(T storage);
#endregion
}
public abstract class StorageData
{
#region Properties
public abstract Guid Guid { get; }
public abstract string Filename { get; }
#endregion
#region Methods
public abstract string GenerateRelativeLinkForFile();
#endregion
}
Классы, импл. IUserStorageInfo окончательно формируют путь, добавляя инф-ию к RelativeLink. Для aws s3 это вообще не нужно -- там будет guid+filename писаться в базу. Это все скорее к StorageProvider типа фс.
У StorageData пока что три наследника -- пути по типу файла, по временным каким-то параметрам, и простой вариант по умолчанию. Как-то так.
Здравствуйте, Sharov, Вы писали:
S>·>или в разных классах по разному? Какие именно сценарии _использования_ ты ожидаешь? S>пока использую так:
Это не _использование_ функционала. Это сам функционал. Использование я ожидаю примерно такое:
public void onButtonClick()
{
string path = storageInfo.GeneratePath(fileNameInputBox.getText(), new Guid()....);
...
use(path);
}
S>Классы, импл. IUserStorageInfo окончательно формируют путь, добавляя инф-ию к RelativeLink. Для aws s3 это вообще не нужно -- там будет guid+filename писаться в базу. Это все скорее к StorageProvider типа фс. S>У StorageData пока что три наследника -- пути по типу файла, по временным каким-то параметрам, и простой вариант по умолчанию. Как-то так.
Не люблю наследование.
Я до сих пор не понимаю — чем такой вариант не устраивает-то?
Здравствуйте, ·, Вы писали:
S>>У StorageData пока что три наследника -- пути по типу файла, по временным каким-то параметрам, и простой вариант по умолчанию. Как-то так. ·>Не люблю наследование.
А я люблю -- мощнейшая штука.
·>Я до сих пор не понимаю — чем такой вариант не устраивает-то? ·>
В общем и целом нормально. Как-то так оно и было, в лоб, что называется. И если надо быстро и может даже качественно, то сойдет. Но.
Мешается работа с aws и файловой системой, т.е. SRP летит. Не очень страшно, конечно. Далее, потенциально может быть очень много строк кода, т.е. это такой монолит в себе. Я же пока решил метод GenerateFilesystemPathForFile
разнести по классам, а параметры сделать параметрами конструктора. Мне этот подход кажется гибче. Плюс, если добавиться что-то общее, то можно будет использовать template method базового класса. Т.е. больше возможности для маневра в будущем, легче адаптироваться к изменениям.
Здравствуйте, Sharov, Вы писали:
S>>>У StorageData пока что три наследника -- пути по типу файла, по временным каким-то параметрам, и простой вариант по умолчанию. Как-то так. S>·>Не люблю наследование. S>А я люблю -- мощнейшая штука.
Поэтому его надо использовать обдуманно, а не пихать везде куда пихается.
S>В общем и целом нормально. Как-то так оно и было, в лоб, что называется. И если надо быстро и может даже качественно, то сойдет. Но. S>Мешается работа с aws и файловой системой, т.е. SRP летит.
Никуда он не летит. Ты сам волен выбирать удобное тебе Responsibility. UserStorageInfo как юнит для работы с информации о storage вполне single.
S> Не очень страшно, конечно. Далее, потенциально может быть очень много строк кода, т.е. это такой монолит в себе.
Много строк — раздели, тривиальный рефакторинг:
public class AwsS3UserStorageInfo
{
string GeneratePathForFile(string fileName,string projectName,Guid entityGuid)
{
code-code-code
}
}
public class FilesystemUserStorageInfo
{
string GeneratePathForFile(string fileName,string projectName,Guid entityGuid);
{
code-code-code
}
string GeneratePathForFile(string fileName,EntityTypeEnum entityTypeEnum);
{
code-code-code
}
string GeneratePathForFile(string fileName,Datetime dt);
{
code-code-code
}
}
S> Я же пока решил метод GenerateFilesystemPathForFile S>разнести по классам, а параметры сделать параметрами конструктора. Мне этот подход кажется гибче.
Гибче в чём? В каких сценариях использования?
S>Плюс, если добавиться что-то общее, то можно будет использовать template method базового класса. Т.е. больше возможности для маневра в будущем, легче адаптироваться к изменениям.
Что "что-то"? Есть какое-то конретное требование? Озвучь. Или просто "маловато будет"?
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Здравствуйте, Sharov, Вы писали: S>А чего так все ополчились против общего объекта, который бы каждый использовал на свое усмотрение?
Если я правильно понял, то есть три реализации и по три потребности. Итого — девять возможных комбинаций. Из них как минимум 6 заведомо неработоспособные.
Всё, что вы делаете — скрываете этот факт от читателя кода, провоцируя его на ошибку, которая вылезет только в рантайме.
Внимание, вопрос: нахрена? У вас слишком низкие затраты на разработку, и вы хотите нагреть атмосферу за счёт бюджета?
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, ·, Вы писали:
S>>В общем и целом нормально. Как-то так оно и было, в лоб, что называется. И если надо быстро и может даже качественно, то сойдет. Но. S>>Мешается работа с aws и файловой системой, т.е. SRP летит. ·>Никуда он не летит. Ты сам волен выбирать удобное тебе Responsibility. UserStorageInfo как юнит для работы с информации о storage вполне single.
Что значит не летит? Для aws пути генерируются одним образом, своя спецификация, для фс генерируется другим образом. Мешать все это дело на зачем. S>> Не очень страшно, конечно. Далее, потенциально может быть очень много строк кода, т.е. это такой монолит в себе. ·>Много строк — раздели, тривиальный рефакторинг: ·>
Во, так и сделал. Только вместо трех методов один интерфейс с абстрактным классом
код
public abstract class StorageData
{
#region Properties
public abstract Guid Guid { get; }
public abstract string Filename { get; }
#endregion
#region Methods
public abstract string GenerateRelativeLinkForFile();
#endregion
}
public interface IUserStorageInfo<T> where T : StorageData
{
#region Methods
string GenerateDataLinkForFile(T storage);
#endregion
}
public class FileSystemStorage<T> : IUserStorageInfo<T>, where T : StorageData
{
//....
}
public class AmazonS3Storage : IUserStorageInfo<StorageData>
{
//...
}
Амазону ничего кроме названия файла и гуида пока не требуется. S>> Я же пока решил метод GenerateFilesystemPathForFile S>>разнести по классам, а параметры сделать параметрами конструктора. Мне этот подход кажется гибче. ·>Гибче в чём? В каких сценариях использования?
Пока гибче для меня, для рефакторинга и т.д. Т.е. из мелких частей конструирую что-то более крупное. S>>Плюс, если добавиться что-то общее, то можно будет использовать template method базового класса. Т.е. больше возможности для маневра в будущем, легче адаптироваться к изменениям. ·>Что "что-то"? Есть какое-то конретное требование? Озвучь. Или просто "маловато будет"?
На сегодня требований нет. А как на завтра У меня aws s3 появился когда я пришел в смежный отдел для интеграционного тестирования, мол у меня все готово. И выяснилось, что файлы определенного типа надо складывать в s3. С таким подходом может быть все что угодно.
Здравствуйте, Sinclair, Вы писали:
S>Здравствуйте, Sharov, Вы писали: S>>А чего так все ополчились против общего объекта, который бы каждый использовал на свое усмотрение? S>Если я правильно понял, то есть три реализации и по три потребности. Итого — девять возможных комбинаций. Из них как минимум 6 заведомо неработоспособные.
Я выше посчитал -- 2 реализации по 3 потребности. Из них 3 совпадают пока друг с другом, итого 4.
S>Всё, что вы делаете — скрываете этот факт от читателя кода, провоцируя его на ошибку, которая вылезет только в рантайме.
Согласен, это был неверный подход. Выше я уже привел вариант более осмысленного решения. Читателем этого кода на 99% буду только я.
S>Внимание, вопрос: нахрена? У вас слишком низкие затраты на разработку, и вы хотите нагреть атмосферу за счёт бюджета?
Причем здесь это, как это относится к исходной проблеме?
Здравствуйте, Sharov, Вы писали: S>>>Мешается работа с aws и файловой системой, т.е. SRP летит. S>·>Никуда он не летит. Ты сам волен выбирать удобное тебе Responsibility. UserStorageInfo как юнит для работы с информации о storage вполне single. S>Что значит не летит? Для aws пути генерируются одним образом, своя спецификация, для фс генерируется другим образом. Мешать все это дело на зачем.
И где тут _responsibility_? S> S>Во, так и сделал. Только вместо трех методов один интерфейс с абстрактным классом S>
код
S>
S> public abstract class StorageData
S> {
S> #region Properties
S> public abstract Guid Guid { get; }
S> public abstract string Filename { get; }
S> #endregion
S> #region Methods
S> public abstract string GenerateRelativeLinkForFile();
S> #endregion
S> }
S> public interface IUserStorageInfo<T> where T : StorageData
S> {
S> #region Methods
S> string GenerateDataLinkForFile(T storage);
S> #endregion
S> }
S>public class FileSystemStorage<T> : IUserStorageInfo<T>, where T : StorageData
S> {
S> //....
S> }
S>public class AmazonS3Storage : IUserStorageInfo<StorageData>
S>{
S>//...
S>}
S>
Вместо четырёх методов и двух типов у тебя тут больше 4 типов, до хрена методов, ещё и генерики. "А хотя бы я и жадничаю, зато от чистого сердца." S>Пока гибче для меня, для рефакторинга и т.д. Т.е. из мелких частей конструирую что-то более крупное.
"А теперь я с этой палкой во что хочешь превращусь. В теле такая приятная гибкость образовалась…" S>>>Плюс, если добавиться что-то общее, то можно будет использовать template method базового класса. Т.е. больше возможности для маневра в будущем, легче адаптироваться к изменениям. S>·>Что "что-то"? Есть какое-то конретное требование? Озвучь. Или просто "маловато будет"? S>На сегодня требований нет. А как на завтра У меня aws s3 появился когда я пришел в смежный отдел для интеграционного тестирования, мол у меня все готово. И выяснилось, что файлы определенного типа надо складывать в s3. С таким подходом может быть все что угодно.
Вот появятся требования — будешь думать как их лучше всего реализовать. А пока "закладываться на будущее" — чистые фантазии.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Здравствуйте, ·, Вы писали: ·>Здравствуйте, Sharov, Вы писали: S>>>>Мешается работа с aws и файловой системой, т.е. SRP летит. S>>·>Никуда он не летит. Ты сам волен выбирать удобное тебе Responsibility. UserStorageInfo как юнит для работы с информации о storage вполне single. S>>Что значит не летит? Для aws пути генерируются одним образом, своя спецификация, для фс генерируется другим образом. Мешать все это дело на зачем. ·>И где тут _responsibility_?
Да, не прав. Поспешил. S>> S>>Во, так и сделал. Только вместо трех методов один интерфейс с абстрактным классом S>>
код
S>>
S>> public abstract class StorageData
S>> {
S>> #region Properties
S>> public abstract Guid Guid { get; }
S>> public abstract string Filename { get; }
S>> #endregion
S>> #region Methods
S>> public abstract string GenerateRelativeLinkForFile();
S>> #endregion
S>> }
S>> public interface IUserStorageInfo<T> where T : StorageData
S>> {
S>> #region Methods
S>> string GenerateDataLinkForFile(T storage);
S>> #endregion
S>> }
S>>public class FileSystemStorage<T> : IUserStorageInfo<T>, where T : StorageData
S>> {
S>> //....
S>> }
S>>public class AmazonS3Storage : IUserStorageInfo<StorageData>
S>>{
S>>//...
S>>}
S>>
·>Вместо четырёх методов и двух типов у тебя тут больше 4 типов, до хрена методов, ещё и генерики. "А хотя бы я и жадничаю, зато от чистого сердца."
Мне Ваш вариант в принципе подходит, и если я совсем сильно закопаюсь в рефакторинге со своей "специализацией", то откачусь (git revert) к нему.
S>>Пока гибче для меня, для рефакторинга и т.д. Т.е. из мелких частей конструирую что-то более крупное. ·>"А теперь я с этой палкой во что хочешь превращусь. В теле такая приятная гибкость образовалась…"
Здравствуйте, Sharov, Вы писали: S>Причем здесь это, как это относится к исходной проблеме?
Очень просто: вы вместо того, чтобы просто писать код, ищете паттерны, увеличивая количество мусорного кода.
Ваш топик выглядит примерно так: "у меня есть код, который выводит строкии форматирует диск. Точнее, строки на экран пишет один модуль, а форматирует диск — другой. Я придумал интерфейс IFormat, у которого две функции FormatDisk(string diskName) и FormatString(string format, [Params] object arg[]). Есть два класса, каждый реализует по одному методу из двух, а второй бросает NotImplementedException. Каждый модуль будет получать реализацию ILogFormat, сконфигурированную снаружи.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Sinclair, Вы писали:
S>Здравствуйте, Sharov, Вы писали: S>>Причем здесь это, как это относится к исходной проблеме? S>Очень просто: вы вместо того, чтобы просто писать код, ищете паттерны, увеличивая количество мусорного кода. S>Ваш топик выглядит примерно так: "у меня есть код, который выводит строкии форматирует диск. Точнее, строки на экран пишет один модуль, а форматирует диск — другой. Я придумал интерфейс IFormat, у которого две функции FormatDisk(string diskName) и FormatString(string format, [Params] object arg[]). Есть два класса, каждый реализует по одному методу из двух, а второй бросает NotImplementedException. Каждый модуль будет получать реализацию ILogFormat, сконфигурированную снаружи.
У меня был интерфейс, реализуя который некоторый классы использовали только одни метод, а остальные бросали. Точка предложил вполне вменяемый подход -- http://rsdn.org/forum/design/6685137.1
]здесь[/url], которым можно и воспользоваться. Исходная проблема состояла в том, что ни о какой похожести разных ф-ий речи не было -- генерировать путь к файлу на основе разных данных об этом файле (см. ссылку выше). Я пока каждый метод из предложенного выше решения убрал за соотв. класс, а в конструктор передаю параметры метода. Это безусловно выглядит сложнее, но мне кажется и удобнее (выше я уже описал почему).