开发者

How can I inheriting from a concrete class in Perl?

Our software represents each device as a concrete class. So let's say I have a class named Cpu-Version-1, which is derived from a abstract baseclass called Device. Now, the CPU vendor wants to release the economic version of this CPU ( Cpu-Version-2 ), which is a feature reduced version of Cpu-Version-1 (obviously for a lower price ). 90% code of Cpu-Version-2 is same as of Cpu-Version-1, but the remaining 10% code is no longer same. i.e. these are the features that have been removed in Cpu-Version-2 and so I should no longer represent it.

Now what would be the best way to design this class? I could inherit Cpu-Version-2 from Cpu-Version-1 (even though the inheritance is imperfect, since these two devices are peers). This would force me to override a lot of methods of cpu-version-1 to do nothing ( which looks ugly and somehow doesn't feel right). Also I don't think I could change the baseclass (Cpu-Version-1 or it's base) since the code is already in production and requires lot of approvals and justification. How would you make this design decision if you were me?

How do we make Cpu-Version-2 as much reusable and maintainable? Is there any OOP principles that you would follow? Or is it better tradeoff to take the easy wayout and override all the non-applicable methods of Cpu-Version-1 to do nothing? I code in object-ori开发者_运维问答ented Perl.


I think one of the major stumbling blocks that impedes the progress of object-oriented programming projects is the common misconception that inheritance is the only way to compose new classes from multiple sources. But inheritance in general is actually a pretty sucky model, and only really works for things that can be neatly taxonomized in a perfect hierarchy. As you have discovered, the real world often doesn't work that way.

It sounds like inheritance is not a good model for this problem. Fortunately, there are other models available for constructing classes in Perl.

With Moose, you can use roles for composing classes with a combination of things they "do" without having to create a complicated (and in this case, ill-suited) inheritance hierarchy.

If you don't want something as heavy-duty as Moose, there are also simpler options like mixin.

Another option that may interest you is prototype-based composition, as with Class::Prototyped. This is still hierarchy-based, but gives you a lot more flexibility by allowing you to mess with individual instances and then use those as the prototype for new instances. This is how OO is done in languages like Javascript and Self.


If you can't go all the way to using Moose and roles, you can refactor the CPU classes to use composition and delegation.

CPU1 is a CPU
CPU1 has a Frobnicator
CPU1 has a Thingummy
CPU1 has a Poddlewhick
CPU1 has a Fridgemagnet

Where Frobnicator, Thingummy and Poddlewhick are all classes that operate on a CPU object.

CPU2 is a CPU
CPU2 has a Frobnicator
CPU2 has a Thingummy
CPU2 has a Poddlewhick

CPU2 is just like CPU1 but has no Fridgemagnet.

Methods to composed classes would need to get a copy of the CPU object passed to them so that they can request info from the delegator.

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 => @_ );
}

This approach leads to a lot of repeated code. You could probably work up some pseudo-role type code injection by importing functions into your classes with a use statement. But if you need to go that far, just use Moose rather than reimplementing it.

Do this ONLY if you can't just use Moose with roles. They work better, with less hassle and boilerplate.


You have several options. I don't think having CPU-Version-2 derive from CPU-Version-1 is the answer, though.

If your code is "refactorable" (i.e. you have plenty of unit tests which would allow you to refactor with confidence), then you might consider creating a CPU class which inherits directly from Device. All the common methods could be moved up to CPU, and then you could derive CPU-Version-X from there.

Better yet, however, might be to use composition. Keep an instance of CPU-Version-1 in your CPU-Version-2 object. For any common methods, your CPU-Version-2 object can simply delegate to the equivalent method in CPU-Version-1. You can augment this behavior with CPU-Version-2's own methods to satisfy the requirements.

For instance (pseudocode):

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;

Does this help?


It sounds like you need to move more stuff into your abstract class so you can make concrete classes with exactly what they need, and each inherits from the abstract class.

Alternatively, since you say you cannot change the base class (so, going the not-as-reuseable and not-as-maintainable route), you can just nerf stuff in Cpu-Version-2. If you are inheriting from Cpu-Version-1 but don't want one of it's methods, override it to do nothing:

 package CPU::V2;
 use parent qw( CPU::V1 );

 sub dont_need_this_feature { return }

Without seeing the actual classes and what else you've done, that's as good a guess as I can give you.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜