这可能会成为一个令人尴尬的愚蠢问题,但可能比创建令人尴尬的愚蠢代码更好。 :-)这是一个OO设计问题,真的。
假设我有一个对象类'Foos',它代表一组动态配置元素,通过查询磁盘上的命令'mycrazyfoos -getconfig'获得。假设我想要'Foos'对象有两类行为:
/usr/bin/mycrazyfoos
命令和参数。在这里,我不仅仅是查询,而是实际运行一堆system()命令。影响变化。这是我的班级结构:
包Foos,它有一个新的($ hashref - > {name =>'myfooname',)构造函数,它接受'crazyfoo NAME',然后查询该NAME的存在,看看它是否已经存在(通过炮轰并运行上面的mycrazyfoos命令)。如果该crazyfoo已经存在,则返回一个Foos :: Existing对象。对此对象的任何更改都需要shelling out,运行命令并确认所有内容都运行正常。
如果这是要走的路,那么new()构造函数需要有一个测试来查看要使用的子类构造函数(如果在这种情况下甚至有意义)。这是子类:
如上所述,这适用于Foos对象已存在的情况。
如果在上面,'crazyfoo NAME'实际上不存在,那么这个对象将被创建。在这种情况下,将检查上面的new()构造函数是否有其他参数,并且它将继续使用 - > create()使用system()调用shell并创建一个新对象...可能返回一个'现有的'一...
要么
当我输入时,我意识到这可能是最好有一个:
Foos类,有一个
- > new()只需要一个名字
- > create(),它接受额外的创建参数
- > delete(), - > change()和其他影响存在的参数;必须动态检查。
所以我们这里有两个主要方向。我很好奇哪种方式更聪明。
一般来说,new
方法返回除了新对象之外的任何东西都是错误的(设计方面,而不是语法方面)。如果您希望有时返回现有对象,请将该方法称为其他方法,例如new_from_cache()
。
我也觉得奇怪的是,你将这个功能(构建一个新对象,并返回一个现有的对象)拆分成不同的命名空间,还有不同的对象。所以一般来说,你接近第二种方法,但你仍然可以让主构造函数(new
)处理各种参数:
package Foos;
use strict;
use warnings;
sub new
{
my ($class, %args) = @_;
if ($args{name})
{
# handle the name => value option
}
if ($args{some_other_option})
{
# ...
}
my $this = {
# fill in any fields you need...
};
return bless $this, $class;
}
sub new_from_cache
{
my ($class, %args) = @_;
# check if the object already exists...
# if not, create a new object
return $class->new(%args);
}
注意:在你还在学习的时候,我不想让事情复杂化,但是你也可能想看看Moose,它会为你处理很多关于构造的血腥细节,以及属性的定义和它们的访问者。
一般来说,一个超类知道它的子类是一个坏主意,这个原则延伸到构造。[1]如果您需要在运行时决定要创建哪种对象(并且您确实如此),请创建第四个类来完成该任务。这是一种“工厂”。
说到你的名义问题,你所描述的问题似乎并没有要求进行子类化。特别是,你显然将根据他们所属的具体类别对待不同类别的Foos
。所有你真正要求的是一种统一的方式来实例化两个独立的对象类。
那么这个建议如何[3]:使Foos::Exists
和Foos::Pending
成为两个独立且无关的类,并提供(在Foos
中)返回适当类的方法。不要叫它new
;你没有制作新的Foos
。
如果你想统一接口以便客户端不必知道他们正在谈论哪种类型,那么我们可以讨论子类化(或者更好的是,委托给一个懒惰创建和更新的Foos::Handle
)。
[1]:解释为什么这是真的是一本书足够重要[2],但简短的回答是它在子类(依赖于它的超类定义)和超类(超类)之间创建了一个依赖循环。由于糟糕的设计决定而依赖于它的子类)。 [2]:Lakos, John. (1996). Large-scale C++ Software Design. Addison-Wesley. [3]:不是推荐,因为我不能很好地处理你的要求,以确保我不会在黑暗的海洋中捕鱼。
如果对象的构造函数将返回一个被保存到多个包中的实例,那么它也是一个factory pattern(在Perl中很糟糕)。
我会创造这样的东西。如果names
存在而不是is_created
被设置为1,否则它被设置为0.我会将::Pending
和::Existing
合并在一起,如果没有创建对象只是将它放入default
用于_object
,则检查发生懒洋洋。此外,Foo-> delete()和Foo-> change()将遵循_object
中的实例。
package Foo;
use Moose;
has 'name' => ( is => 'ro', isa => 'Str', required => 1 );
has 'is_created' => (
is => 'ro'
, isa => 'Bool'
, init_arg => undef
, default => sub {
stuff_if_exists ? 1 : 0
}
);
has '_object' => (
isa => 'Object'
, is => 'ro'
, lazy => 1
, init_arg => undef
, default => sub {
my $self = shift;
$self->is_created
? Foo->new
: Bar->new
}
, handles => [qw/delete change/]
);
有趣的答案!当我在代码中尝试不同的东西时,我正在消化它。
好吧,我有同一个问题的另一个变体 - 同样的问题,请注意,对同一个类只是一个不同的问题:子类创建问题!
这次:
此代码是命令行的接口,该命令行具有许多不同的复杂选项。我之前告诉过你关于/usr/bin/mycrazyfoos
的事,对吗?好吧,如果我告诉你那个二进制文件根据版本发生了变化,有时它会完全改变它的底层选项。而且我们正在编写这门课程,它必须能够解释所有这些事情。目标(或者可能是想法)是:(也许叫做我们上面讨论的Foos类):
Foos :: Commandline,它具有底层'/ usr / bin / mycrazyfoos'命令的不同版本的子类。
例:
my $fcommandobj = new Foos::Commandline;
my @raw_output_list = $fcommandobj->getlist();
my $result_dance = $fcommandobj->dance();
其中'getlist'和'dance'依赖于版本。我想过这样做:
package Foos::Commandline;
new (
#Figure out some clever way to decide what version user has
# (automagically)
# And call appropriate subclass? Wait, you all are telling me this is bad OO:
# if v1.0.1 (new Foos::Commandline::v1.0.1.....
# else if v1.2 (new Foos::Commandline::v1.2....
#etc
}
然后
package Foos::Commandline::v1.0.1;
sub getlist ( eval... system ("/usr/bin/mycrazyfoos", "-getlistbaby"
# etc etc
和(不同的.pom文件,在食物/命令行的子目录中)
package Foos::Commandline::v1.2;
sub getlist ( eval... system ("/usr/bin/mycrazyfoos", "-getlistohyeahrightheh"
#etc
合理?我在代码中表达了我想做的事情,但它感觉不对,特别是考虑到上述回答中讨论的内容。感觉正确的是,应该有一个通用的接口/超类到Commandline ......并且不同的版本应该能够覆盖它。对?希望得到一两个建议。格拉西亚斯。