Въпросче?

teroristd

Registered
Нека хипотетично си представим, че имам един обикновен клас в който, правя конекция към база данни. Да кажем имам втори клас с някави методи който е сингълтон и наследява конекцията.

Въпросът ми е ако използвам сингълтона в някакви други класове, винаги ли ще имам една и съща конекция, въпреки че тя самата не е сингълтон?

Съвсем грубо ще онагледя за да стане по-ясно какво имам в предвид.

Код:
class Connection {
    // Конекция към база данни
}

class Singleton extends Connection {
    // Някакви методи
}

class TestOne {
    $singleton = Singleton::getInstance();
}

class TestTwo {
    $singleton = Singleton::getInstance();
}
 
Явно никой не може да отговори на въпроса. Тогава ще попитам нещо друго. Твърди се че използването на singleton е лошо. Въпросът ми е как може да има само една инстанция на обекта без този обект да е singleton?
 
Трябва да пуснеш малко повече код от това, което си написал по-горе. :)

А иначе сингълтона се смята за антипатърн. Нещо като глобалните променливи, които е препоръчително да не се използват. Нарушава single responsibility принципа. Също така прави кода tight coupled, което пречи на поддръжката на кода.

Може да погледнеш за dependency injection, много хора го дават като алтернатива и по-добър начин, ако се говори за code maintainability :)
 
Fakeheal каза:
Трябва да пуснеш малко повече код от това, което си написал по-горе. :)

А иначе сингълтона се смята за антипатърн. Нещо като глобалните променливи, които е препоръчително да не се използват. Нарушава single responsibility принципа. Също така прави кода tight coupled, което пречи на поддръжката на кода.

Може да погледнеш за dependency injection, много хора го дават като алтернатива и по-добър начин, ако се говори за code maintainability :)

Разбира се dependency injection :) . Само че той не е алтернатива на singleton, или може би аз го разбирам погрешно.

Код:
class Connection { 

    // Конекция към база данни 

}



class ModelOne {

    private $_connection;

    public function __construct(Connection $connection) {

        $this->_connection = $connection;

    }

}


class ModelTwo {

    private $_connection;

    public function __construct(Connection $connection) {

        $this->_connection = $connection;

    }

}

Поправи ме ако греша, но ето какво се случва в този примерен код за dependency injection. Да кажем имаме клас, който се грижи за създаването на обектите. В случая аз инжектирам Connection в два различни класа и съответно ще бъдат създадени два обекта, което пък значи че ще имам две конекции.

Ако класът Connection беше сингълтон щях да имам само една.
 
Поправи ме ако греша, но ето какво се случва в този примерен код за dependency injection. Да кажем имаме клас, който се грижи за създаването на обектите. В случая аз инжектирам Connection в два различни класа и съответно ще бъдат създадени два обекта, което пък значи че ще имам две конекции.
Ти ще инжектираш само един Connection? Няма да правиш втори.
 
anonimen каза:
Поправи ме ако греша, но ето какво се случва в този примерен код за dependency injection. Да кажем имаме клас, който се грижи за създаването на обектите. В случая аз инжектирам Connection в два различни класа и съответно ще бъдат създадени два обекта, което пък значи че ще имам две конекции.
Ти ще инжектираш само един Connection? Няма да правиш втори.

Това не го разбрах. Какво значи няма да правиш втори? Те обектите се създават автоматично.
 
В случая аз инжектирам Connection в два различни класа и съответно ще бъдат създадени два обекта, което пък значи че ще имам две конекции.

PHP:
$connection = /* по някакъв начин получаваш конекцията.
Може с new, или от някъде другаде */;

$modelOne = new ModelOne($connection);
$modelTwo = new ModelTwo($connection);

Така имаш една инстанция на конекцията в 2 обекта.
 
anonimen каза:
В случая аз инжектирам Connection в два различни класа и съответно ще бъдат създадени два обекта, което пък значи че ще имам две конекции.

PHP:
$connection = /* по някакъв начин получаваш конекцията.
Може с new, или от някъде другаде */;

$modelOne = new ModelOne($connection);
$modelTwo = new ModelTwo($connection);

Така имаш една инстанция на конекцията в 2 обекта.

Принципно е така, обаче този пример е прекалено прост и при мене реално няма как да се случи. Ето как стоят нещата при мен.

Входа ми е един index.php в който, зареждам в този ред Config, Loader и Container.

Config ми е много важен защото с него зареждам различни масиви с конфигурации и го ползвам на много места в приложението, и той е един от класовете който, искам да има само една инстанция.

За Loader е ясно само ще добавя че и той ползва Config.

В Container вече чрез reflection правя обектите. Имам един метод make, който получава като параметър име на клас. Правя проверки дали в конструктора е инжектиран интерфейс, нормален клас, сингълтон и т.н. и зареждам според проверките правилния клас, който вече също може да си има зависимости и така. Класът който подавам като параметър на Container е App. В него инжектирам всички останали класове, които ми трябват, като Validator, View, FrontController и т.н. Това всичко е преди да съм почнал да пиша някакъв сайт примерно.
Основното е че след Container всичко се инжектира, и няма къде да пиша да го наречем свободен код :) като примера който си дал горе. Реално единственото място в което имам new class() e index.php.

PHP:
require_once realpath('../application/Config.php');
require_once realpath('../application/Loader.php');
$loader = application\Loader::getInstance();
$loader->load();
$container = application\Container::getInstance();
$app = $container->make('application\App');
$app->appRun();

Ще дам и бейсик пример с контролер и един модел, все едно почвам да пиша някакъв сайт.

PHP:
Class UserController {
    private $_view;
    private $_model;
    public function __construct(View $view, UserModel $model) {
        $this->_view = $view;
        $this->_model = $model;
    }
    public function register() {
        $this->_model->register($firstName);
        $this->_view->display('register');
    }
}

Class UserModel {
    private $_database;
    private $_validation;
    public function __construct(Database $database, Validation $validation){ 
        $this->_database = $database;
        $this->_validation = $validation;
    }
    public function register($firstName) {
        $this->_validation->setRule('alphacyrillic', $firstName, '', 'alpha');
         if ($this->_validation->validate() === TRUE) {          
             $this->_database->prepare('INSERT INTO user (first_name) VALUES(?)', array ($firstName))->execute();         }
    }
}

Както виждаш всички обекти идват в конструкторите. Нека си представим че имам други модели и контролери, които не са свързани по никакъв начин с тези, но трябва да ползват същата инстанция на database, на Config, и на т.н. В случая тези класове са ми сингълтони и всичко си е ок, ама нали е антипатерн :) . За това питам за алтернатива.

Доста дълъг ми стана поста, дано да ме разберете :) .
 
Просто в контейнера гледаш дали няма вече създадена ДБ връзка? Ако има - даваш нея. Ако няма - правиш нова.
 
Извинявай колега, може аз да не те разбирам, или ти не четеш какво съм написал.
Не се хващай за примера с конекцията. Има и още класове които трябва да са сингълтони. А може и по време на писане да изникнат още. Аз контейнера няма как да седна да го преправям всеки път.
 
teroristd каза:
Извинявай колега, може аз да не те разбирам, или ти не четеш какво съм написал.
Не се хващай за примера с конекцията. Има и още класове които трябва да са сингълтони. А може и по време на писане да изникнат още. Аз контейнера няма как да седна да го преправям всеки път.
Защо тогава не укажеш изрично на контейнера кои класове трябва да се зареждат еднократно и кои - всеки път?

PHP:
class Container {
$instantiationRules = [];
$cached = [];

// после, при създаването на инстанцията:
switch ($instantiationRules[$class]) {
case SINGLETON: $instance = new $class; break;
case KEEP_INSTANCE:
    if(!$cached[$class]) $cached[$class] = new $class;
    $instance = $cached[$class];
break;
default:
    $instance = new $class;
}

}

// При създаването на контейнера:

$container->addRule(Database::class, Container::KEEP_INSTANCE);
$container->addRule(UserController::class, Container::SINGLETON);
// и т.н.
// a може и директно в $instantiationRules да пипаш. Както ти е удобно.
 
Така може и да се получи. Изникна ми един въпрос обаче. Как мога да тествам(да проверя) дали даден клас е една и съща инстанция?
 
teroristd каза:
Така може и да се получи. Изникна ми един въпрос обаче. Как мога да тествам(да проверя) дали даден клас е една и съща инстанция?
Тук нещо не те разбрах. Какво значи клас да е инстанция? Имаш предвид дали две референции сочат към един обект? С оператор "==", предполагам.

Може би търсиш нещо такова?
http://php.net/manual/en/language.references.spot.php#72203
 
Извинявай, не се изразих правилно :). Да кажем вече съм си написал кода за кеша, за който говорихме в предишните постове. Питам как мога да проверя че, кода работи коректно? Или казано с други думи, ако инжектирам да кажем два пъти някакъв обект, как да видя дали това е един и същи обект а не ми е създало два?
 
teroristd каза:
Извинявай, не се изразих правилно :). Да кажем вече съм си написал кода за кеша, за който говорихме в предишните постове. Питам как мога да проверя че, кода работи коректно? Или казано с други думи, ако инжектирам да кажем два пъти някакъв обект, как да видя дали това е един и същи обект а не ми е създало два?
В конструктора инкрементваш статик брояч и го гледаш.

Това за еднократен тест... иначе в production не би трябвало да ти се наложи да го проверяваш?
 
anonimen каза:
teroristd каза:
Извинявай, не се изразих правилно :). Да кажем вече съм си написал кода за кеша, за който говорихме в предишните постове. Питам как мога да проверя че, кода работи коректно? Или казано с други думи, ако инжектирам да кажем два пъти някакъв обект, как да видя дали това е един и същи обект а не ми е създало два?
В конструктора инкрементваш статик брояч и го гледаш.

Това за еднократен тест... иначе в production не би трябвало да ти се наложи да го проверяваш?

Ще те помоля за примерен код.
 
PHP:
class Connection {
static $counter = 0;

public __construct() {
self::$counter++;
}
}
Така?

Сега, когато и да погледнеш в Connection::counter ще видиш колко пъти е извикан конструкторът.
 
Благодаря за помощта, най-накрая се отървах от сингълтоните :) , с малко неудобство да пиша в конфигурацията от кои класове искам само по една инстанция. Остана обаче един малък проблем. Първият клас който викам е Config. След него идва Loader и трети е Container. След контейнера вече всичко е ок, обаче самият конфиг е необходим и на лоадера и на контейнера за да работят. Демек викам го два пъти преди да мине през кеша, който е в контейнера. Не е кой знае какво но малко ме дразни :D . Някакви идеи какво да направя?
 
teroristd каза:
Благодаря за помощта, най-накрая се отървах от сингълтоните :) , с малко неудобство да пиша в конфигурацията от кои класове искам само по една инстанция. Остана обаче един малък проблем. Първият клас който викам е Config. След него идва Loader и трети е Container. След контейнера вече всичко е ок, обаче самият конфиг е необходим и на лоадера и на контейнера за да работят. Демек викам го два пъти преди да мине през кеша, който е в контейнера. Не е кой знае какво но малко ме дразни :D . Някакви идеи какво да направя?
Нали правиш Config с new и после даваш тоя обект и на Loader и на Container? Защо стават 2 обекта?
 
Еми защото кеша е в Container. След кеша независимо колко пъти го викам, бройката не се увеличава. Ето така ми е в index.

Код:
require_once realpath('../application/Config.php');

require_once realpath('../application/Loader.php');

$config = new \application\Config();

$loader = new application\Loader($config);

$loader->load();

$container = new application\Container($config);

$app = $container->make('application\App');

$app->appRun();
 

Горе