【问题标题】:Attribute Inheritance with Interfaces and Abstract Classes - Java接口和抽象类的属性继承 - Java
【发布时间】:2019-01-09 12:56:33
【问题描述】:

好的,所以我正在尝试为此找出最佳结构。我想知道什么是这些情况下的最佳做法以及总体上最有效的做法。

问题

假设我正在创建一个小世界。这个世界由不同类型的鸟类组成,所以我创建了一个 Bird 类来充当所有鸟类的父母。

public abstract class Bird {
  //stuff all birds have in common
}

现在我想创建不同类型的鸟。让我们创建一个 Penguin 和一个 Goose 类并让它们扩展 Bird。

public class Penguin extends Bird{
    //penguin stuff
}

public class Goose extends Bird{
    //goose stuff
}

到目前为止一切顺利,但现在是扳手。让我们赋予鸟类飞行的能力。 惊喜,不是所有的鸟都会飞——比如企鹅!那么让我们来看看我们的选择吧..

在 Bird 类中创建 fly 方法是不可能的(不是所有的鸟都会飞)。另一种糟糕的方式是拥有多个父类,例如 BirdsThatFly,因为这会变得非常混乱,尤其是当其他 birdly 属性添加到混合中时。

选项 1

接口

一个可行的选择是使用接口,理论上听起来是正确的想法。例如,我可以有一个 Fly 接口。

public interface Fly {
    public void fly();
}

这将允许我仅在可以飞行的 Bird 子类上实现 Fly。例子..

public class Goose extends Bird implements Fly {
    @Override
    public void fly() {
        //fly!
    }
}

这看起来很干净,并强制在 goose 上使用 fly 方法,并且在添加其他属性时似乎可以很好地工作。

似乎有问题的是,我仍然需要为每种类型的鸟创建一个自定义苍蝇,而且可能会有很多鸟!默认空缺对我有帮助吗?我觉得随着这些属性的增长,它们可能会受到相当大的限制。

也许可以告诉我其他情况以及如何重新考虑这个接口结构。

选项 2

我自己的属性类

这涉及创建属性对象并将它们添加到各个鸟类类中。如果我对接口的看法是正确的,那么这个选项可能是一种有效的方法。

属性类看起来像这样

public abstract class Attribute {
    public abstract void execute();
}

一个属性可能看起来像这样

public class Fly extends Attribute{
    @Override
    public void execute() {
        //fly!
    }
}

现在可以通过在 Bird 中拥有一个属性列表并将其填充到鸟类类型类中来将这个 fly 属性添加到鸟类。

public abstract class Bird {
    private List<Attribute> attributes = new ArrayList<Attribute>();

    protected void addAttribute(Attribute attribute){
        attributes.add(attribute);
    }
    public List<Attribute> getAttributes(){
        return attributes;
    }
}

并将其添加到 Goose...

public class Goose extends Bird{
    public Goose(){
        super.addAttribute(new Fly());
    }
}

对我来说,这种方法似乎非常可定制,但也似乎不实用,并且可能无法立即清楚每个班级能够做什么。它还需要大量工作才能在更大规模上正确设置才能执行特定属性。

有没有一种方法可以被认为是更好的做法,或者这些方法中的任何一种是合理的方法还是一个好的开始?

【问题讨论】:

  • 我认为常规的做法是使用 1 和 2 的组合:在单独的类中实现功能并通过共享接口将其公开。缺点是它可能会导致大量样板文件。我在类似的场景中使用了默认方法,但这有它自己的警告。
  • 您可能想要的是Mixin。这些在 Java 中实现起来有点困难,但是有一些库可以添加这种支持,例如 Apache Polygene
  • 有趣的是,飞行是鸟(不是人类)的属性,在面向对象的术语中它是一种行为!

标签: java oop inheritance interface abstract


【解决方案1】:

这只是围绕面向对象设计的一些基本思考。如果没有更具体的信息,很难就设计类或接口做出明确的决定。

继承、行为和属性主要影响设计。设计方式还取决于大小(想到的数量、类型和种类)。可以看看 Java 语言本身的继承设计,例如集合——Collection 接口和Map 接口及其实现。

以下是一些即时的想法:

public interface Bird {
    public void eat();
    public void layEggs();
}

// Provides implementations for _some_ abstract methods and _no_ new methods.
// Also, some implementations can just be empty methods.
public abstract class AbstractBird implements Bird {
    public void eat() {
        // generic eating implementation
    }
    public abstract void layEggs(); // no implementation at all
}


// Specific to ground-oriented with Bird behaviour
public interface GroundBird extends Bird {
    public void walk();
}


// Specific to air-oriented with Bird behavior
public interface FlyBird extends Bird {
    public void fly();
}


// Ground-oriented with _some_ bird implementation 
public class Penguin extends AbstractBird implements GroundBird {
    public void walk() {
    }
    public void layEggs() {
        // lays eggs on ground
    }
    // Can override eat if necessary
}


// Air-oriented with _all_ bird implementation 
public class Eagle implements FlyBird {
    public void fly() {
    }
    public void layEggs() {
        // lays eggs on trees
    }
    public void eat() {
        // can eat while flying
    }
}

这种设计将允许:

  • 稍后在Bird 接口中提供更多方法如pluck(),并且仅在AbstractBird 中实现。
  • 还有一些类别类可以完全跳过AbstractBird,并且 直接实现Bird接口,更具体的实现 eatlayEggs。这也允许新类别的鸟类扩展 一个新的类或抽象类。
  • 以后添加其他种类的鸟,例如WaterBirds*。

*

public interface WaterBird {
    public void swim();
}
public class Duck implements WaterBird, GroundBird {
    public void swim() {
    }
    public void walk() {
    }
    public void eat() {
    }
    public void layEggs() {
        // lays eggs on ground
    }
}

并且可以创建新的界面,例如Diveable 用于可以潜入水中捕鱼的鸟类,Glideable 用于可以翱翔和滑翔的鸟类。可以看出Glideable是飞鸟的一种特殊行为。另请注意Glidable 也是HangGlider 的一种行为,由鸟和飞机共享。

public interface Glideable {
    public void glide();
}

public class Eagle extends AbstractBird implements FlyBird, Glideable {
    public void fly() {
    }
    public void glide() {
        // eagle specific
    }
    public void layEggs() {
        // lays eggs on trees
    }
    // Can override eat if necessary
}


注意:从 Java 8 开始,可以考虑在接口中使用 staticdefault 方法。

【讨论】:

    【解决方案2】:

    如果所有飞鸟共享相同的飞行方法,您可以使用多级继承。

    创建一个主要的Bird 对象来处理所有鸟类可以执行的操作。然后创建两个扩展Bird 的子类,比如FlyingBirdGroundedBird。这是区分Bird每种类型的能力的更合适的方式。

    abstract class Bird {
        void eat() {
            // Because all birds get hungry
        }
    }
    
    abstract class FlyingBird extends Bird {
        void fly() {
            // Do the flight!
        }
    }
    
    abstract class GroundedBird extends Bird {
        void waddle() {
            // They gotta get around somehow.
        }
    }
    
    class Penguin extends GroundedBird;
    class Goose extends FlyingBird;
    

    编辑

    还有几个其他选项可以处理更多属性。 OP 还询问(在下面的 cmets 中)如果鸟可以飞游泳怎么办?

    在继承链中的某个时刻,您将需要以任何一种方式实现该行为。如果您希望定义 Bird 的多个属性,则可以使用接口代替:

    class Goose extends Bird implements Flyable {
        @Override
        public void fly() {
            // Need to override the fly() method for all flying birds.
        }
    }
    

    现在,假设您希望所有飞鸟都有相同的飞行动作。虽然不一定知道,但您可以创建一个名为 BirdAction 的静态类来保存鸟可能能够使用的所有“动词”。

    您仍然需要为每个Bird 覆盖fly() 方法,但让它们都在BirdAction 中调用相同的静态方法:

    class BirdAction {
        static void fly(Bird bird) {
            // Check if this Bird implements the Flyable interface
            if (bird instanceof Flyable) {
                // All birds fly like this
            } else {
                // This bird tried to fly and failed
            }
        }
    }
    

    我不会说这是一个理想的解决方案,但根据您的实际应用程序,它可能会起作用。 当然,Penguin 默认情况下不会有 fly() 方法,但如果您仍然从 Penguin 类调用 BirdAction.fly(),则会进行检查。

    【讨论】:

    • 我很欣赏这个答案 :) 但现在我希望鸟类能够游泳.. FlyingSwimingBird?接地游泳鸟?考虑到 Java 不能扩展多个抽象类,我不禁觉得这在大规模上最终会非常困难。或许你已经想到了?
    • 在继承链的某个时刻,您将需要实现您想要的行为。通常,在这种情况下,您将拥有interface Flyableinterface Swimmable(或类似的东西),并且您将拥有在每个Bird 中实现该方法...
    • 您添加属性列表的想法可能可行,但正如您所怀疑的那样,这不是最佳做法,并且会导致代码混乱。
    • 另一件需要考虑的事情是,并非所有的鸟类都以相同的方式飞行。所以HummingBird 需要实现代码以“狂暴”,而Eagle 可能“优雅地滑行”。所以无论如何你都需要不同的实现:)
    • 优秀。如果您在答案中添加一些此类信息,那么这基本上就是我从这篇文章中寻找的内容。 :)
    猜你喜欢
    • 1970-01-01
    • 2013-01-09
    • 1970-01-01
    • 1970-01-01
    • 2015-10-16
    • 1970-01-01
    • 2013-04-18
    • 2015-03-13
    • 1970-01-01
    相关资源
    最近更新 更多