Почему Singleton антипаттерн


(Mykhailo Poliarush) #1

мне задали вопрос на счет использования singleton в разработке автоматических тестов.

немного полазив в интернете, нашел всего лишь одну нормальную заметку с объяснением

MS>Я понимаю, что тема возможно избита, но все же у кого какие мысли?
Итого дискуссий по синглтону:
"главная проблема синглтона в том, что это первый паттерн описанный в GoF" (c) MaximVK. На него набрасываются и не замечают его недостатков, из коих:
1. Синглтон нарушает SRP (Single Responsibility Principle) — класс синглтона, помимо того чтобы выполнять свои непосредственные обязанности, занимается еще и контролированием количества своих экземпляров. 
2. Глобальное состояние. Про вред глобальных переменных вроде бы уже все знают, но тут та же самая проблема. Когда мы получаем доступ к экземпляру класса, мы не знаем текущее состояние этого класса, и кто и когда его менял, и это состояние может быть вовсе не таким, как ожидается. Иными словами, корректность работы с синглтоном зависит от порядка обращений к нему, что вызывает неявную зависимость подсистем друг от друга и, как следствие, серьезно усложняет разработку.
3. Зависимость обычного класса от синглтона не видна в публичном контракте класса. Так как обычно экземпляр синглтона не передается в параметрах метода, а получается напрямую, через GetInstance(), то для выявления зависимости класса от синглтона надо залезть в тело каждого метода — просто просмотреть публичный контракт объекта недостаточно. 
4. Наличие синглтона понижает тестируемость приложения в целом и классов, которые используют синглтон, в частности. Во-первых, вместо синглтона нельзя подпихнуть Mock-объект, а во-вторых, если синглтон имеет интерфейс для изменения своего состояния, то тесты начинают зависеть друг от друга.
Говоря же проще — синглтон повышает связность, и все вышеперечисленное, в том или ином виде, есть следствие повышения связности.
Естественно, можно акккуратненько пройти по граблям и использовать синглетон, но (цитата из доки к пикоконтейнеру) "Overuse makes for bad solutions. At the enterprise level, it makes for very very bad solutions"...
Тем более, что при тщательном рассмотрении вопроса, использования синглтона, как правило, можно легко избежать. А если можно легко избежать, значит это нужно сделать, чтобы удержать себя от излишнего соблазна "оверюза"... Например, для контроля количества экземпляров объекта вполне можно (и нужно) использовать различного рода фабрики.
Наибольшая же опасность, как было сказано, подстерегает при попытке построить на основе сиглтонов всю архитектуру приложения, такому подходу существует масса замечательных альтернатив. Например, IoC контейнеры — там проблема контроля создания сервисов решается естественным образом, так как они, по сути, являются "фабриками на стероидах" =). Другой альтернативой являются Service Locator-ы, из известных вариантов этого подхода — паттерн IServiceProvider.

MS>Я понимаю, что тема возможно избита, но все же у кого какие мысли?

Итого дискуссий по синглтону:

"главная проблема синглтона в том, что это первый паттерн описанный в GoF" (c) MaximVK. На него набрасываются и не замечают его недостатков, из коих:

1. Синглтон нарушает SRP (Single Responsibility Principle) — класс синглтона, помимо того чтобы выполнять свои непосредственные обязанности, занимается еще и контролированием количества своих экземпляров. 

2. Глобальное состояние. Про вред глобальных переменных вроде бы уже все знают, но тут та же самая проблема. Когда мы получаем доступ к экземпляру класса, мы не знаем текущее состояние этого класса, и кто и когда его менял, и это состояние может быть вовсе не таким, как ожидается. Иными словами, корректность работы с синглтоном зависит от порядка обращений к нему, что вызывает неявную зависимость подсистем друг от друга и, как следствие, серьезно усложняет разработку.

3. Зависимость обычного класса от синглтона не видна в публичном контракте класса. Так как обычно экземпляр синглтона не передается в параметрах метода, а получается напрямую, через GetInstance(), то для выявления зависимости класса от синглтона надо залезть в тело каждого метода — просто просмотреть публичный контракт объекта недостаточно. 

4. Наличие синглтона понижает тестируемость приложения в целом и классов, которые используют синглтон, в частности. Во-первых, вместо синглтона нельзя подпихнуть Mock-объект, а во-вторых, если синглтон имеет интерфейс для изменения своего состояния, то тесты начинают зависеть друг от друга. Говоря же проще — синглтон повышает связность, и все вышеперечисленное, в том или ином виде, есть следствие повышения связности.

Естественно, можно акккуратненько пройти по граблям и использовать синглетон, но (цитата из доки к пикоконтейнеру) "Overuse makes for bad solutions. At the enterprise level, it makes for very very bad solutions"...

Тем более, что при тщательном рассмотрении вопроса, использования синглтона, как правило, можно легко избежать. А если можно легко избежать, значит это нужно сделать, чтобы удержать себя от излишнего соблазна "оверюза"... Например, для контроля количества экземпляров объекта вполне можно (и нужно) использовать различного рода фабрики.

Наибольшая же опасность, как было сказано, подстерегает при попытке построить на основе сиглтонов всю архитектуру приложения, такому подходу существует масса замечательных альтернатив. Например, IoC контейнеры — там проблема контроля создания сервисов решается естественным образом, так как они, по сути, являются "фабриками на стероидах" =). Другой альтернативой являются Service Locator-ы, из известных вариантов этого подхода — паттерн IServiceProvider.

Какое ваше мнение? Потому, что я очень много раз видел использование данного паттерна в разработке автоматических тестов


(Дмитрий Жарий) #2

Автоматизация тестирования, конечно же – это программирование. Но, это упрощенное программирование. И многие проблемы, с которыми сталкивается разработчик продакшн кода, кода реальных приложений, – просто не касаются автоматизаторов. 
Начнем по порядку, т.е. в обратном порядке.
4. Наличие синглтона понижает тестируемость приложения в целом и классов
Этот пункт вообще не касается кода автоматизации тестирования. Тесты для авто тестов? Нет, ну я слышал, что кто-то выдвигал такие идеи, но кто на самом деле пишет модульные тесты для авто тестов? Если уже на то пошло, то сам фреймворк по авто тестированию может протестировать сам себя, при этом тестируя продакшн-приложение. 
Вот пример
Дано: Класс GooglePage, описывающий страницу с адресом http://google.com
На странице есть эелементы:
Текстовое поле SearchBox { name = “q”}
Кнопка SearchButton {name = “search”}
В классе GooglePage создаем метод: 
GetExpectedControls {
Return { SearchBox , SearchButton };
}
В тесте получаем список всех ожидаемых элементов, и проверяем что каждый из них существует.
Таким образом убиваем двух зайцев: Мы протестировали продакшн страницу GooglePage и проверили, не изменился ли ID у каждого из описанных элементов. 
Какое еще тестирование для кода автоматизации необходимо?
3. Зависимость обычного класса от синглтона не видна в публичном контракте класса
Зачем нам нужно знать об этих зависимостях? Программистам для создания юнит тестов – да, нужно. А автоматизаторам функциональных тестов это зачем?
2. Глобальное состояние.
Иногда, в автоматизации тестирования это очень полезно: изменить глобальное состояние системы. Но, вдруг действительно, какой-то плохой человек (редиска), в 18-м тесте изменил что-то очень важное, из-за чего начал валится 169 и 341 тесткейс. Это, конечно же, очень неприятно, но:
В самом синглтоне мы можем добавить метод RestoreDefaults(), который будет приводить синглтон в первоначальное состояние. Этот метод можно автоматически вызывать после прохода каждого тест кейса. 
Каждое изменение свойств Синглтона мы може логировать. И уже при помощи лога найти виновного. 
Да, у Синглтона и глобальных переменных есть проблемы. Но, с другой стороны, синглтон значительно упрощает программирование авто-тестов. 
1. Синглтон нарушает SRP (Single Responsibility Principle)
Чем это плохо? Ну и пусть себе контролирует количество экземпляров. Как по мне, это не самая большая проблема, которую можно встретить в коде автоматизации. 
Пусть об этом лучше спорят теоретики программирования, а мне это нарушение абсолютно не мешает. 
А как мы, кстати, в автоматизации используем синглтон?
По-моему,  самое частое использование – это вынос экземпляра браузера в отдельный класс и возможность сделать его доступным из любого теста. 
Еще для передачи дефолтных значений, если не хватает обычного статического класса. 
А еще то для чего?

Автоматизация тестирования, конечно же – это программирование. Но, это упрощенное программирование. И многие проблемы, с которыми сталкивается разработчик продакшн кода, кода реальных приложений, – просто не касаются автоматизаторов. 

Начнем по порядку, т.е. в обратном порядке.

4. Наличие синглтона понижает тестируемость приложения в целом и классов

Этот пункт вообще не касается кода автоматизации тестирования. Тесты для авто тестов? Нет, ну я слышал, что кто-то выдвигал такие идеи, но кто на самом деле пишет модульные тесты для авто тестов? Если уже на то пошло, то сам фреймворк по авто тестированию может протестировать сам себя, при этом тестируя продакшн-приложение. 

Вот пример

Дано: Класс GooglePage, описывающий страницу с адресом http://google.com

На странице есть эелементы:

Текстовое поле SearchBox { name = “q”}

Кнопка SearchButton {name = “search”}

В классе GooglePage создаем метод: 

GetExpectedControls {

Return { SearchBox , SearchButton };

}

 

В тесте получаем список всех ожидаемых элементов, и проверяем что каждый из них существует.

Таким образом убиваем двух зайцев: Мы протестировали продакшн страницу GooglePage и проверили, не изменился ли ID у каждого из описанных элементов. 

Какое еще тестирование для кода автоматизации необходимо?

 

3. Зависимость обычного класса от синглтона не видна в публичном контракте класса

Зачем нам нужно знать об этих зависимостях? Программистам для создания юнит тестов – да, нужно. А автоматизаторам функциональных тестов это зачем?

 

2. Глобальное состояние.

Иногда, в автоматизации тестирования это очень полезно: изменить глобальное состояние системы. Но, вдруг действительно, какой-то плохой человек (редиска), в 18-м тесте изменил что-то очень важное, из-за чего начал валится 169 и 341 тесткейс. Это, конечно же, очень неприятно, но:

В самом синглтоне мы можем добавить метод RestoreDefaults(), который будет приводить синглтон в первоначальное состояние. Этот метод можно автоматически вызывать после прохода каждого тест кейса. 

Каждое изменение свойств Синглтона мы може логировать. И уже при помощи лога найти виновного. 

Да, у Синглтона и глобальных переменных есть проблемы. Но, с другой стороны, синглтон значительно упрощает программирование авто-тестов. 

 

1. Синглтон нарушает SRP (Single Responsibility Principle)

Чем это плохо? Ну и пусть себе контролирует количество экземпляров. Как по мне, это не самая большая проблема, которую можно встретить в коде автоматизации. 

Пусть об этом лучше спорят теоретики программирования, а мне это нарушение абсолютно не мешает. 

 

А как мы, кстати, в автоматизации используем синглтон?

По-моему,  самое частое использование – это вынос экземпляра браузера в отдельный класс и возможность сделать его доступным из любого теста. 

Еще для передачи дефолтных значений, если не хватает обычного статического класса. 

 

А еще то для чего?


(Mykhailo Poliarush) #3

дело в том, что очень много и не используем

была такая мысть "сингелтон - это антипаттерн и он теоритичеки может повлиять на многопоточность, например"

может быть сингелтон и есть антипаттерн, но для автоматизации тестов - это вещь не заменимая и прекрасно работает в многих потоках, прям на выступлении показывал, как можно запустить тесты паралельно без всяких проблем


(Denis Veselovskiy) #4

так сингелтон же позволяет только 1 обьект создавать, как его прилепить к многопоточности???

Миша, а скинь плиз пример кода того, а то чуствую пока носом меня не ткнут не разберусь (


(Дмитрий Жарий) #5

Многие люди, под словом Singleton понимают абсолютно разные вещи. В том числе и я. 
Вариаций на эту тему много. 
Вот, например, есть такая вариация, как: Per-Thread Singleton. Это типа Синглтон, который является глобально доступным и всегда возвращает один и тот же экземпляр только для конкретного потока. Т.е. для каждого другого потока он создает новый экземпляр. 
Для C# я вот нашел хороший пример:
http://www.aspnet-answers.com/microsoft/Csharp/31749999/singleton--one-per-thread.aspx
Но, там реализация идет через атрибут [ThreadStatic] и весь алгоритм определения текущего потока делегируются компилятору  и .NET Framework.  
Для Java  я искать побоялся, но несколько примеров видел по фразе «Per-Thread Singleton Java»

Многие люди, под словом Singleton понимают абсолютно разные вещи. В том числе и я. 

Вариаций на эту тему много. 

Вот, например, есть такая вариация, как: Per-Thread Singleton. Это типа Синглтон, который является глобально доступным и всегда возвращает один и тот же экземпляр только для конкретного потока. Т.е. для каждого другого потока он создает новый экземпляр. 

 

Для C# я вот нашел хороший пример:

http://www.aspnet-answers.com/microsoft/Csharp/31749999/singleton--one-per-thread.aspx

Но, там реализация идет через атрибут [ThreadStatic] и весь алгоритм определения текущего потока делегируются компилятору  и .NET Framework.  

Для Java  я искать побоялся, но несколько примеров видел по фразе «Per-Thread Singleton Java»


(Taras) #6

Я читал что это делаеться через sinchronyzed. Можете скинуть кусок кода примера использования этого паттерна в автоматизации, тоесть наглядный пример постоения тестов - из фреймворка или теста самого, где он там юзаеться, как би что б увидеть на примере какие оьекте создаються под шаблоном этого паттерна. А то на практике еще ни разу не реализововал. Спасибо. Буду благодарен если описание короткое еще добавите)


(Mykhailo Poliarush) #7

пример сингелтона на питоне

{syntaxhighlighter brush: python;fontsize: 100; first-line: 1; }class Wrapper(object):
_instance = None

def __new__(cls, *args, **kwargs):
    if not cls._instance:
        cls._instance = super(Wrapper, cls).__new__(cls, *args, **kwargs)
    return cls._instance

def remote_webdriver(self, *args, **kwargs):
    self.connection = Firefox()
    return self.connection{/syntaxhighlighter}<p>и его использование</p>{syntaxhighlighter brush: python;fontsize: 100; first-line: 1; }class BaseTestCase(unittest.TestCase):
def setUp(self):
    self.driver = Wrapper().remote_webdriver()
    self.driver.implicitly_wait(30)

def tearDown(self):
    self.driver.close(){/syntaxhighlighter}{syntaxhighlighter brush: python;fontsize: 100; first-line: 1; }class TextElement(Element):
def __set__(self, obj, val):
    e = Wrapper().connection.find_element_by_name(self._locator)
    e.send_keys(val)

def __get__(self, obj, cls=None):
    try:
        e = Wrapper().connection.find_element_by_name(self._locator)
        return str(e.text)
    except Exception, err:
        raise err{/syntaxhighlighter}

(Denis Veselovskiy) #8

забыл что ты змее-ед а не жабо-ед )

но за пример все равно спс (хоть и нифига не понятный :) )


(Mykhailo Poliarush) #9

практически тоже самое для джава

{syntaxhighlighter brush: java;fontsize: 100; first-line: 1; }public class ClassicSingleton {
private static ClassicSingleton instance = null;
protected ClassicSingleton() {
// Exists only to defeat instantiation.
}
public static ClassicSingleton getInstance() {
if(instance == null) {
instance = new ClassicSingleton();
}
return instance;
}
}{/syntaxhighlighter}

http://www.javaworld.com/javaworld/jw-04-2003/jw-0425-designpatterns.html


(Дмитрий Жарий) #10

А вот мой вариант:
Видео:

Выносим Webdriver в отдельный класс -- AT.info форум


Я старался сделать C# походим на Java ;)

 

Код. "Типа Синглтон". На самом деле смесь Синглтона, Статических методов, Класса-Обвертки

{syntaxhighlighter brush: csharp;fontsize: 100; first-line: 1; } public class WebBrowser
{
// Вот тут то и будет хранится наш единственный экземпляр
private static IWebDriver instance;

        // Четко даем понять, что конструктор приватный и экземпляр класса нельзя создать извне
        // Хотя, в данном примере – это совсем лишнее… 
        private WebBrowser()   { }

        // Это не чистый синглтон, так как мы возвращаем экземпляр другого класса… Что делать, жизнь – штука жесткая
        public static IWebDriver GetInstance() 
        {
            if (instance == null)
            {
                instance = new InternetExplorerDriver(); // Или понатыкать еще можно if/else  для Хромов и Фаярфоксов
            }

            return instance;
        }

        // Вот теперь можно из любого участка кода закрыть браузер, если он открыт
        public static void Close()
        {
            if (instance != null)
            {
                instance.Quit();
                instance = null;
            }
        }
    }

{/syntaxhighlighter}

 

Код. Страница Гугла в стиле Page Object

{syntaxhighlighter brush: csharp;fontsize: 100; first-line: 1; }public class GoogleSearchPage
{
        public IWebDriver GetDrv() { 
            return WebBrowser.GetInstance(); 
        }

        public IWebElement txtQueryBox() { 
            return GetDrv().FindElement(By.Name("q")); 
        }

        public void Show()
        {
            Show("http://www.google.co.uk");
        }

        public void Show(string url)
        {
            //Navigate to the site
            GetDrv().Navigate().GoToUrl(url);
        }

        public void Search(string searchPhrase)
        {
            Show();
            //Work with the Element that's on the page
            txtQueryBox().SendKeys(searchPhrase);
            txtQueryBox().SendKeys(Keys.Enter);
            System.Threading.Thread.Sleep(2000);
        }
    }{/syntaxhighlighter}<p>&nbsp;</p><p>Код. Тесткейс</p>{syntaxhighlighter brush: csharp;fontsize: 100; first-line: 1; }        [Test]
    public void TestTestGoogle()
    {
        // Actual: "this is a test - Поиск в Google"
        // * Arrange
        var googlePage = new GoogleSearchPage();

        string googleSearchPhrase = "This is a test";

        // * Act
        googlePage.Search(googleSearchPhrase);

        // * Assert
        // 1. Should contain the search phrase
        StringAssert.Contains(googleSearchPhrase, WebBrowser.GetInstance().Title);

        // 2. Should contain our Company name
        StringAssert.Contains("Google", WebBrowser.GetInstance().Title);
        WebBrowser.Close();
    }

{/syntaxhighlighter}


(Mykhailo Poliarush) #11

отличное видео Дима, спасибо большое :)


(Mykhailo Poliarush) #12

{syntaxhighlighter brush: java;fontsize: 100; first-line: 1; }import java.io.File;
import java.io.FileInputStream;
import java.util.Properties;

public class Config {

/**
 * Singleton instance.
 */
private static Config instance;

.........

private Properties properties;

/**
 * Constructor
 * Loads the Properties file.
 */
private Config() {
    properties = new Properties();
    File file = new File(Config.PROPERTIES_FILE);
    try {
        properties.load(new FileInputStream(file));
    } catch (Exception ex) {
        System.err.println("Could not load properties file: " + file.getAbsolutePath());
        ex.printStackTrace();
    }
}

/**
 * Singleton access to this class
 * @return Config
 */
public synchronized static Config getInstance() {
    if (instance == null) {
        instance = new Config();
    }
    return instance;
}

......
}
{/syntaxhighlighter}

пример использования для конфигов http://accessrichard.blogspot.com/2011/01/automated-web-testing-with-java.html


(Taras) #13

мега большое спасибо

теперь модифицирую свой фреймворк под такую конструкцию)


(KaNoN) #14

Какой-то некорректный вопрос. Вообще-то Singleton это вполне себе паттерн, который решает определенные задачи.

Но есть такая вещь как Singletonitis (Одиночество) - вот это антипаттерн, при котором Singleton применяется либо неуместно, либо слишком часто. Со своими последствиями.

Если же таких объектов немного и они в основном read-only, то не вижу проблем, особенно если такой объект в системе реально один на всем протяжении выполнения тестов.


(Shaman) #15

насчет того что синглтон не потокобезопасен, существуют конструкции которые делают его потокобезопасным. советую почитать книгу Э. Фримена. Паттерны проектирования. хоть она и в довольно детской форме, но понимание дает хорошее. в книге все примеры на Java.