Наследование абстрактного синглетона - нормально или изврат ?..

StUV

Rotaredom
Наследование абстрактного синглетона - нормально или изврат ?..

Привет всем.

Соорудил примерно такую конструкцию:

PHP:
abstract class User
{
	static protected $_instance;
	protected $_userInfo;
		...

	protected function __construct()
	{
		...
	}

	static public function getInstance()
	{
		if (!isset(self::$_instance))
		{
			try
			{
				$userInfo = PhpSession::getInstance()->loadObject(Config::SESSION_USER_NAME);
				self::$_instance = new User_Registered();
				$this->_userInfo = $userInfo;
			}
			catch (PhpSession_Exception $e)
			{
				self::$_instance = new User_Unregistered();
			}
		}
		return self::$_instance;
	}

	abstract public function isRegistered();
}

class User_Registered extends User
{
	protected function __construct()
	{
		parent::__construct();
	}

	public function isRegistered()
	{
		return true;
	}

		...
}

class User_Unregistered extends User
{
	protected function __construct()
	{
		parent::__construct();
	}

	public function isRegistered()
	{
		return false;
	}

		...
}
Общий смысл - "унифицированное" получение инстанса юзера в зависимости от переменных в сессии:

PHP:
$user = User::getInstance();
// далее $user передается в объекты, использующие интерфейс абстрактного User'a, не зная о том, зарегистрирован он или нет
Пытался перепроектировать используя фабрику или реестр - ничего настолько же простого не получилось.
Сам никаких граблей пока не нашел, но есть сомнения на "уровне интуиции" =)

Можно ли считать такое решение "имеющим право на жизнь" ?..

Спасибо.
 

StUV

Rotaredom
благодаря crocodile2u, обнаружился косяк в коде - обращение к $this в static методе =)
 

crocodile2u

http://vbolshov.org.ru
Стас, этот код нерабочий (используешь $this в статическом методе). (Собсно, мы с тобой уже это обсудили - пишу здесь специально на случай, если новичок посмотрит)
 

StUV

Rotaredom
Автор оригинала: crocodile2u
Стас, этот код нерабочий (используешь $this в статическом методе). (Собсно, мы с тобой уже это обсудили - пишу здесь специально на случай, если новичок посмотрит)
переписал теперь так

PHP:
abstract class User
{
	static protected $_instance;
	
		...

	protected function __construct()
	{
		...
	}

	static public function getInstance()
	{
		if (!isset(self::$_instance))
		{
			try
			{
				PhpSession::getInstance()->loadObject(Config::SESSION_USER_NAME);
				self::$_instance = new User_Registered();
			}
			catch (PhpSession_Exception $e)
			{
				self::$_instance = new User_Unregistered();
			}
		}
		return self::$_instance;
	}

	abstract public function isRegistered();
}

class User_Registered extends User
{
	protected $_userInfo;

	protected function __construct()
	{
		parent::__construct();
		$this->_userInfo = PhpSession::getInstance()->loadObject(Config::SESSION_USER_NAME);
	}

	public function isRegistered()
	{
		return true;
	}

		...
}

class User_Unregistered extends User
{
	protected function __construct()
	{
		parent::__construct();
	}

	public function isRegistered()
	{
		return false;
	}

		...
}
теперь явное дублирование
можно в парент-конструкторе просто вызывать check на существование переменной в сессии
все-равно для unregistered эта инфа не нужна

зы: но паттерны все-равно поковыряю ;)
 

crocodile2u

http://vbolshov.org.ru
Фактически, то, что ты сделал, по функциональности похоже на State или Strategy (cобсно, по смыслу, скорее на Strategy, хотя эти два паттерна очень схожи)
 

StUV

Rotaredom
в итоге конструктивного диалога получилось:

PHP:
abstract class User
{
	static protected $_instance;

		...

	protected function __construct()
	{
		...
	}

	static public function getInstance()
	{
		if (!isset(self::$_instance))
		{
			try
			{
				self::$_instance = new User_Registered();
			}
			catch (PhpSession_Exception $e)
			{
				self::$_instance = new User_Unregistered();
			}
		}
		return self::$_instance;
	}

		...
}

class User_Registered extends User
{
	protected $_userInfo;

	protected function __construct()
	{
		$this->_userInfo = PhpSession::getInstance()->loadObject(Config::SESSION_USER_NAME);
		parent::__construct();
	}

		...
}

class User_Unregistered extends User
{
	protected function __construct()
	{
		parent::__construct();
	}

		...
}
так что дублирование ушло
а так же лишние поля в одном из наследуемых классов.
 

whirlwind

TDD infected, paranoid
ну и посмотри что получилось :) у тебя user зависит от наследников. к тому же это лишняя путаница - может показаться что User_Registered и User_Unregistered - это два разных сингельтона, а на самом деле - это один сингельтон

PHP:
<?php


abstract class User {

	static public $v = 1;

	function pv(){
		echo get_class($this)," ",self::$v,"\n";
	}

}


class T1 extends User {

	function __construct(){
		self::$v = 2;
	}

}

class T2 extends User {

	function __construct(){
		self::$v = 3;
	}

}

$t1 = new T1();
$t1->pv();
$t2 = new T2();
$t1->pv();
$t2->pv();

?>
-~{}~ 29.08.06 14:38:

PS. точнее не зависит от наследников, а ограничивает сферу наследования.
 

StUV

Rotaredom
whirlwind
точнее не зависит от наследников, а ограничивает сферу наследования.
хм...
public static полей не предусмотрено
так же и public static методов кроме getInstance - все "сокрыто" в инстансах дочерних классов - доступен лишь общий интерфейс обычных паблик методов

мне нужно полиморфное поведение при вызове методов, наследуемых от базовых абстрактных (и не только) - и оно работает...
 

crocodile2u

http://vbolshov.org.ru
Хорошее решение, имхо.
Я бы все же использовал Состояние - но в значительной мере это дело вкуса.
 

whirlwind

TDD infected, paranoid
>public static полей не предусмотрено
так же и public static методов кроме getInstance

да пофик на паблик. не в нем суть :) Не нужен тут сингельтон тем более его наследование. Если в плане интерфейса между Registered и Unregistered нет разницы - зачем платить больше? :)

PS. загляни вперед и представь как будет выглядеть код, в котором обрабатывается список зарегистрированных пользователей. Грубо говоря - юзер (который getInstance) смотрит список других юзеров. Или ты будешь писать еще и Displayable_User при чем с нуля? :)
 

StUV

Rotaredom
whirlwind
фактически эта реализация нужна для User Session
синглетон - так как дергается в разных слабо-связанных местах
+ позволяет заменить условную логику в представлении полиморфным поведением

загляни вперед и представь как будет выглядеть код, в котором обрабатывается список зарегистрированных пользователей. Грубо говоря - юзер (который getInstance) смотрит список других юзеров. Или ты будешь писать еще и Displayable_User при чем с нуля?
соответственно, этот список не имеет ничего общего с приведенной структурой
 

whirlwind

TDD infected, paranoid
>фактически эта реализация нужна для User Session

ну дык а зачем ты смешиваешь бизнеслогику две абсолютно не связанных сущности?

Ну сделай Session::getUser() которая работает с полноценным инстансом User.

Ну в общем то упираемся в то же самое, во что уперлись в предыдущих темах:


У тебя классы: RegisteredUser, UnregisteredUser, UserForDisplaying, UserForMailing, UserForContactingAnotherUser etc... :)
а у меня механизмы:

$authmod->auth(User $user);
$ctrl->display(User $user); // утрировано
$user->sendmail(...);
или
$mailer->sendmail($user->email,...);

шо удобнее сопровождать, решает каждый сам :)
 

StUV

Rotaredom
whirlwind
ну да ;)

-~{}~ 29.08.06 15:27:

Ну сделай Session::getUser() которая работает с полноценным инстансом User
вот оно:
$this->_userInfo = PhpSession::getInstance()->loadObject(Config::SESSION_USER_NAME);
$authmod->auth(User $user);
есть при логине

--------
в общем все одинаково - но, как и в тот раз, подходы разные =)

фактически, мне это надо для того, чтобы никто кроме представления не знал о том - зареген юзер или нет, включая самого юзера =)
 

crocodile2u

http://vbolshov.org.ru
Раз уж выкладываем код, то вот моя версия :) :
PHP:
abstract class User
{
	/**
     * @var User
     */
    static protected $_instance;
    /**
     * @var UserState
     */
    protected $state;

    protected function __construct()
    {
    	try
        {
            $this->state = new UserState_Registered();
        }
        catch (PhpSession_Exception $e)
        {
            $this->state = new UserState_Unregistered();
        }
    }

    static function getInstance()
    {
        if (null === self::$_instance)
        {
                self::$_instance = new User();
        }
    }
    
    function isRegistered()
    {
    	return $this->state->isRegistered();
    }
}

interface UserState {
	function isRegistered();
}

class UserState_Registered implements UserState
{
    function isRegistered()
    {
    	return true;
    }
}

class UserState_Unregistered implements UserState
{
    function isRegistered()
    {
    	return false;
    }
}
 

whirlwind

TDD infected, paranoid
StUV >фактически, мне это надо для того, чтобы никто кроме представления не знал о том - зареген юзер или нет

Фигня это все... Что мешает User::getInstance instanceof UserRegistered? Ну а в чем тогда разница между $user->isRegistered() ? Ни в чем. За то в моем варианте это привычно и понятно, а в твоем... не тот случай... в общем выглядит ИМХО через Ж :)

Если бы активная модель, то все было б шоколадно
PHP:
$user->selectByLogin(PhpSession::getInstance()->get(Config::SESSION_USER_NAME);
echo $user->isSelected() ? "registered" : "unregistered";
и сессия ничего не знает о юзере, и юзеру на сессию наплевать. А у тебя... [и тут спор разгорелся с новой силой] :)

PS. вообще не могу объема юниттестов для твоего случая представить
 

StUV

Rotaredom
whirlwind
к сожалению забыл указать на свой промах в первых постах - понял его только после отдельного от топика диалога с crocodile2u

Метод isRegistered был использован только в качестве проверки типа - фактически никаких условных операторов (методов участвующих в "обычной" условной логике) в данных типах нет

повторюсь, главная задача как раз заменить условные переходы полиморфным поведением - т.е. пользователи интерфейса класса User не знают, с каким из дочерних классов они работают

-~{}~ 29.08.06 22:05:

PS. вообще не могу объема юниттестов для твоего случая представить
данная конструкция всего-лишь "кирпичик" архитектуры и для меня (с учетом всего остального) структура тестов прозрачна
 
Сверху