Комментарии 84
Я так понимаю что объяснение — «Статичный класс не имеет инстансов, а Singleton имеет только один», слишком банальное?
+16
Сингелтон — это логическая бомба, его использование почти всегда неоправданно. Основной его минус состоит в том, что он активно мешает масштабированию системы.
+7
Согласен. Но иногда некоторые объекты очень удобно делать синглтонами если это не вступает в конфликт с общей архитектурой системы. Ну напрмер некий менеджер сессий SessionManager, репозитарий словарей DictionaryRepository и т.д.
+2
Ну а почему не может быть несколько менеджеров сессий или репозитариев словарей? зачем здесь singleton?
0
Но иногда некоторые объекты очень удобно делать синглтонами
Никто не говорил что не может быть нескольких экземпляров вышеперечисленных объектов :)
0
Ну — значит пример неудачный.
вот этот пример мне показался более удачным: habrahabr.ru/blogs/refactoring/103681/#comment_3262041
вот этот пример мне показался более удачным: habrahabr.ru/blogs/refactoring/103681/#comment_3262041
0
Синглтон — это чудесное спасение :) Основные его плюсы начинают проявляться только после того, как вы вымарываете из интерфейса объекта все намеки на то, что это именно синглтон, а не обычный объект.
0
Синглтон — это чудесное спасение :)
Статья выглядит именно так? Самое интересно что я такой цели не преследовал :)
0
Кстати а что вы имели ввиду под «инрерфейсом»?
0
Это сарказм? Я не понял, что вы хотите сказать, переформулируйте.
0
Хм, видимо действительно непонятно получилось, попробую подробнее, и на всякий случай без привязки к дотнету, как это получилось у автора.
Мы говорим о синглтоне, как об объекте, у которого существует только один экземпляр. Пока это так, есть куча мест, где синглтон может быть выгоден — например, у вас есть объект, который бессмысленно инициализировать каждый раз при вызове, т.к. он все равно не меняется в заданных условиях (объект с разобранными данными http-запроса, объект с данными конфигурационного файла и т.д.). Те, кто не читали о том, что глобальные переменные — зло, запихивают такого рода данные в глобальные переменные, и проигрывают, как только начальные условия меняются (например, в зависимости от имени хоста должны подгружаться разные конфиги). Проигрывают, как правило, и те, кто сделал для объекта «классическую» точку доступа в виде метода getInstance — она теряет свой смысл и сбивает с толку, в случае если объект понадобится перестроить из классического синглтона во что-то другое. Вывод лично у меня простой: о том, что объект работает как синглтон, не должен знать никто снаружи (используем самописный new вместо getInstance). Это помогает как в случаях, когда «традиционный» объект нужно превратить в синглтон, так и в обратных. Проблем при рефакторинге за несколько лет тоже не замечено.
Сорри если опять сумбурно получилось :)
Мы говорим о синглтоне, как об объекте, у которого существует только один экземпляр. Пока это так, есть куча мест, где синглтон может быть выгоден — например, у вас есть объект, который бессмысленно инициализировать каждый раз при вызове, т.к. он все равно не меняется в заданных условиях (объект с разобранными данными http-запроса, объект с данными конфигурационного файла и т.д.). Те, кто не читали о том, что глобальные переменные — зло, запихивают такого рода данные в глобальные переменные, и проигрывают, как только начальные условия меняются (например, в зависимости от имени хоста должны подгружаться разные конфиги). Проигрывают, как правило, и те, кто сделал для объекта «классическую» точку доступа в виде метода getInstance — она теряет свой смысл и сбивает с толку, в случае если объект понадобится перестроить из классического синглтона во что-то другое. Вывод лично у меня простой: о том, что объект работает как синглтон, не должен знать никто снаружи (используем самописный new вместо getInstance). Это помогает как в случаях, когда «традиционный» объект нужно превратить в синглтон, так и в обратных. Проблем при рефакторинге за несколько лет тоже не замечено.
Сорри если опять сумбурно получилось :)
0
(используем самописный new вместо getInstance)
Переопределить оператор new вы сможете в C++. А как же быть с С#? Я не говорю о других, более примитивных CLI-совместимых языках — например VB .NET. Да и вообще сложно себе представить как вы будете с помощью переопределения new реализовывать синглотон. Слушайте — правда очень заинтересовало. Если не сложно — можно примеры кода? Если не можете опубликовать здесь — в личку, я никому их не покажу, честно :)
0
Я специально написал дисклеймер про непривязку к дотнету, т.к. не был уверен, что в шарпе и иже с ним можно баловаться подобным образом. Если нельзя — значит нельзя. В том же перле такие вещи делаются на раз, а сам объект можно хранить в переменной класса.
Таким образом, все нюансы, связанные с единственностью объекта, остаются в самом объекте. При необходимости аналогичным образом можно прозрачно сделать пул и прочие интересности, но наружу ничего не выплывет.
my $self; sub new { my $class = shift; unless ($self) { $self = $class->SUPER::new; # some init code } return $self; }
Таким образом, все нюансы, связанные с единственностью объекта, остаются в самом объекте. При необходимости аналогичным образом можно прозрачно сделать пул и прочие интересности, но наружу ничего не выплывет.
0
Вне всяких сомнений, синглетоны *с глобальной точкой доступа* используют только лохи. Их удобство обманчиво, ведь в большинстве случаев получается та же самая глобальная переменная, а если их (таких синглетонов) становится много (а такое бывает нередко, синглетоны как чипсы: съел один, и тут же хочется второй), то значительно затрудняется рефакторинг. Гораздо разумнее использовать концепцию обращения контроля и её частный случай — внедрение зависимости. Необходимые синглетоны при этом никуда не деваются, но к ним уже нельзя обратиться откуда попало, контейнер сам следит за тем, чтобы разные объекты при создании получали один и тот же экземпляр синглетона, а весь граф взаимодействия объектов в приложении формируется, как правило, в самом начале работы программы.
+4
Почти… но один два синглтона на всё приложение почти всегда оправдано :)
+1
Кроме есть замечания по коду:
нужно заменить на
Ну и рабока с многопоточностью не рассмотрена.
public class Singleton<T> where T : class { private static T _instance; private static T CreateInstance() { ConstructorInfo cInfo = typeof(T).GetConstructor( BindingFlags.Instance | BindingFlags.NonPublic, null, new Type[0], new ParameterModifier[0]); return (T)cInfo.Invoke(null); }
нужно заменить на
public class Singleton<T> where T : class, new() { private static T _instance; protected Singleton() { } private static T CreateInstance() { return new T(); }
Ну и рабока с многопоточностью не рассмотрена.
+1
Вы видимо недопоняли зачем нужен код, использующий поиск конструктора. Предложенный вами вариант будет работать только если класс-синглтон объявлен так:
В противном случае вы получите на этапе компиляции 'ConsoleApplication1.Session' must be a non-abstract type with a public parameterless constructor in order to use it as parameter 'T' in the generic type or method 'ConsoleApplication1.Singleton'.
Ну а поскольку вы будете вынуждены сделать конструктор открытым — возможно будет создать много экземпляров этого класса. Какой же это синглтон?
public class Session : Singleton<Session>
{
public Session()
{
}
public bool IsSessionExpired()
{
return false;
}
}
В противном случае вы получите на этапе компиляции 'ConsoleApplication1.Session' must be a non-abstract type with a public parameterless constructor in order to use it as parameter 'T' in the generic type or method 'ConsoleApplication1.Singleton'.
Ну а поскольку вы будете вынуждены сделать конструктор открытым — возможно будет создать много экземпляров этого класса. Какой же это синглтон?
+2
А работу с многопоточносю я НАМЕРЕННО не стал рассматривать :) Так как, во-первых очень спать хотелось, а во вторых, думаю все вменяемые разработчики уже давно используют Double-Checker для синхронизации доступа к экземпляру синглотона. Например, так:
public class Singleton<T> where T : class
{
// ...
public static T Instance
{
get
{
if (_instance == null)
{
lock (_locker)
{
if (_instance == null)
{
_instance = CreateInstance();
}
}
}
return _instance;
}
}
}
0
а вот про double-checker в последнее время отзываются не очень хорошо:
www.yoda.arachsys.com/csharp/singleton.html
www.yoda.arachsys.com/csharp/singleton.html
+2
В .NET 4 можно не запариваться с базовыми классами для singlton, а писать так
public sealed class Session
{
private static readonly Lazy InstanceField = new Lazy(() => new Session());
private Session()
{
}
public static Session Instance
{
get
{
return InstanceField.Value;
}
}
// ...
}
Кода немного, ничего страшного, зато все безопасно и работает.
+2
Кстати, вместо длинного вызова GetConstructor + Invoke можно было использовать Activator.CreateInstance(typeof(T))
+1
Спасибо за статью. Правда не всегда вопрос поставлен именно так — или синглтон, или статический класс. Не всегда целесообразно логику объекта и логику создания (поиска) объекта помещать в одном месте (синглтон). Если злоупотреблять синглтонами, то получим много дублирующегося кода и как следствие — трудности с расширением архитектуры. В таких ситуациях спасают, например, фабричные методы.
0
Пишу на дельфи, поэтому не совсем понимаю откуда взялось ограничение на наследование. Это в дотнете так сделано, или имеется в виду что-то концептуальное?
0
Это ограничения любого CLI-совместимого языка платформы .NET — С#, VB .NET и пр.
0
Самое забавное, что это хорошее ограничение.
Оно заставляет хорошо продумывать правильную архитектуру и использовать интерфейсы вместо базовых классов.
Composition over Inveritance ;)
Оно заставляет хорошо продумывать правильную архитектуру и использовать интерфейсы вместо базовых классов.
Composition over Inveritance ;)
0
Наследование — не нужно? :)
0
Я не говорю, что не нужно. Я хочу сказать, что отсутствие множественного наследования классов в C# ничуть не мешает. Можно наследовать множество интерфейсов — и это используется везде и по максимуму.
И на мой взгляд лучше не плодить иерархию классов, это делает компоненты кода слишком связанными. Другое дело интерфейсы — это всего лишь контракты и одну реализацию всегда можно «незаметно» заменить другой.
И на мой взгляд лучше не плодить иерархию классов, это делает компоненты кода слишком связанными. Другое дело интерфейсы — это всего лишь контракты и одну реализацию всегда можно «незаметно» заменить другой.
+2
Нет, я не о множественном наследовании (его в дельфи тоже нет). Насколько я понял из топика, вообще нельзя наследовать от статического класса.
0
А, ну это да.
Наследование в .NET определено только для чего-то «экземплярного», т.е., например, классов. И методы статических классов определяются только на уровне типов, но не экземпляров.
Наследование в .NET определено только для чего-то «экземплярного», т.е., например, классов. И методы статических классов определяются только на уровне типов, но не экземпляров.
0
Все-таки недопоняли. В дельфи есть в три типа методов — методы экземпляра, методы класса, и статические методы класса.
Вторые вполне могут быть виртуальными, а вызываются на классе, а не экземпляре. Поэтому имеет смысл наследование класса, экземпляр которого никогда не создаётся.
Вторые вполне могут быть виртуальными, а вызываются на классе, а не экземпляре. Поэтому имеет смысл наследование класса, экземпляр которого никогда не создаётся.
0
Не совсем понял ваше объяснение. Причем тут виртуальные методы? Они только определяют, как «смотреть» на объект в конкретном контексте — как на экземпляр базового класса или как на экземпляр дочернего класса, чтобы определить чей метод вызвать (если он переопределен, конечно). Но вызывается то он у экземпляра, а не класса. Или в Дельфи какая-то своя специфика? (в чем я сомневаюсь, ибо это концептуальная вещь).
Если уже лезть в дебри реализации на C# — там неявно при вызове метода у объекта (экземпляра), первым параметром в этот метод передается указатель на текущий экземпляр класса. Но это происходит только для нестатических методов классов, ибо у статических нет экземпляров, и компилятор не сможет определить какой именно метод выполнить — базового статического класса, или дочернего.
P.S. К слову, не вижу ни одного сценария, где нужно наследование статиков. Внутреннее ощущение, что это вообще противоречит концепциям ООП.
Если уже лезть в дебри реализации на C# — там неявно при вызове метода у объекта (экземпляра), первым параметром в этот метод передается указатель на текущий экземпляр класса. Но это происходит только для нестатических методов классов, ибо у статических нет экземпляров, и компилятор не сможет определить какой именно метод выполнить — базового статического класса, или дочернего.
P.S. К слову, не вижу ни одного сценария, где нужно наследование статиков. Внутреннее ощущение, что это вообще противоречит концепциям ООП.
0
Виртуальные методы тут при том, что с ними в наследовании больше смысла.
А вызываются они именно что не у экземпляра, а у класса, я об этом и толкую.
Не вижу что тут противоречит ООП
А вызываются они именно что не у экземпляра, а у класса, я об этом и толкую.
Не вижу что тут противоречит ООП
0
Сначала про ООП — я не вижу смысла в static virtual, когда можно использовать для этого просто virtual. Концептуально — предположим, что static class и есть сам по себе экземпляр чего-то (например, Планеты Земля). Тогда наследующий его класс (тоже статический) будет в том числе экземпляром того-же самого (Планеты Земля). И эти два экземпляра планеты Земля должны будут сосуществовать в одной вселенной (одном Application Domain). Как? :)
«А вызываются они именно что не у экземпляра, а у класса»
Можно поподробнее?
«А вызываются они именно что не у экземпляра, а у класса»
Можно поподробнее?
+1
Ну, примерчик тот еще. Окей, это может пригодиться если вы решите расширить модель, чтобы она включала М-теорию.
Вместо подробнее вот вам пастебин pastebin.com/RxnQXKst
Вместо подробнее вот вам пастебин pastebin.com/RxnQXKst
0
Да, модель с M-теорией будет использоваться в одном контексте, а модель без нее — в другом. Но все равно не будет ситуации, когда в одном контексте, одной области видимости 2 статика одного типа. Если же нужно, чтобы это было возможно — нельзя (и реально нет необходимости) делать их статиками. Если в одном AppDomain меняется сам контекст (нужна то более сложная модель, то более простая), и нужна замена — никаких статиков, в крайнем случае — Singleton.
0
В М-теории предполагается сразу куча контекстов, и в каждом по Земле
0
Статики тут тогда тем более ни при чем. Вообще. Куча контекстов — речь об экземплярах, экземплярах, экземплярах.
К слову, то что я пытаюсь донести, и что хотел сказать фразой про противоречие концепциям ООП, отлично выразил ниже в комментарии товарищ bobermaniac. Статики — это не часть ООП, и я с этим согласен.
К слову, то что я пытаюсь донести, и что хотел сказать фразой про противоречие концепциям ООП, отлично выразил ниже в комментарии товарищ bobermaniac. Статики — это не часть ООП, и я с этим согласен.
0
И собсно роль «синглтона» играет каждый sealed-потомок, а предка вообще не обязательно рассматривать в этом смысле, он просто реализовывает общую базу
0
Кстати, я посмотрел код по вашей ссылке. По сути, на C# такое можно сделать (т.е. вызвать Class.Method() без создания объекта), если использовать статические методы. Если класс имеет статик-методы, но сам при этом не статик, его, кстати, можно унаследовать.
Обратите внимание, ваш метод независим от класса, он не оперирует его состоянием и внутренними переменными, его задача просто вывести фразу или сделать еще какую-то общую вспомогательную вещь. Кстати, поэтому в C# статики часто используются для всевозможных Helper-ов, чтобы просто логически объединить некоторый функционал, не более.
Обратите внимание, ваш метод независим от класса, он не оперирует его состоянием и внутренними переменными, его задача просто вывести фразу или сделать еще какую-то общую вспомогательную вещь. Кстати, поэтому в C# статики часто используются для всевозможных Helper-ов, чтобы просто логически объединить некоторый функционал, не более.
+1
Ну это будет не совсем то. Дело в том, что при наследовании, если вы захотите переопределить в другом классе некий статический метод или свойство — вам придеться воспользоваться модификатором new. А это синтаксически будет совсем не то же самое что перегрузка.
0
Это просто простой пример. Если я объявлю поля как class var, то мой метод сможет оперировать состоянием, при этом экземпляр не требуется
0
А состоянием чего тогда будет оперировать метод?
Я себе могу представить только то, что неявно создается какой-то «анонимный объект по-умолчанию» или что-то в этом роде.
P.S. Ухх, представляю каково все это тестировать :)
Я себе могу представить только то, что неявно создается какой-то «анонимный объект по-умолчанию» или что-то в этом роде.
P.S. Ухх, представляю каково все это тестировать :)
0
Состояние класса. Если рассматривать данные класса, то никаких сложностей с ними нет — это просто статические переменные, с ограниченной областью видимости. Это нельзя назвать экземпляром поумолчанию, потому что у класса и экземпляра разный набор полей. Поля класса находятся прямо в секции данных бинарника, и никаких отдельных усилий для создания прилагать не нужно. Но есть конструкторы классов, выполняющиеся автоматически при запуске. Но они вроде и сишарпе есть, так что тут сравнивать нечего
0
Черт… Совершенно забыл за несколько лет забвения Делфи… Что такое «методы класса» — пример краткий можно?
0
В моей практике уже есть как минимум два сценария, в который множественное наследование было бы КРАЙНЕ желательным. И вы не поверите, к каким ухищрениям пришлось прибегнуть, чтобы это обойти.
+1
Не исключено. Но всегда есть обходной путь. Можно примерчик сценария?
0
Код приводить не могу — NDA. Скажу вкратце — нужнь было бизнес-объект включить в дерево в пользовательском интерфейсе. Конечно проникновение UI в бизнес-слой — это грубейшая ошибка проектирования, но так надо было. Такая специфическая задача была — обертку невозможно было использовть. Да еще и для организации взаимодействия с UI использовался MVVM. Так вот существовал класс, реализующий полностью функционал узла дерева — TreeNodeBase. И был некий LinqEntityBase класс. Задача изящно решалась бы если было бы возможно: Product: LinqEntityBase, TreeNodeBase. А на деле пришлось: Product: LinqEntityBase, ITreeNode + паттерн Декоратор.
0
Но это действительно очень специфичный сценарий, имеющий множество ограничений изначально:
— «проникновение UI в бизнес-слой» и «так надо было»
— «некий LinqEntityBase»
Первое — действительно грубейшая ошибка. Product не должен знать, что он является TreeNode'ом. А то так то он может быть еще и MenuItem'ом, и SomethingElse'ом. Единственный логичнейший выход — Продукт реализовывает ITreeNode, к тому же он наверняка реализовывал какие-то свои особенности «встраивания». Ну и само собой, про ITreeNode должен бы знать только ProductViewModel, но никак не Product.
Этот приведенный пример (вынуждающие условия) сам по себе ведет к «решению на костылях», и главным костылем тут было бы использование множественного наследования.
Второе — правильно ли я понимаю, что использовался EF1? С точки зрения хорошей архитектуры (желательно Domain-Driven), сущности предметной области должны быть POCO-классами, не используя общий какой-то контекст из родительского класса, так что это скорее всего издержки технологии.
— «проникновение UI в бизнес-слой» и «так надо было»
— «некий LinqEntityBase»
Первое — действительно грубейшая ошибка. Product не должен знать, что он является TreeNode'ом. А то так то он может быть еще и MenuItem'ом, и SomethingElse'ом. Единственный логичнейший выход — Продукт реализовывает ITreeNode, к тому же он наверняка реализовывал какие-то свои особенности «встраивания». Ну и само собой, про ITreeNode должен бы знать только ProductViewModel, но никак не Product.
Этот приведенный пример (вынуждающие условия) сам по себе ведет к «решению на костылях», и главным костылем тут было бы использование множественного наследования.
Второе — правильно ли я понимаю, что использовался EF1? С точки зрения хорошей архитектуры (желательно Domain-Driven), сущности предметной области должны быть POCO-классами, не используя общий какой-то контекст из родительского класса, так что это скорее всего издержки технологии.
0
как вариант использовать generic:
class TreeNodeBase where TEntity: LinqEntityBase
…
class Product: LinqEntityBase
…
class ProductTreeNode: TreeNodeBase
class TreeNodeBase where TEntity: LinqEntityBase
…
class Product: LinqEntityBase
…
class ProductTreeNode: TreeNodeBase
0
Чем по сути отличается наследование от абстрактного класса и реализация интерфейса? Парой ключевых слов? В тех языках, конечно, где разрешено множественное наследование.
0
Сложностями с полями. Ну или их отсутствием
0
Ну, скажем так, концептуально интерфейсы предоставляют большую гибкость — они определяют обязательный контракт с «внешним миром», который реализующие его классы должны выполнить.
И за счет жестко установленных контрактов мы получаем уверенность, что конкретный объект реализует интерфейс и может рассматриваться в соответствующей ситуации именно как экземпляр этого интерфейса. Все это позволяет развязать компоненты между собой (они не связаны иерархией наследования). Принципы SOLID — это «the must» в проектировании.
Однако я не сказал бы, что абстрактные классы вообще не нужны, ровно как и наследование от них. Просто на практике, при построении «правильной» архитектуры, с огромным отрывом превалируют именно интерфейсы. Но бывают ситуации, когда объекты имеют достаточное количество общей логики, чтобы ее было логично вынести в абстрактный базовый класс и использовать как поведение по-умолчанию. Все же, такие ситуации бывают редко, что является следствием использования SOLID.
И за счет жестко установленных контрактов мы получаем уверенность, что конкретный объект реализует интерфейс и может рассматриваться в соответствующей ситуации именно как экземпляр этого интерфейса. Все это позволяет развязать компоненты между собой (они не связаны иерархией наследования). Принципы SOLID — это «the must» в проектировании.
Однако я не сказал бы, что абстрактные классы вообще не нужны, ровно как и наследование от них. Просто на практике, при построении «правильной» архитектуры, с огромным отрывом превалируют именно интерфейсы. Но бывают ситуации, когда объекты имеют достаточное количество общей логики, чтобы ее было логично вынести в абстрактный базовый класс и использовать как поведение по-умолчанию. Все же, такие ситуации бывают редко, что является следствием использования SOLID.
+1
По-моему мы о разном говорим. Вы говорите об интерфейсах как о логической сущности или, если угодно, термине проектирования, я же о конкретной синтаксической единице ЯП. Какая разница иерархия наследования от иерархии абстрактных классов или иерархия имплементации иерархии интерфейсов? :) Главное, чтобы был «обязательный контракт с «внешним миром», который реализующие его классы должны выполнить.»
Если вы, например, переносите класс в другой проект, то вам надо будет и переносить либо все классы от которых он унаследован, либо все интерфейсы, связанность и тут, и тут. То же самое, если решите добавить метод в «контракт», в любом случае реализацию придётся модифицировать. И по вашему получается, что на таких языках как python, где интерфейсов, как части языка, нет в принципе невозможно спроектировать приложение согласно SOLID? По-моему, возможно :) Более того, в языках с утиной типизацией даже наследование от абстрактных классов(имплементации интерфейсов) формально не нужно, достаточно чтобы объект (сознательно не пишу класс) реализовывал нужные сигнатуры методов. Ну а если не реализует, то получим исключение при попытке такие методы вызывать (в статических языках получим ошибку компиляции). Хотя лично я предпочитаю создавать класс, назвать его IRepository :) и наследоваться от него, при необходимости добавить в него какую-то общую для всех репозиториев логику или свойства максимумм что мне нужно, так переименовать этот класс в RepositoryAbstract или RepositoryBase.
По-моему, интерфейсы, как часть языка, являются лишь ещё более абстрактными классами, чем собственно абстрактные классы. Просто соглашение между кодерами — если в абстрактном классе нет кода, кроме объявления абстрактных методов, можно сделать его интерфейсом (а можно и не делать, ни на что по сути это не влияет). В языках же где множественного наследования нет, разработчиков вынуждают те классы, которые могли бы быть просто абстрактными объявлять интерфейсами и общую логику реализовывать либо непосредственно в классах, реализующих их, либо вводить абстрактный класс, частично реализующий интерфейс. Не вижу никакого уменьшения связанности. По сути — интерфейсы в языках с множественным наследованием (как и «обычные» абстрактные классы) лишь «фулл протекшен», так же и в языках без множественного наследования, где от него отказались в пользу интерфейсов сознательно, а не из-за необходимости облегчить работу транслятору :) Там же где нет множественного наследования «по техническим причинам» интерфейсы лишь костыль, позволяющий его хоть как-то реализовать.
Если вы, например, переносите класс в другой проект, то вам надо будет и переносить либо все классы от которых он унаследован, либо все интерфейсы, связанность и тут, и тут. То же самое, если решите добавить метод в «контракт», в любом случае реализацию придётся модифицировать. И по вашему получается, что на таких языках как python, где интерфейсов, как части языка, нет в принципе невозможно спроектировать приложение согласно SOLID? По-моему, возможно :) Более того, в языках с утиной типизацией даже наследование от абстрактных классов(имплементации интерфейсов) формально не нужно, достаточно чтобы объект (сознательно не пишу класс) реализовывал нужные сигнатуры методов. Ну а если не реализует, то получим исключение при попытке такие методы вызывать (в статических языках получим ошибку компиляции). Хотя лично я предпочитаю создавать класс, назвать его IRepository :) и наследоваться от него, при необходимости добавить в него какую-то общую для всех репозиториев логику или свойства максимумм что мне нужно, так переименовать этот класс в RepositoryAbstract или RepositoryBase.
По-моему, интерфейсы, как часть языка, являются лишь ещё более абстрактными классами, чем собственно абстрактные классы. Просто соглашение между кодерами — если в абстрактном классе нет кода, кроме объявления абстрактных методов, можно сделать его интерфейсом (а можно и не делать, ни на что по сути это не влияет). В языках же где множественного наследования нет, разработчиков вынуждают те классы, которые могли бы быть просто абстрактными объявлять интерфейсами и общую логику реализовывать либо непосредственно в классах, реализующих их, либо вводить абстрактный класс, частично реализующий интерфейс. Не вижу никакого уменьшения связанности. По сути — интерфейсы в языках с множественным наследованием (как и «обычные» абстрактные классы) лишь «фулл протекшен», так же и в языках без множественного наследования, где от него отказались в пользу интерфейсов сознательно, а не из-за необходимости облегчить работу транслятору :) Там же где нет множественного наследования «по техническим причинам» интерфейсы лишь костыль, позволяющий его хоть как-то реализовать.
0
С последней мыслью не согласен в корне — интерфейсы — это отдельная концепция и она именно концептуально и отличается от абстрактных классов.
С точки же зрения синтаксиса языка, наверное, действительно, разницы особой нет — в C#, например, это просто отдельный механизм именно для поддержания этой концепции, и интерфейсы используются, чтобы проверять факт реализации сигнатуры интерфейса при компиляции и корректность реализации тестами.
Про «интерфейсы в языках с множественным наследованием» — тут да, по сути, можно сделать то же самое двумя способами. Но лучше, имхо, объявлять интерфейсы именно как интерфейсы, это что-то вроде соглашения (для уверенности, что контракт будет выполнен).
Здесь кроется важная мысль — в таких языках абстрактные классы можно использовать как интерфейсы, а можно — как абстрактные базовые классы.
И тут уже просто приходим к разным подходам к проектированию — Inheritance или Composition.
Однако стоит сказать еще вот про какую вещь — «если решите добавить метод в «контракт», в любом случае реализацию придётся модифицировать».
В большинстве случаев возникновение такой ситуации — это нарушение Open-Closed Principle и/или Single Responsibility Principle, и зачастую для новой функциональности лучше создать отдельный интерфейс, реализации которого и будут содержать новую логику (включая наш новый класс, который мы иначе унаследовали бы от базового абстрактного). То есть, по сути, я лучше создам новый контракт и соберу конечный класс из множества кусочков-контрактов. Но это уже опять о подходах к проектированию, а не инструкциях языка. Повторюсь — можно _синтаксически_ использовать и abstract class, но если есть interface — то зачем?
P.S. Пора писать статью со своим взглядом и аргументами, почему Composition лучше Inheritance :)
С точки же зрения синтаксиса языка, наверное, действительно, разницы особой нет — в C#, например, это просто отдельный механизм именно для поддержания этой концепции, и интерфейсы используются, чтобы проверять факт реализации сигнатуры интерфейса при компиляции и корректность реализации тестами.
Про «интерфейсы в языках с множественным наследованием» — тут да, по сути, можно сделать то же самое двумя способами. Но лучше, имхо, объявлять интерфейсы именно как интерфейсы, это что-то вроде соглашения (для уверенности, что контракт будет выполнен).
Здесь кроется важная мысль — в таких языках абстрактные классы можно использовать как интерфейсы, а можно — как абстрактные базовые классы.
И тут уже просто приходим к разным подходам к проектированию — Inheritance или Composition.
Однако стоит сказать еще вот про какую вещь — «если решите добавить метод в «контракт», в любом случае реализацию придётся модифицировать».
В большинстве случаев возникновение такой ситуации — это нарушение Open-Closed Principle и/или Single Responsibility Principle, и зачастую для новой функциональности лучше создать отдельный интерфейс, реализации которого и будут содержать новую логику (включая наш новый класс, который мы иначе унаследовали бы от базового абстрактного). То есть, по сути, я лучше создам новый контракт и соберу конечный класс из множества кусочков-контрактов. Но это уже опять о подходах к проектированию, а не инструкциях языка. Повторюсь — можно _синтаксически_ использовать и abstract class, но если есть interface — то зачем?
P.S. Пора писать статью со своим взглядом и аргументами, почему Composition лучше Inheritance :)
0
По-моему, в случае добавления метода нарушения Open-Closed Principle не будет, ведь мы не модифицируем существующий код, а расширяем его. Расширять можно по разному, главное не затрагивать уже существующие методы. На каждый метод создавать отдельный интерфейс/класс как-то не тру. Или я вообще Open/Closed не понял. Он что не совместим с Test Driven Development, где разработка в принципе и заключается в добавлении новых методов/свойств/классов по мере необходимости реализации требований ТЗ и постоянном рефакторинге (включая и изменение интерфейсов классов)?
Для публичного продукта, предназначенного для других разработчиков, фиксировать внешние интерфейсы конечно необходимо и добавлять новую функциональность, не ломая старую является, как минимум, хорошим тоном (хотя бы в минорных релизах). Но фиксировать внутренние интерфейсы после каждого удачного прогона, а после этого изменять их только наследованием или созданием новых интерфейсов — перебор, по-моему, так как код станет абсолютно нечитаемым и неуправляемым, логика просто утонет за кучей оберток к каждому методу, а то и к каждой сигнатуре метода.
P.S. Под противостоянием композиции и наследования как-то всегда понимал что-то вроде (псевдо Си, нормального С++ не помню уже :) ):
против
и причём тут интерфейсы не понял :-/ Так что пишите статью ;)
Для публичного продукта, предназначенного для других разработчиков, фиксировать внешние интерфейсы конечно необходимо и добавлять новую функциональность, не ломая старую является, как минимум, хорошим тоном (хотя бы в минорных релизах). Но фиксировать внутренние интерфейсы после каждого удачного прогона, а после этого изменять их только наследованием или созданием новых интерфейсов — перебор, по-моему, так как код станет абсолютно нечитаемым и неуправляемым, логика просто утонет за кучей оберток к каждому методу, а то и к каждой сигнатуре метода.
P.S. Под противостоянием композиции и наследования как-то всегда понимал что-то вроде (псевдо Си, нормального С++ не помню уже :) ):
class A:B {
B _b;
A() {
_b = new B();
}
getB() {
return _b;
}
...
}
class B {
void doSmth () {
...
}
...
}
A.getB().doSmth();
против
class A:B {
...
}
class B() {
void doSmth () {
...
}
...
}
A.doSmth();
и причём тут интерфейсы не понял :-/ Так что пишите статью ;)
0
>Необходимо сериализовать объект (такая задача гипотетически возможна, но трудно представить себе сценарии использования)
По-моему, как раз самый распространённый вариант использования синглтонов — хранение настроек приложения, при входе в приложение десериализуем его из конфигов, при изменении — сериализуем, а так всё приложение имеет доступ к одному и тому же объекту настроек, исключена ситуация, что, например, одна функция пытается послать e-mail через sendmail, а другая через smtp. Можно, конечно, читать конфиги по требованию, но, имхо, это не всегда удобно/оправдано.
По-моему, как раз самый распространённый вариант использования синглтонов — хранение настроек приложения, при входе в приложение десериализуем его из конфигов, при изменении — сериализуем, а так всё приложение имеет доступ к одному и тому же объекту настроек, исключена ситуация, что, например, одна функция пытается послать e-mail через sendmail, а другая через smtp. Можно, конечно, читать конфиги по требованию, но, имхо, это не всегда удобно/оправдано.
+1
Добротный анализ, уважаю.
Сравнил для себя с возможностями в Java: Статические классы не поддерживаются на уровне языка, они там тоже используются для тех же целей. Забавно, что через protected-конструктор можно обеспечить наследование в разных пакетах и для статических классов. А singleton-ы в Java удобно делать через enum.
Сравнил для себя с возможностями в Java: Статические классы не поддерживаются на уровне языка, они там тоже используются для тех же целей. Забавно, что через protected-конструктор можно обеспечить наследование в разных пакетах и для статических классов. А singleton-ы в Java удобно делать через enum.
0
Вот оно, большое программирование…
-3
Кое-чего не хватает.
Кстати, статический класс — это название специфично для .NET, а вообще этот паттерн называется Monostate.
Так вот, если нужна какая-то инициализация перед использованием (например, открыть файл перед использованием лога), то у Singleton это легко решается — помещаем инициализацию в конструктор, он вызовется при первом обращении к instance. Если произойдет исключение — оно будет поймано и обработано нормальным образом. А вот у Monostate с этим хуже — конструктора у него нет, а исключение в конструкторе статического поля приведет к тому, что прога упадет и точная причина будет неизвестна.
Кстати, статический класс — это название специфично для .NET, а вообще этот паттерн называется Monostate.
Так вот, если нужна какая-то инициализация перед использованием (например, открыть файл перед использованием лога), то у Singleton это легко решается — помещаем инициализацию в конструктор, он вызовется при первом обращении к instance. Если произойдет исключение — оно будет поймано и обработано нормальным образом. А вот у Monostate с этим хуже — конструктора у него нет, а исключение в конструкторе статического поля приведет к тому, что прога упадет и точная причина будет неизвестна.
0
Есть такой тонкий момент, о котором почему-то не все знают.
Статический класс не имеет к ООП вообще никакого отношения. Фактически, статический класс — это просто неймспейс с набором данных и функций. Естественно, так как это не класс, то никакого наследования, интерфейсов и прочих ООП-шных вещей, у него быть не может.
В С++ с этим попроще, там допустимо как
namespace SomeStaticData { int Data; int GetData() { return Data; }}
так и
class SomeStaticData {public: static int Data; static int GetData() {return Data; }}
Конечно, в последнем случае допустима простейшая инкапсуляция, то есть сокрытие публичных полей, но это не делает неймспейс классом.
Статический класс не имеет к ООП вообще никакого отношения. Фактически, статический класс — это просто неймспейс с набором данных и функций. Естественно, так как это не класс, то никакого наследования, интерфейсов и прочих ООП-шных вещей, у него быть не может.
В С++ с этим попроще, там допустимо как
namespace SomeStaticData { int Data; int GetData() { return Data; }}
так и
class SomeStaticData {public: static int Data; static int GetData() {return Data; }}
Конечно, в последнем случае допустима простейшая инкапсуляция, то есть сокрытие публичных полей, но это не делает неймспейс классом.
+4
Могу еще одно преимущество синглтона привести, которое выгодно при использовании, например Qt или других языков/библиотек, позволяющих что-то типа сигналов/слотов. Можно иметь механизм глобального оповещения об изменениях (или других событиях) в синглтоне. Например, я использую синглтон для менеджера конфигурации. К нему обоащается окно Preferences, теневое обновление конфигурации с сервера, ну и конечно другие компоненты системы. Последние должны знать когда конфигурация изменилась, независимо от причины изменения. При использовании синглтона создаваемый интерфейс всегда подключается к синглтону и генерирует события уже внешним объектам. Как-то так.
-1
В Qt QApplication фактически является синглтоном, что более, чем оправданно, сколько бы потоков не было в любом случае наличие нескольких QApplication'ов это логическая ошибка. Опять же к примеру хочется визуальные уведомления делать: вполне естественным сделать менеджер, который ими управляет синглтоном: ну не должно быть нескольких менеджеров.
-1
Статический класс — всего лишь контейнер для отдельных подпрограмм, которые для красоты обозваны статическими методами и все тех же глобальных переменных, которые для красоты же обозваны статическими полями. Синглтон — тоже эквивалент глобальной переменной.
На практике имеет смысл использовать статический класс как пространство имен для отдельных подпрограмм, а синглтон лучше вообще не использовать: класс, контролирующий количество своих экземпляров множит на ноль ортогональность, тестируемость и масштабируемость. Для контроля количества экземпляров гораздо лучше использовать фабрики (или другие порождающие паттерны).
На практике имеет смысл использовать статический класс как пространство имен для отдельных подпрограмм, а синглтон лучше вообще не использовать: класс, контролирующий количество своих экземпляров множит на ноль ортогональность, тестируемость и масштабируемость. Для контроля количества экземпляров гораздо лучше использовать фабрики (или другие порождающие паттерны).
+1
Зарегистрируйтесь на Хабре , чтобы оставить комментарий
Singleton (Одиночка) или статический класс?