对于没有类爆炸的选择性组合物的良好物体结

时间:2011-12-03 11:50:50

标签: java php oop

我的真实场景有点难以解释,所以我会把它映射到一个更易识别的领域,比如家庭娱乐设备:

特定设备可以提供不同的服务:

Panasonic XYZ可以播放DVD和CD 索尼ABC只能播放CD 日立PQR可以播放DVD并接收电视 ...
...

每个服务(DVD,CD,TV,...)都有一个默认实现,大多数模型使用,但某些模型具有特定服务的自定义版本。


接口

选择实施DVD',CD',TV'izable,...合同的模型会 导致模型之间的代码重复很多。


单一继承

实现默认服务的单个超类允许我为每个模型包含一个包含其所有自定义行为的子类。然而,对于不提供所有类型服务的模型,我的超类将非常笨重且比它需要的更重。


多重继承

多表继承具有选择性地整合所需服务并提供默认实现的能力,表面看来似乎是理想的。我重视将所有PanasonicXYZ的自定义功能集中在一个类中的凝聚力,而不是继承所引入的耦合。

但是我没有使用C ++(而不是PHP),我觉得有一种更好的方法。我也不想使用mixins或5.4的特性等专有扩展。


组合物

我看到一个类爆炸,我的自定义功能分散在多个类的特定模型上 - 例如,我需要PanasonicXYZ_CD类和PanasonicXYZ_DVD类,它们只能被PanasonicXYZ对象使用。


有一个更好的结构吗?

编辑:我会很好地考虑一些评论和答案,而不是过早评论。

6 个答案:

答案 0 :(得分:3)

使用编辑Luke

对象类表示系统中的角色。即使考虑“设备”是很自然的,但对于OOP而言,最好考虑一下设备的作用:DVD播放器,电视接收器,CD播放器等。

单个设备完成所有这些操作并不重要,考虑对象将要具有的角色将帮助您以单一责任对象结束:

class SonyTvWithDVDPlayer {
     DVDPlayer asDVDPlayer();
     Tv asTv();
}

通过这种方式很容易重构常用功能,您可以拥有GenericDVDPlayer并在asDVDPlayer方法中返回。

如果您想允许更动态的使用,例如询问Device它支持哪种功能,您可以使用某种Product Trader,例如:

interface  MultifunctionDevice {
      <T> T as(Class<T> functionalitySpec) throws UnsupportedFunctionalityException
}

他们在代码中你可以这样做:

device.as(DVDPlayer.class).play(dvd);

在这种情况下,“多功能设备”的作用类似于产品交易商,而DVDPlayer.class产品的规范

实现交易者和规范有很多不同的方法,一种是使用访客模式。但我发现在很多情况下(当你想要动态配置你的“多功能设备”时)你可以这样做:

class MultifunctionDevice {
      Iterable<Device> devices;

      <T extends Device> T as(Class<T> spec) {
          for (Device dev : devices) {
              if (dev.provides(spec)) return dev;
          }
          throw new UnsupportedFunctionalityException(spec);
      }
 }

这与Builder和流畅的API 相结合,可以轻松定义不同的设备而不会发生类爆炸:

   dvdAndTv = new DeviceBuilder("Sony All in one TV and DVD")
        .addPart(new SonyDvdPlayer())
        .addPart(new SonyTv())
        .build();

答案 1 :(得分:0)

我会使用依赖注入来做这样的事情:

abstract class Device {
    private function addFunction(Function $function)
    {
        // store it
    }
    public function callFunction($function, $method)
    {
        // check whether local method exists (e.g. $function$method() or dvdPlay())
        // if not: call $function->$method
    }
}

然后所有设备都扩展Devices,所有功能都扩展为Function。要更改默认Function实施,您的设备类(例如SonyAbc)可以提供自己的方法,例如dvdPlay()

答案 2 :(得分:0)

我赞成作文。

您是否甚至需要每个设备的实际类?或者将特定电视提供为具有某些功能的设备的正确连接实例是否足够?

samsunSxYZ() {
   Device device = new Device()
   device.addFunction(new SamsungDvdStandard())
   device.addFunction(new SamsungCdStandard())
   return device;
}

samsunSabc() {
   Device device = new Device()
   device.addFunction(new SamsungDvdStandard())
   device.addFunction(new SamsungCdSpecialAbc())
   return device;
}

可以通过调用

来检索功能
DvdFeature dvdFeature = device.getFunction(DvdFeature.class)

答案 3 :(得分:0)

服务是否已知且数量有限? 如果是这样,我会做这样的事情:

  • 为每种服务定义一个接口(例如DvdPlayer,TvReceiver,CdPlayer等)

  • 为每个服务接口提供一个默认实现(许多设备将共享的通用实现),并编码您还需要的特定于模型的实现。

  • 定义第二组接口,表示特定设备具有提供服务的能力(例如DvdPlayerCapable),每个接口声明一个(唯一命名的)服务接口的getter,例如:

    public DvdPlayer getDvdPlayer();
    
  • 现在您的设备可以组合多个功能接口,并使用服务的默认实现或特定于模型的实现......

因此,如果您想要在Sony123Device(即 DvdPlayerCapable )上访问DVD第15章,您可以致电:

mySonyDevice.getDvdPlayer().goToChapter(15);

答案 4 :(得分:0)

看来你有两个问题。第一个问题是如何在服务之间传递通用功能,这可以通过继承来解决。如下所示:

public abstract class BaseDevice  
{  
  //common methods with implementations  
}  

public class SonyPlayer extends BaseDevice {...}  

public class SamsungPlayer extends BaseDevice{...}

问题的一个有趣的部分是我们进入默认的自定义版本。在这个例子中,我们不想使用继承,因为我们知道它直接违反了LSP。因此,我们可以通过合成来处理它,这将导致类似于以下内容:

public class CustomSonyPlayer  
{  
   SonyPlayer sonyPlayer;  

   //Adds a digital signature after using the SonyPlayer#ripMP3
   public void ripMP3Special(CustomSonyPlayer customSonyPlayer)  
   {  
          MP3 mp3 = sonyPlayer.ripMP3(new MP3("Life goes on"));  
          customSonyPlayer.addSignature(mp3);
   }  
}

或者您可以在实用程序类中公开常用特殊功能。

答案 5 :(得分:0)

过去我在根抽象类上提供了一个通用的Custom_Fuctions。它被序列化存储

然后我打电话给

foreach($device->Custom_Functions() as $name=>$function){
    if(is_int($name))
        echo '<li>',$function, '</li>';
    else
        echo '<li>',$name, ': ', $function, '</li>';
}

通过方法添加海关:

function Add_Custom($name, $function) {
    if(empty($name))
        $this->customizes[] = $function;
    else
        $this->customize[$name] = $function;
}