我们的软件将每个设备表示为具体类。所以假设我有一个名为Cpu-Version-1
的类,它来自一个名为Device
的抽象基类。现在,CPU供应商想要发布这个CPU的经济版本(Cpu-Version-2
),这是Cpu-Version-1
的功能缩减版本(显然价格较低)。 Cpu-Version-2
的90%代码与Cpu-Version-1
的代码相同,但剩余的10%代码不再相同。即这些是Cpu-Version-2
中已删除的功能,因此我不再代表它。
现在设计这门课的最佳方法是什么?我可以从Cpu-Version-2
继承Cpu-Version-1
(即使继承不完美,因为这两个设备都是对等的)。这将迫使我覆盖cpu-version-1的许多方法什么都不做(看起来很丑陋,不知何故感觉不对)。此外,我认为我不能更改基类(Cpu-Version-1
或它的基础),因为代码已经在生产中并且需要大量的批准和理由。如果你是我,你会如何做出这个设计决定?
我们如何使Cpu-Version-2
具有可重用性和可维护性?你会遵循任何OOP原则吗?或者,采用简单的方式并覆盖Cpu-Version-1
的所有不适用的方法,什么都不做,这是更好的权衡吗?我在面向对象的Perl中编码。
答案 0 :(得分:4)
我认为阻碍面向对象编程项目进展的主要障碍之一是常见的误解,即继承是从多个来源组成新类的唯一方式。但是一般来说,继承实际上是一个非常糟糕的模型,并且只适用于可以在完美的层次结构中整齐地分类的事物。正如您所发现的,现实世界通常不会那样工作。
听起来遗传不是这个问题的好模型。幸运的是,还有其他模型可用于在Perl中构造类。
使用Moose,您可以使用roles组合具有“执行”功能组合的类,而无需创建复杂的(在这种情况下,不适合)继承层次结构。 / p>
如果你不想要像Moose那样重要的东西,那么还有更简单的选项,如mixin。
您可能感兴趣的另一个选项是基于原型的合成,与Class::Prototyped一样。这仍然是基于层次结构的,但是通过允许您混淆单个实例然后将它们用作新实例的原型,可以为您提供更大的灵活性。这就是OO在Javascript和Self等语言中的表现。
答案 1 :(得分:3)
如果你不能一直使用Moose和角色,你可以重构CPU类以使用组合和委托。
CPU1 is a CPU
CPU1 has a Frobnicator
CPU1 has a Thingummy
CPU1 has a Poddlewhick
CPU1 has a Fridgemagnet
Frobnicator,Thingummy和Poddlewhick都是在CPU对象上运行的类。
CPU2 is a CPU
CPU2 has a Frobnicator
CPU2 has a Thingummy
CPU2 has a Poddlewhick
CPU2就像CPU1一样,但没有Fridgemagnet。
组合类的方法需要获取传递给它们的CPU对象的副本,以便它们可以从委托者请求信息。
package CPU;
sub delegate {
my $self = shift;
my $accessor = shift;
my $method = shift;
return $self->$accessor->$method( $self, @_ );
}
package CPU1;
use Frobnicator;
use Thingummy;
use Poddlewhick;
use Fridgemagnet;
package CPU2;
use Frobnicator;
use Thingummy;
use Poddlewhick;
package Frobnicator;
use Exporter;
our @ISA = /Exporter/;
our @EXPORT_OK = qw( frobnicate );
our @EXPORT = @EXPORT_OK;
sub frobnicate {
my $delegator = shift;
return $delegator->delegate( frobnicator => frob => @_ );
}
这种方法会导致很多重复的代码。您可以使用import
语句通过use
函数将一些伪角色类型代码注入到您的类中。但是,如果你需要走得那么远,只需使用Moose而不是重新实现它。
仅在您不能将Moose与角色一起使用时才执行此操作。它们工作得更好,麻烦和样板更少。
答案 2 :(得分:2)
您有几种选择。我不认为CPU-Version-2派生自CPU-Version-1就是答案。
如果您的代码是“可重构的”(即您有足够的单元测试可以让您自信地进行重构),那么您可以考虑创建一个直接从Device继承的CPU类。所有常用方法都可以移到CPU,然后你可以从那里派生出CPU-Version-X。
然而,更好的是,可能会使用构图。在CPU-Version-2对象中保留CPU-Version-1的实例。对于任何常用方法,您的CPU-Version-2对象可以简单地委托给CPU-Version-1中的等效方法。您可以使用CPU-Version-2自己的方法来增强此行为,以满足要求。例如(伪代码):
class CPU-Version-2:
CPU-Version-1 cpu;
int commonMethod1():
return cpu.method1();
void commonMethod2():
cpu.doSomething();
int newMethod():
x = cpu.getSomeValue();
y = x + 42;
return y;
这有帮助吗?
答案 3 :(得分:1)
听起来你需要将更多的东西移动到抽象类中,这样你就可以使用他们需要的具体类来创建具体的类,并且每个类都继承自抽象类。
或者,既然你说你不能改变基类(那么,去不可重复使用且不可维护的路线),你可以在Cpu-Version-2
中找到东西。如果您继承自Cpu-Version-1
但不想要其中一种方法,则将其覆盖为什么都不做:
package CPU::V2;
use parent qw( CPU::V1 );
sub dont_need_this_feature { return }
没有看到实际的课程以及你做了什么,这就像我能给你的那样好。