У этого интерфейсам есть разные (пока три) реализации. Причем каждой реализации нужен только один GenerateStoragePathForFile, все остальные честно бросают NotImplementedException. Полный ужас и идиотизм, согласен. Продолбал момент, что называется. И вот сейчас задумался как это дело унифицировать. Единственное, что приходит на ум -- создать метод типа
И каждая реализация будет лезть в этот StorageData по потребностям. Вроде вариант нормальный, взывающая сторона заполняет релевантной инф-ей объект StorageData, и каждая реализация берет то, что нужно. Можно наследоваться от класса, и передавать параметры в конструкторе. Проблема в том, что некоторые реализации я пихаю в контейнер(TinyIoc), и все используют этот экземпляр (по сути singleton).
Что скажете, посоветуете? Мне пока вариант с унифицированным объектом больше всего нравится.
Здравствуйте, Sharov, Вы писали:
S>У этого интерфейсам есть разные (пока три) реализации. Причем каждой реализации нужен только один GenerateStoragePathForFile, все остальные честно бросают NotImplementedException. Полный ужас и идиотизм, согласен. Продолбал момент, что называется. И вот сейчас задумался как это дело унифицировать.
Какой смысл в юнификации? Не проще ли выбросить интерфейс и оставить три конкретных класса с соответствующим методом каждый?
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Здравствуйте, Sharov, Вы писали:
S>Такая ситуация.
UPD Хрень написал, не так понял вопрос. Поправлено.
+1 к мнению "·". Зачем тут вообще интерфейс, если общего API нет?
общий план действий
1. Выбросить из головы текущую реализацию.
2. Сделать API по реальным сценариям использования. Как я понял, тут общей части нет вообще, поэтому можно с чистой совестью делать свою реализацию для каждого из типов.
3. _Если_ реализации по факту переиспользуют логику — рассмотреть возможность вытащить код в базовый тип / хелпер. Сомнительно для примера топикстартера.
4. _Если_ будет несколько вариантов реализации — рассмотреть вариант с базовым типом. Вычёркиваем, как я понял.
5. _Если_ будет несколько вариантов реализации, предоставляемых через public API — в дополнение к базовому типу завести интерфейс. Тож самое, вычёркиваем.
6. Сравнить текущую реализацию с тем, что планируется сделать, набросать план рефакторинга / тесты, выполнять
Здравствуйте, ·, Вы писали:
·>Здравствуйте, Sharov, Вы писали:
S>>У этого интерфейсам есть разные (пока три) реализации. Причем каждой реализации нужен только один GenerateStoragePathForFile, все остальные честно бросают NotImplementedException. Полный ужас и идиотизм, согласен. Продолбал момент, что называется. И вот сейчас задумался как это дело унифицировать. ·>Какой смысл в юнификации? Не проще ли выбросить интерфейс и оставить три конкретных класса с соответствующим методом каждый?
Искать(resolve) в контейнере через интерфейс, как вариант. А как от там и кем был сконфигурирован вопрос десятый.
Здравствуйте, Sharov, Вы писали:
S>·>Какой смысл в юнификации? Не проще ли выбросить интерфейс и оставить три конкретных класса с соответствующим методом каждый? S>Искать(resolve) в контейнере через интерфейс, как вариант. А как от там и кем был сконфигурирован вопрос десятый.
Это плохой вариант, негодный. Не надо интерфейсы создавать лишь потому что "контейнер". Но если это такая политика партии и бороться невозможно — создай три интерфейса, пусть хлебают, за подробностями сюда
Здравствуйте, ·, Вы писали:
·>Здравствуйте, Sharov, Вы писали:
S>>·>Какой смысл в юнификации? Не проще ли выбросить интерфейс и оставить три конкретных класса с соответствующим методом каждый? S>>Искать(resolve) в контейнере через интерфейс, как вариант. А как от там и кем был сконфигурирован вопрос десятый.
·>Это плохой вариант, негодный. Не надо интерфейсы создавать лишь потому что "контейнер". Но если это такая политика партии и бороться невозможно — создай три интерфейса, пусть хлебают, за подробностями сюда
Согласен, читал. TinyIoc маленький, опереточный. Он вообще мною был использован по началу для интеграционных тестов. Потом пошел в обычный код.
Далее, интерфейс создавался "потому что общая семантика". Т.е. имеется три класса, которые генерят путь для сохранения файла (пусть даже в S3). Разница между классами в том, какой сервис их использует. Одному нужно учитывать время для сохранения, другому тип сохраняемого файла, третьему проект. Классика же, иметь код, который мог бы всеми (пере)использоваться -- т.е. сервис запихивает нужную ему реализацию в контейнер, затем эту использует.
А чего так все ополчились против общего объекта, который бы каждый использовал на свое усмотрение?
Здравствуйте, Sharov, Вы писали:
S>Далее, интерфейс создавался "потому что общая семантика". Т.е. имеется три класса, которые генерят путь для сохранения файла (пусть даже в S3). Разница между классами в том, какой сервис их использует. Одному нужно учитывать время для сохранения, другому тип сохраняемого файла, третьему проект. Классика же, иметь код, который мог бы всеми (пере)использоваться -- т.е. сервис запихивает нужную ему реализацию в контейнер, затем эту использует.
Так ведь нет кода переиспользуемого. Или в начальном вопросе что-то упущено, было только сказано что три независимые имплементации. То что общая семантика можно выразить по-другому, например, разместив классы в одном и том же namespace/package.
S>А чего так все ополчились против общего объекта, который бы каждый использовал на свое усмотрение?
Потому что KISS.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Здравствуйте, Sharov, Вы писали:
S>Далее, интерфейс создавался "потому что общая семантика".
Ну, пока получается, что общего не больше, чем между поиском в ФС и поиском контрола на форме. Смысл тут в общем интерфейсе?
S>т.е. сервис запихивает нужную ему реализацию в контейнер, затем эту использует.
А напрямую заиспользовать, без промежуточных запихнул-достал, почему нельзя?
S>А чего так все ополчились против общего объекта, который бы каждый использовал на свое усмотрение?
Потому что ваш контракт врёт пользователю: 2/3 комбинаций из IUserStorageInfo и StorageData или бросают NotImplementedException, или требуют заполнения игнорируемых по факту полей. Зашибись юзабилити
Здравствуйте, Sinix, Вы писали:
S>Здравствуйте, Sharov, Вы писали:
S>>Далее, интерфейс создавался "потому что общая семантика". S>Ну, пока получается, что общего не больше, чем между поиском в ФС и поиском контрола на форме. Смысл тут в общем интерфейсе?
Общее -- генерация пути хранения файла в зависимости от используемого хранилища -- фс или aws s3 -- и в зависимости от используемого сервиса. Для каждого сервиса нужно генерировать путь со своими нюансами.
S>>т.е. сервис запихивает нужную ему реализацию в контейнер, затем эту использует. S>А напрямую заиспользовать, без промежуточных запихнул-достал, почему нельзя?
Можно, по сути так и происходит. Но поскольку четкого тз нет, решил притащить контейнер, чтобы можно было в случае чего протаскивать объекты. Пока решил протаскивать объекты, отвечающие за генерацию пути. Знаю, звучит прикольно.
S>>А чего так все ополчились против общего объекта, который бы каждый использовал на свое усмотрение? S>Потому что ваш контракт врёт пользователю: 2/3 комбинаций из IUserStorageInfo и StorageData или бросают NotImplementedException, или требуют заполнения игнорируемых по факту полей. Зашибись юзабилити
S>>Далее, интерфейс создавался "потому что общая семантика". Т.е. имеется три класса, которые генерят путь для сохранения файла (пусть даже в S3). Разница между классами в том, какой сервис их использует. Одному нужно учитывать время для сохранения, другому тип сохраняемого файла, третьему проект. Классика же, иметь код, который мог бы всеми (пере)использоваться -- т.е. сервис запихивает нужную ему реализацию в контейнер, затем эту использует.
·>Так ведь нет кода переиспользуемого. Или в начальном вопросе что-то упущено, было только сказано что три независимые имплементации. То что общая семантика можно выразить по-другому, например, разместив классы в одном и том же namespace/package.
Кода нет, потому что плясать для каждой сущности приходится отдельно. А так можно было бы prgoramming against interface.
S>>А чего так все ополчились против общего объекта, который бы каждый использовал на свое усмотрение? ·>Потому что KISS.
Ну и что же сложного в общем полиморфном объекте, который каждый использует на свое усмотрение?
Здравствуйте, stomsky, Вы писали:
S>Здравствуйте, Sharov, Вы писали:
S>>Что скажете, посоветуете? Мне пока вариант с унифицированным объектом больше всего нравится.
S>Может быть эти параметры, специфические для каждого класса через конструкторы передавать без универсального объекта StorageData? S>Вот так: S>
S>
Да, это был запасной вариант. И судя по реакции публики не самый худший...
Здравствуйте, Sharov, Вы писали:
S>Общее -- генерация пути хранения файла в зависимости от используемого хранилища -- фс или aws s3 -- и в зависимости от используемого сервиса. Для каждого сервиса нужно генерировать путь со своими нюансами.
Ну как и сказал — вы пытаетесь ответственность хранилища вытащить в отдельное API. Кому оно поможет? Пользователям — нет, реализаторам хранилищ — тож нет, коду, который будет выступать как посредник между хранилищами и клиентским API — снова нет. У вас нет по факту общего API. Спрятать N несовместимых перегрузок за одной с классом параметров — это не оно
Если всё-таки хочется абстрагироваться от хранилища любой ценой — см последовательность выше: собираем public API по реальным сценариям, смотрим, чем придётся пожертвовать ради сохранения простоты API, реализуем. Подход "обобщил, потому что внешне похоже" на практике не работает — абстракции начинают протекать систематически.
S>>А напрямую заиспользовать, без промежуточных запихнул-достал, почему нельзя? S>Можно, по сути так и происходит. Но поскольку четкого тз нет, решил притащить контейнер
А не проще сначала написать код, посмотреть, что по факту дублируется, и только потом заводить общий интерфейс? Может, у вас вообще ничего общего не останется — смысл усложнять код на ровном месте?
Здравствуйте, Sharov, Вы писали:
S>·>Так ведь нет кода переиспользуемого. Или в начальном вопросе что-то упущено, было только сказано что три независимые имплементации. То что общая семантика можно выразить по-другому, например, разместив классы в одном и том же namespace/package. S>Кода нет, потому что плясать для каждой сущности приходится отдельно. А так можно было бы prgoramming against interface.
Так надо с кода начинать, а не с абстрактных всемогуторов, которые непонятно что должны делать конкретно.
Попробуй TDD — опиши необходимые сценарии в виде тестов и начни свой пляс от них.
S>>>А чего так все ополчились против общего объекта, который бы каждый использовал на свое усмотрение? S>·>Потому что KISS. S>Ну и что же сложного в общем полиморфном объекте, который каждый использует на свое усмотрение?
Так ты попробуй написать оба варианта кода — три независимых класса, которые делают то что надо. А потом своё решение — абстрактный класс, три полиморфных потомка, конструкторы для них. Потом ещё сам сервис, который как-то диспатчит по полиморфному типу (виртуальный метод? визитор? switch?) и потом попробуй представить как это всё потом _можно_ связывать и какие из комбинаций работоспособны, а какие нет.
И сам увидишь, что твоё решение гораздо сложнее, без каких-либо преимуществ.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Здравствуйте, ·, Вы писали:
·>Здравствуйте, Sharov, Вы писали:
S>>·>Так ведь нет кода переиспользуемого. Или в начальном вопросе что-то упущено, было только сказано что три независимые имплементации. То что общая семантика можно выразить по-другому, например, разместив классы в одном и том же namespace/package. S>>Кода нет, потому что плясать для каждой сущности приходится отдельно. А так можно было бы prgoramming against interface. ·>Так надо с кода начинать, а не с абстрактных всемогуторов, которые непонятно что должны делать конкретно.
Код надо продумать, абстрактные всемогуторы помогают начать думать в правильном направлении.
·>Попробуй TDD — опиши необходимые сценарии в виде тестов и начни свой пляс от них.
TDD я не использую.
S>>>>А чего так все ополчились против общего объекта, который бы каждый использовал на свое усмотрение? S>>·>Потому что KISS. S>>Ну и что же сложного в общем полиморфном объекте, который каждый использует на свое усмотрение? ·>Так ты попробуй написать оба варианта кода — три независимых класса, которые делают то что надо. А потом своё решение — абстрактный класс, три полиморфных потомка, конструкторы для них. Потом ещё сам сервис, который как-то диспатчит по полиморфному типу (виртуальный метод? визитор? switch?) и потом попробуй представить как это всё потом _можно_ связывать и какие из комбинаций работоспособны, а какие нет. ·>И сам увидишь, что твоё решение гораздо сложнее, без каких-либо преимуществ.
Я тут для себя сейчас таблицу составил, где у меня три способа генерации пути (общий, по типу, по дате) и 2 провайдера -- aws s3 и фс. Всего шесть вариантов. Ну если я использую s3, то там на самом деле всего 4 (3 для фс и один s3 для всех остальных). В идеале хотелось бы накатать какой-нибудь обобщенный класс с двумя параметрами-типами -- способ генерации и тип хранилища. Ну либо принимать два соотв. интерфейса. Как-то так.
Здравствуйте, Sharov, Вы писали:
S>>>Кода нет, потому что плясать для каждой сущности приходится отдельно. А так можно было бы prgoramming against interface. S>·>Так надо с кода начинать, а не с абстрактных всемогуторов, которые непонятно что должны делать конкретно. S>Код надо продумать, абстрактные всемогуторы помогают начать думать в правильном направлении.
Может быть и помогают думать, не знаю, все думают по-разному... но по каким критериям ты определяешь правильность направления?
S>·>Попробуй TDD — опиши необходимые сценарии в виде тестов и начни свой пляс от них. S>TDD я не использую.
Так начинай.
S>·>Так ты попробуй написать оба варианта кода — три независимых класса, которые делают то что надо. А потом своё решение — абстрактный класс, три полиморфных потомка, конструкторы для них. Потом ещё сам сервис, который как-то диспатчит по полиморфному типу (виртуальный метод? визитор? switch?) и потом попробуй представить как это всё потом _можно_ связывать и какие из комбинаций работоспособны, а какие нет. S>·>И сам увидишь, что твоё решение гораздо сложнее, без каких-либо преимуществ. S>Я тут для себя сейчас таблицу составил, где у меня три способа генерации пути (общий, по типу, по дате) и 2 провайдера -- aws s3 и фс. Всего шесть вариантов.
Но эти способы генерации используются независимо, как я понял. Зачем их пихать в один метод|интерфейс?
S>Ну если я использую s3, то там на самом деле всего 4 (3 для фс и один s3 для всех остальных).
Это предложение я не смог распарсить.
S>В идеале хотелось бы накатать какой-нибудь обобщенный класс с двумя параметрами-типами -- способ генерации и тип хранилища. Ну либо принимать два соотв. интерфейса. Как-то так.
Ещё раз — начни с вариантов использования. Какие сценарии-то?
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Здравствуйте, ·, Вы писали:
S>>Код надо продумать, абстрактные всемогуторы помогают начать думать в правильном направлении. ·>Может быть и помогают думать, не знаю, все думают по-разному... но по каким критериям ты определяешь правильность направления?
Главное начать, а там посмотрим.
S>>TDD я не использую. ·>Так начинай.
Нет, у меня есть интеграционные тесты. Над проектом я работаю один, много возни, чтобы их писать и поддерживать.
S>>Я тут для себя сейчас таблицу составил, где у меня три способа генерации пути (общий, по типу, по дате) и 2 провайдера -- aws s3 и фс. Всего шесть вариантов. ·>Но эти способы генерации используются независимо, как я понял. Зачем их пихать в один метод|интерфейс?
Независимо, но работу выполняют одну и ту же -- генерируют путь к файлу в зависимости от [общий, по типу, по датеъ + тип хранилища -- фс или s3. Т.е. 2 степени свободы.
S>>Ну если я использую s3, то там на самом деле всего 4 (3 для фс и один s3 для всех остальных). ·>Это предложение я не смог распарсить.
Два типа хранилища (см. выше) и три типа генерации пути. Всего шесть. Для aws s3 по сути у всех будет одно и тоже -- имя файла + гуид. Значит всего четыре способа.
S>>В идеале хотелось бы накатать какой-нибудь обобщенный класс с двумя параметрами-типами -- способ генерации и тип хранилища. Ну либо принимать два соотв. интерфейса. Как-то так. ·>Ещё раз — начни с вариантов использования. Какие сценарии-то?
Генерировать путь к файлу в фс в зависимости от даты, генерировать путь к файлу в фс в зависимости от типа файла, генерировать путь к файлу в aws s3 в зависимости от даты и т.д. Все это можно аккуратно убрать за интерфейсы либо ad-hoc полиморфизм.
Здравствуйте, stomsky, Вы писали:
S>Может быть эти параметры, специфические для каждого класса через конструкторы передавать без универсального объекта StorageData?
Какой-то странный вариант интерфейс получается. Для генерации скажем 10 Storage Path надо будет 10 раз дернуть сначала фабрику и потом уже сам интерфейс.
Мне кажется это те же яйца что и были, только over-engineered.
Здравствуйте, Sharov, Вы писали:
S>Что скажете, посоветуете? Мне пока вариант с унифицированным объектом больше всего нравится.
Унифицированный объект ломает SRP т.к. отвечает за все варианты сразу. Это заставит и работающие с ним классы быть универсальными. Плюс без доп танцев с валидацией объект легко можно загнать в некорректное состояние. А валидировать ему надо уметь все состояния.
Идей как по другому 2
1) Сделать фасад, за которым будут скрываться небольшие интерфейсы + реализации для конкретных параметров.
Хорошо если это все будет жить синглтоном на уровне контейнера.
2) Сделать базовый абстрактный класс-маркер для параметров, наследники только с нужными свойствами. Принимать в интерфейсе базовый класс, от в зависимости от реального типа дергать небольшой класс-реализацию (каждый из которых Lazy). По сути можно завести что-то типа Dictionary<Type, Lazy<IPathGenerator>.
Здравствуйте, Doc, Вы писали: Doc>Идей как по другому 2 Doc>1) Сделать фасад, за которым будут скрываться небольшие интерфейсы + реализации для конкретных параметров. Doc>Хорошо если это все будет жить синглтоном на уровне контейнера. Doc>2) Сделать базовый абстрактный класс-маркер для параметров, наследники только с нужными свойствами. Принимать в интерфейсе базовый класс, от в зависимости от реального типа дергать небольшой класс-реализацию (каждый из которых Lazy). По сути можно завести что-то типа Dictionary<Type, Lazy<IPathGenerator>.
Так?
public abstract class StorageData
{
}
public class TemporalStorageData: StorageData
{
//какие-то свои поля
}
public interface IUserStorageInfo2<T> where T:StorageData
{
#region Properties
StorageProvider CurrentStorageProvider { get; }
#endregion
#region Methods
string GenerateDataLinkForFile(string filePath, T data);
#endregion
}
public class TestStorage:IUserStorageInfo2<TemporalStorageData>
{
public StorageProvider CurrentStorageProvider { get; private set; }
public string GenerateDataLinkForFile(string filePath, TemporalStorageData data)
{
throw new NotImplementedException();
}
}