【问题标题】:Alternative for multiple inheritance [duplicate]多重继承的替代方案[重复]
【发布时间】:2015-05-30 07:14:45
【问题描述】:

在 20 年左右第一次编写 Java 之后,我希望我有多重继承。但我没有,所以我正在为这个具体问题寻找替代方案。

真正的应用程序是某种 ERP,而且有些复杂,所以我尝试将这个问题转化为汽车。这只是到目前为止,但这是我能做的最好的。

让我们从描述汽车功能的界面开始:

public interface Car {

    public String accelerate();
    public String decelerate();

    public String steerLeft();
    public String steerRight();
}

现在我们有了一个基本的(但不是抽象的)实现:

public class BasicCar implements Car {

    protected final String name;

    public BasicCar( String name ) {

        this.name = name;
    }

    // In the real word this method is important and does lots of stuff like calculations and caching
    protected String doDrive( String how ) {
        return name + " is " + how + "ing";
    }
    @Override
    public String accelerate() {
        return doDrive( "accelerat" );
    }
    @Override
    public String decelerate() {
        return doDrive( "decelerat" );
    }


    // This method is important, too
    protected String doSteer( String where ) {
        return name + " is steering to the " + where;
    }
    @Override
    public String steerLeft() {
        return doSteer( "left" );
    }
    @Override
    public String steerRight() {
        return doSteer( "right" );
    }
}

在现实世界中,这本身就是 DAO 的外观。请注意,我需要有 doDrivedoSteer 方法,因为这是完成一些计算、翻译和缓存等实际工作的地方。

还有一些更具体的实现:

public class Sportscar extends BasicCar {

    public Sportscar( String name ) {
        super( name );
    }

    // I need to call the super method
    @Override
    public String doDrive( String how ) {
        return super.doDrive( how ) + " fastly";
    }
}

public class Truck extends BasicCar {

    public Truck( String name ) {
        super( name, new BasicMotor(), new TruckySteerer() );
    }

    // I need to call the super method
    @Override
    public String doSteer( String where ) {
        return super.doSteer( where ) + " carefully";
    }
}

我现在能做的是:

Car mcqueen = new Sportscar( "McQueen" );
mcqueen.steerLeft() //-> "McQueen is steering left"
mcqueen.accelerate() // -> "McQueen is accelerating fastly"

Car mack = new Truck( "Mack" );
mack.steerLeft() //-> "Mack is steering left carefully"
mack.accelerate() // -> "Mack is accelerating"

我现在想要做的是将这两者合二为一,共享它们的功能:

Car red = new Firetruck( "Red" );
red.steerLeft() //-> "Red is steering left *carefully*"
red.accelerate() // -> "Red is accelerating *fastly*"

我尝试/想到了什么

我可以将两者中的代码复制并粘贴到一个类中。但这绝不是一个好主意。而在实际应用程序中,这几乎是代码,所以这是一个更糟糕的主意。

我认为我在这里面临一些重大的重写/重构。

所以我可以将SportscarTruck 设为装饰器,而Firetruck 则同时使用两者。但这不起作用,因为doSteerdoDrive 是从被装饰对象内部调用的,而装饰器仅适用于来自“外部”的调用。所以我也必须将这些方法放在装饰器中,这也不是一个好主意。

所以我可以使用 Java8 的新奇特功能并将 doSteerdoDrive 委托给如下接口:

@FunctionalInterface
interface Driver {
    public String doDrive( String where );
}

并将其提供给BasicCar 的构造函数 但后来(除了变得相当复杂和final 遇到一些其他相当讨厌的 Java 问题之外)我无法再以一种好的方式访问BasicCar 的状态了。

所以目前我在这里有点迷路。任何好的想法都会被欣赏。

编辑:举个例子,这在现实世界中的样子:doSteer 组可能类似于:

class ProductImpl {
    String getTitle(){
        return getField( "title" );
    }
    String getTeaser(){
        return getField( "teaser" );
    }
    String getField( String name ){
        Locale loc = configuredLocale();
        Map vars = configuredVarialbes();
        String value = getCached( name, loc );
        value = translateVariables( value );
        value = replaceVariables( value, vars );
        // and more
    }
}

【问题讨论】:

  • 假设的Firetruck 组合需要访问SportscarTruck 的哪些方法?只是你命名的protected,还是在现实世界中有很多这样的方法?
  • 嗨,鲍里斯。还有很多。真实的对象是具有许多属性的物品/产品。后代是产品,如“Set”(包含子产品)、“In set”(包含在集合中)、“matrix”(包含同一产品的变体)、“In matrix”(同一产品的变体)。尝试将“矩阵”放入“集合”或其他方式时会出现问题。所以真正的对象应该是extends Product_InMatrix, Product_Set。真正的属性类似于availableAmout,它在“Sets”中将是其包含的产品数量的最小值。等等。
  • 使用 Java8 功能并将它们全部转换为具有方法实现而不是抽象方法的接口有什么问题?
  • 参见Stack Overflow: java traits or mixins pattern?,了解其他编程语言如何克服单继承限制的一些讨论。另一个相关技术是Stack Overflow: Java Aspect-Oriented Programming with Annotations
  • @Necreaux:几乎什么都没有。取决于你站在哪里。在我的情况下,始终如一地这样做(并且一致性几乎没有任何问题),这意味着拥有几乎与我想要覆盖的方法一样多的接口。 (约 20 个)。并且这些接口的实现数量相同(以及后代对象的数量更多)。这成为一个好主意,特别是如果您有许多可以由这些部分组成的后代类。如果只有少数,感觉不对。 (在我的情况下是 4-6)。 Gabriel 的建议也是如此,基本上就是这样。

标签: java inheritance design-patterns


【解决方案1】:

您可以使用模式策略来定义不同的行为并让子类实现它们。

就像这张图片:

它是法语(下面的字典),但不言自明:

  • Personnage 继承的所有类都是视频游戏中使用的角色。
  • 所有实现接口的类(EspritCombatifDeplacementSoin)在接口中定义方法。它们就是行为。
  • Personnage 类包含右侧定义的每个接口的对象。
  • 每个字符(Personnage 的子类)通过在构造函数中执行例如 combat = new CombatCouteau(); 来选择要实现的代码。

因此,如果您只想更改所有持刀战斗的人的行为,只需更改CombatCouteau 中的代码即可。


字典

  • 人物 = 人物
  • Guerrier = 战士
  • Medecin = 医生
  • Chirurgien = 外科医生
  • Couteau = 刀
  • Pistolet = 枪
  • 行军 = 步行
  • Courir = 奔跑
  • 总理 = 急救
  • 手术 = 手术
  • 战斗精神 = 好斗的头脑
  • 战斗,战斗 = 战斗,战斗
  • déplacement, se déplacer = 移动,移动
  • soin, soigner = 治愈,治愈

【讨论】:

  • 嗨,Gabriel,感谢您的回答(et le dictionnaire ;)。如果我对您的理解正确,您也提出了Delegate 模式的一些变体。在您的情况下,我将其称为:代表的组合。就像我在我的问题中所写的那样,这是我一直在考虑自己的事情,但问题仍然存在,代表无权访问对象状态。所以我必须完全“分解”我的原始对象。我的希望是,有人会想出一个不包括这样做的方法:)
  • 似乎每个人都同意使用Decorators 的某种组合来完成这项任务。我曾希望有一个更干净的解决方案,但由于一天过去了,但没有出现,我不得不吞下药丸。我将此答案标记为正确。不是因为它比其他答案更正确,也是很好的答案。我选择了这个,因为它最接近我将要用于解决我手头的问题的方法。其他答案是完全有效的,可能最适合与我类似的问题,但可能只是有点不同(或不同的人)。谢谢大家!
  • 嗨@Scheintod,很抱歉没有早点回答。很抱歉,您没有找到更好的答案,但我认为没有比建议的所有内容更好的了。不过我希望你能做你需要的!
【解决方案2】:

Compositor 会这样做吗?它不能顺利转换为汽车世界,但看起来适合您的任务:

public class FireTruck implements Car {

    public FireTruck( List<? extends Car> behaviors ) {
        this.behaviors = behaviors;
    }

    // I need to call the super method
    @Override
    public String doSteer( String where ) {
        String result = "":
        for (Car behavior : behaviors) result += behavior.doSteer(where);
        // post-process behavior some how to cut out infixes;
    }
}

我假设在您的实际应用程序中doSteer 适用于某个域对象而不是String,因此域对象的方法集应该足够丰富,以便在do* 方法中轻松组合行为。

【讨论】:

  • 在 Car Interface 中没有 doSteer 方法,如何覆盖不存在的东西?
  • 感谢您的回答。如果我理解正确,这基本上是我一直在考虑的Delegate 模式的变体。您建议使用委托列表(这里称为行为),而不是扩展委托(我在想什么)。但主要问题仍然存在,我无法访问BasicCar 的状态。
  • doSteer 确实(在某些情况下)在String 上工作。在现实世界中,这很简单,例如:String getTitle(){ return getDescription( "title" ); } 其中getDescription( name ) 将完成真正的工作,例如在正确的语言环境中访问正确的字段、进行翻译、变量替换等等。
  • @Larry:我不介意(非常)不得不更改我的更多代码。我很确定我必须以一种方式或另一种方式来做。所以如果我必须在界面中输入doSteer,我不会很喜欢它,但如果它可以帮助我解决手头的问题,我可以这样做。
  • @Scheintod 温馨提醒,在进行重构之前不要忘记覆盖单元测试。
【解决方案3】:

拆分功能和状态

如果您无论如何都必须进行大规模重构 - 解决这个复杂问题的好方法是将状态和功能分开。请记住 Java 中的对象方法如何在幕后工作:本质上,对象函数是一个静态函数,第一个参数是“this”。 “this”参数是一个状态对象,具有您需要的所有属性。功能不必与此绑定。

它可能看起来像这样:

class CarState {
  public float fuel;
  public String name;
}

class BasicCarFunctionality {
  public static void accelerate( CarState car ) {
    System.out.println( "accelerating" );
  }
}

class SportscarFunctionality {
  public static void drive( CarState car, String how ) {
    // Here you can reference Basic-Behaviour if you want
    BasicCarFunctionality.accelerate( car );
    System.out.println( car.name + " drive " + how );
  }
}

class TruckFunctionality {
  public static void steer( CarState car, String how ) {
    System.out.println( car.name + " steer " + how );
  }
}

interface SportsCar {
  void doDrive( String how );
}

interface Truck {
  void doSteer( String how );
}

class Firewagon implements SportsCar, Truck {
  private CarState car = new CarState();

  @Override
  public void doDrive( String how ) {
    SportscarFunctionality.drive( car, how );
  }

  @Override
  public void doSteer( String how ) {
    TruckFunctionality.steer( car, how );
  }
}

如果您的 firewagon 需要额外的 State-Attributes 而不是每辆车都需要,您可以使用新的 State 对象来组合您的状态:

class FirewagonExtraState {
  public float waterAmount;
  public boolean sirenActive;
}

// And ammend the Firewagon Class:
class Firewagon implements SportsCar, Truck {
  private CarState car                  = new CarState();
  private FirewagonExtraState firewagon = new FirewagonExtraState();

  @Override
  public void doDrive( String how ) {
    FirewagonFunctionality.activateHorn( firewagon );
    SportscarFunctionality.drive( car, how );
  }

  @Override
  public void doSteer( String how ) {
    TruckFunctionality.steer( car, how );
  }
}

【讨论】:

  • 如果您有多个图层,您还可以添加 BasicCarFunctionality 和 BasicCar 功能的静态方法。您可以从任何其他函数调用所有这些函数,传递相同的 CarState。如果你需要额外的状态属性,你可以创建一个复合状态对象,由一个 CarState 和一个带有额外属性的“FiretruckExtraState”组成
  • 嗨,Falco,感谢您的回答,感谢您提醒我Delegate 模式的这种变体。我想我们现在已经完成了。 (这甚至可能吗?)。我的Delegate 示例的主要区别在于,真正的功能不存储在 FunctionalInterface 的实现中,而是存储在静态方法中。 (有时这更清楚,有时它与oo相矛盾。有时两者都有:))
  • 有趣的是,我已经(大部分)在 DAO 对象中分离了状态,我想象中的 BasicCar 对象是 Facade(访问器)。 (可以称它为“DatabaseCar”左右。)
【解决方案4】:

我不确定这个类比是否正确,无法给你一个好的答案..

在 Java 中处理多重继承问题的最基本方法是使用接口,然后使用组合委托行为。

汽车和卡车都具有转向和加速功能,但卡车不是汽车,不应仅仅因为某些实现是共享的而从其继承行为。将共享实现拉出到更有意义的类(转向柱、传动系统等),然后从汽车和卡车委托给它。

使用函数式接口可以减少麻烦,但您不需要 Java 8 来解决问题。

【讨论】:

    【解决方案5】:

    这个呢:

    public class SportsCar implements Car {
        private final BasicCar basicCar;
        public SportsCar( String name ) {
            this.basicCar = new BasicCar(name);
        }
    
        public SportsCar( BasicCar basicCar ) {
            this.basicCar = basicCar;
        }
    
        @Override
        public String accelerate() {
            return basicCar.accelerate() + " fastly";
        }
        @Override
        public String decelerate() {
            return basicCar.decelerate() + " fastly";
        }
    
        @Override
        public String steerLeft(){
            return basicCar.steerLeft() + " fastly";
        }
    
        @Override
        public String steerRight(){
            return basicCar.steerLeft() + " fastly";
        };
    }
    
    public class Truck implements Car {
        private final BasicCar basicCar;
    
        public Truck( String name ) {
            this.basicCar = new BasicCar(name);
        }
    
        public Truck( BasicCar basicCar ) {
            this.basicCar = basicCar;
        }
    
        @Override
        public String accelerate() {
            return basicCar.accelerate() + " carefully";
        }
        @Override
        public String decelerate() {
            return basicCar.decelerate() + " carefully";
        }
    
        @Override
        public String steerLeft(){
            return basicCar.steerLeft() + " carefully";
        }
    
        @Override
        public String steerRight(){
            return basicCar.steerLeft() + " carefully";
        };
    }
    

    现在是FireTruck

    public class FireTruck implements Car {
        Collection<Car> behaviors;
        BasicCar basicCar;
    
        public FireTruck(String name ) {
            this.behaviors = new ArrayList<String>();
    
            this.basicCar = new BasicCar(name);
    
            this.behaviors.Add(new SportsCar(this.basicCar));
            this.behaviors.Add(new Truck(this.basicCar));
        }
    
        @Override
        public String accelerate() {
            StringBuilder result = new StringBuilder();
    
            for(Car c : behaviors){
                result.append(c.accelerate());
            }
    
            //and if I wanted to do my own thing here as a 'FireTruck' I could, something like :
            String myOwnAction = basicCar.accelerate() + " sounding alarms and flashing red light";
            result.append(myOwnAction);
        }
        //same for other methods : decelerate, steerLeft and steerRight
    }
    

    【讨论】:

    • 您好 snajahi,感谢您的回答。就像我在我的问题中写的那样,我绝对需要 doSteerdoDrive 方法,因为真正的工作(阅读:很多代码)是在那里完成的。出现问题是因为我不喜欢在后代中复制/粘贴类似doSteerLeft 的方法。但你是对的:如果不是这样,就不会有问题。
    • 这段代码确实假设doSteardoDrive 方法存在,但仅在BasicCar 类中,您是否声明它们必须出现在SportsCarTruck 类中吗?
    • snajahi:对不起。我没有正确阅读您的代码。但问题仍然存在:我需要重写 do... 方法,因为我必须更改那里的基本功能。仅用几行就很难解释:类似于:doDrive(){ getPropertyA(); do_stuff() } 将被覆盖:`doDrive(){ getPropertyB();做东西() }'。 (它与可以包含其他文章的文章有关,这些文章反过来会改变包含文章的行为。)
    【解决方案6】:

    前段时间我问了一个非常相似的问题......也许你会发现它很有用:

    A Collection of an Abstract Class (or something like that...)

    【讨论】:

      猜你喜欢
      • 2013-06-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-06-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多