我正面临严重的设计问题,这让我发疯。我认为它只能通过多重继承或其他东西来解决。所以这就是我想要做的事情:
假设我有一个名为OrdinaryUser的基本用户类,用这种方式定义:
class OrdinaryUser
{
private $id,$name;
public function __construct($id)
{
$this->id = $id;
$this->name = fictionalDB::getUserNameById($id);
}
public function getName()
{
return $this->name;
}
public function getId()
{
return $this->id;
}
}
我有一个名为AdminUser的子类,具有其他功能:
class AdminUser extends OrdinaryUser
{
public function deleteUser($id)
{
echo "Deleting user where id=$id";
}
}
问题:如果我已经实例化了“OrdinaryUser”类型的对象并希望将其设置为动态的AdminUser对象,该怎么办?有没有一种“扩展对象”的方法,以避免实例化子类并且必须用相同的数据重新填充新对象的字段?
另一个相关问题:我可能会在以后定义许多其他类别的用户,每个用户都有自己独特的行为,但总是一个基本行为,并且创建层次结构是没有意义的这种情况,因为大多数情况下,一种类型的对象不应该继承另一种类型的方法,尽管可能需要将一种类型的附加功能“导入”到另一种类型中。
答案 0 :(得分:3)
现在,这不是立即。以下是一些替代方案,以便于实施。
首先,如果您知道相关类之间的所有可能的转换,您可以轻松创建一个获取当前对象的方法,填充新类的干净实例并返回它。这是您在原始问题中提到的想法。这是你能做的最直接,最安全的事情。
第二次,如果您使用的是足够现代的PHP版本,则可以使用the Serializable interface获得一个巧妙的技巧。如果您实现该接口,则永远不会调用__sleep
/ __wakeup
,也不是构造函数。这意味着您可以使用这些方法获得廉价技巧。这是一些没有界面演示的愚蠢的演示代码:
[mcg@mcg-workstation ~]$ php -a
Interactive shell
php > class Foo { public $a; public $b; }
php > class Bar extends Foo { public $c; }
php > class Baz extends Foo { public $c; }
php > $one = new Bar();
php > $one_s = serialize($one);
php > echo $one_s;
O:3:"Bar":3:{s:1:"c";N;s:1:"a";N;s:1:"b";N;}
php > $one_s = explode(':', $one_s, 4);
php > print_r($one_s);
Array
(
[0] => O
[1] => 3
[2] => "Bar"
[3] => 3:{s:1:"c";N;s:1:"a";N;s:1:"b";N;}
)
php > $two_s = $one_s;
php > $two_s[1] = strlen('Baz'); $two_s[2] = '"Baz"';
php > $two_s = join(':', $two_s);
php > echo $two_s;
O:3:"Baz":3:{s:1:"c";N;s:1:"a";N;s:1:"b";N;}
php > $two = unserialize($two_s);
php > echo get_class($two);
Baz
如果您没有关注,此代码将替换序列化数据中的类名。通过这样做,我刚刚将Bar
转换为Baz
,具有所有相同的属性。这仅在属性相同时才有效。如果属性不匹配,我不确定PHP会做什么,如果你实现Serializable,你的序列化和反序列化方法将需要处理转换。
这也是一个巨大的黑客,可能会导致代码的未来维护者想要追踪你并伤害你。也可能会有轻微的性能损失。如果您最终使用它,请务必进行基准测试。并聘请保镖。或者至少确保未来的维护者不会成为可以找到你地址的凶残精神病患者。
第三,回到基于类的构造而不是基于对象的构造:如果你可以等待PHP 5.4(或者当前的trunk将最终成为什么),你将能够将匿名函数放在属性中并将它们称为方法。虽然您可以在早期版本的属性中放置匿名函数,至少从PHP 5.3开始,这些函数不能引用$this
,因此作为对象的一部分是无用的。此限制也阻止您使用the __call
magic method来实现同样的目的。
第四,这是一个完全不答案,如果你想要这种类弯曲体操,可以考虑使用Ruby或Perl。我认为Python可以做类似的事情,但我没有在其中工作,也无法确定。在OO方面,PHP不是一种灵活的语言,内部列表中的人员没有兴趣将OO升级到更有趣的其他语言标准。
关于你的相关问题,听起来你真的想要实例级特征。 PHP 5.4也将具有特征,但在类级别,而不是实例级别。有关我的评论,请参阅#4。
答案 1 :(得分:1)
我认为您的方案可能需要Factory Pattern
也许你不应该首先实例化普通用户〜
答案 2 :(得分:1)
听起来像Decorator Pattern可能会帮助你
<?php
/*
an interface to ensure we only decorate Users
and possibly some of the most common methods that all users have
so that we don't always suffer the overhead of the magic __call method
*/
interface User
{
public function getId();
public function getName();
}
class OrdinaryUser implements User
{
private $id,$name;
public function __construct($id)
{
$this->id = $id;
$this->name = fictionalDB::getUserNameById($id);
}
public function getName()
{
return $this->name;
}
public function getId()
{
return $this->id;
}
}
/*
There aren't any abstract methods in this class
but it is declared abstract because there is no point in instantiating one
*/
abstract class UserDecorator implements User
{
protected $user;
public function __construct( User $user )
{
$this->user = $user;
}
public function getId()
{
return $this->user->getId();
}
public function getName()
{
return $this->user->getName();
}
/*
Any methods that aren't implemented by this type of
user are dealt with by the decorated user
*/
public function __call( $method, $arguments )
{
return call_user_func_array( array( $this->user, $method ), $arguments );
}
}
class AdminUser extends UserDecorator
{
/*
Add any methods that are particular to this type of user
*/
public function addedMethod()
{
// do AdminUser type stuff
return "doing added method stuff\n";
}
}
class FooUser extends UserDecorator
{
public function foo()
{
// do some foo
return "doing fooness\n";
}
}
// just for testing
class fictionalDB
{
public static function getUserNameById( $id )
{
$db = array(
1 => 'Peter',
2 => 'Paul'
);
return $db[$id];
}
}
$user = new OrdinaryUser( 1 );
echo $user->getName(); // Peter
// make Peter into an AdminUser
$user = new AdminUser( $user );
// and also add some fooness
$user = new FooUser( $user );
echo $user->addedMethod(); // doing added method stuff
echo $user->foo(); // doing fooness
答案 3 :(得分:0)
您所描述的是不可能的,您是否考虑过不同的方法?如果您的User类'包含'数组中的各种权限实现,那该怎么办。
将其视为“是A”和“有A”问题;虽然AdminUser'是'管理员,但它'具有'权限,所以说priv应该存储为成员变量。