这可能会成为一个令人尴尬的愚蠢问题,但可能比创建令人尴尬的愚蠢代码更好。 :-)这是一个OO设计问题,真的。
假设我有一个对象类'Foos'代表一组动态配置元素,这些元素是通过查询磁盘上的命令'mycrazyfoos -getconfig'获得的。假设我想要'Foos'对象有两类行为:
现有的:一个是我刚才提到的命令输出中存在的查询(/ usr / bin / mycrazyfoos -getconfig`。通过炮轰命令对现有命令进行修改。
创建不存在的新的;新的'crazyfoos',使用一组复杂的/usr/bin/mycrazyfoos
命令和参数。在这里,我不仅仅是查询,而是实际运行一堆system()命令。影响变化。
这是我的班级结构:
包Foos,它有一个新的($ hashref-> {name =>'myfooname')构造函数,它接受'crazyfoo NAME',然后查询该NAME的存在以查看它是否已存在(通过炮轰并运行上面的mycrazyfoos命令)。如果该crazyfoo已经存在,则返回一个Foos :: Existing对象。对此对象的任何更改都需要进行外壳修改,运行命令并确认一切正常运行。
如果这是要走的路,那么new()构造函数需要有一个测试来查看要使用的子类构造函数(如果在这种情况下甚至有意义)。以下是子类:
如上所述,这适用于Foos对象已存在的情况。
如果在上面,'crazyfoo NAME'实际上不存在,那么这个对象将被创建。在这种情况下,上面的new()构造函数将被检查其他参数,并且它将继续使用 - > create()使用system()进行shell输出并创建一个新对象...可能返回一个'现有'一个......
OR
当我输入时,我意识到这可能是最好的单身:
Foos类,有
- > new()只需一个名字
- > create(),其中包含其他创建参数
- > delete(), - > change()和其他影响存在的参数;必须动态检查。
所以我们这里有两个主要方向。我很好奇哪种方式更聪明。
答案 0 :(得分:4)
一般来说,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 :(得分:4)
一般来说,超类要了解其子类是一个坏主意,这一原则延伸到构造。[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]:不是推荐,因为我不能很好地处理你的要求,以确保我不会在黑暗的海洋中捕鱼。
答案 2 :(得分:2)
如果对象的构造函数将返回一个包含在多个包中的实例,那么它也是factory pattern(Perl中的错误)。
我会创造这样的东西。如果names
存在,则is_created
设置为1,否则设置为0.我会将::Pending
和::Existing
合并在一起,如果对象是创建只是将其放入default
的{{1}},检查发生了懒惰。此外,Foo-> delete()和Foo-> change()将推迟到_object
中的实例。
_object
答案 3 :(得分:1)
有趣的答案!当我在代码中尝试不同的东西时,我正在消化它。
好吧,我有同一个问题的另一个变体 - 同样的问题,请注意,对同一个类只是一个不同的问题:子类创建问题!
这一次:
此代码是命令行的接口,该命令行具有许多不同的复杂选项。我之前告诉过你/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
和(不同的.pm文件,在Foos / Commandline的子目录中)
package Foos::Commandline::v1.2;
sub getlist ( eval... system ("/usr/bin/mycrazyfoos", "-getlistohyeahrightheh"
#etc
有意义吗?我在代码中表达了我想做的事情,但它感觉不对,特别是考虑到上述回答中讨论的内容。感觉正确的是,应该有一个通用的接口/超类到Commandline ......并且不同的版本应该能够覆盖它。对?希望得到一两个建议。格拉西亚斯。