简体   繁体   中英

Attribute Inheritance with Interfaces and Abstract Classes - Java

Alright, so I'm trying to figure out the best structure for this. I would like to know what would be considered the best practice for these situations and the overall most effective.

The Problem

Let's say I'm creating a little world. This world consists of different types of birds, So I create a Bird class to act as a parent for all birds.

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

Now I want to create different types of birds. Let's create a Penguin and a Goose class and have them extend Bird.

public class Penguin extends Bird{
    //penguin stuff
}

public class Goose extends Bird{
    //goose stuff
}

So far so good, but now for the wrench. Let's give birds the ability to fly. Surprise , not all birds can fly - such as the penguins! So let's approach our options..

Making a fly method inside of the Bird class is out of the option (not all birds fly). Another smelly way is having multiple parent classes, such as BirdsThatFly, because this would become an enormous mess, especially when other birdly attributes are added into the mix.

Option 1

Interfaces

One viable option that in theory sounds like the right idea would be to use interfaces. For example, I could have a Fly interface.

public interface Fly {
    public void fly();
}

This would allow me to implement Fly on only Bird child classes that can fly. Example..

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

This appears clean and forces a fly method onto the goose, and seems it could work really well when adding other attributes.

The thing that seems wrong, is that I would still have to create a custom fly for each type of bird, and there could be loads of birds! Would default voids help me here? I feel they could be fairly limiting as these attributes grow.

Perhaps I can be told otherwise and how to rethink this interface structure.

Option 2

My Own Attribute Class

This involves creating attribute objects and adding them to the individual bird classes. If I'm correct with interfaces, this option may be a valid approach.

The attribute class would look something like this

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

And an attribute could look like this

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

This fly attribute can now be added to a bird by having an attribute list in Bird, and populating it in the bird type classes.

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;
    }
}

And adding it to a Goose...

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

This approach to me seems very customizable, but also seems out of practice and maybe not immediately be clear what each class is able to do. It also requires a lot of work to properly set up on larger scale to be able to execute specific attributes.

Is there a way that would be considered better practice, or are either of these a reasonable approach or a good start?

If all flying birds share the same method of flying, you could use multiple levels of inheritance.

Create a main Bird object that handles the actions ALL birds can do. Then create two subclasses that extend Bird , say FlyingBird and GroundedBird . This is a more appropriate way to separate the abilities of each type of 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;

EDIT

There are a couple of other options for handling more attributes. The OP also asks (in comments below) what to do if the bird can fly and swim?

At some point in the inheritance chain, you will need to implement the behavior either way. If you are looking to define multiple attributes of a Bird , this would be a case to use Interfaces instead:

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

Now, the suppose you want all flying birds to have the same action for flying. While not necessarily idea, you could create a static class called BirdAction to hold all the "verbs" a bird might be capable of.

You would still need to override the fly() method for each Bird but have them all call the same static method within 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
        }
    }
}

I would not say that's an ideal solution, but depending on your real-world application, it may just work. Granted, a Penguin would not have the fly() method by default, but the check is there in case you still call BirdAction.fly() from the Penguin class.

This is just some basic thinking around object-oriented design. Without more specific information its difficult to make clear decisions about one goes about designing classes or interfaces.

Inheritance, behavior and attributes mostly influence the design. The way to design also depends upon the size (number of, type of and kind of - come to mind). One can take a look at the inheritance design in Java language itself, eg, collections - both the Collection interface and the Map interface and their implementations.

Here are some instant thoughts:

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
    }
}

This design will allow:

  • provide more methods like pluck() in the Bird interface later on and has implementation only in AbstractBird .
  • also some category classes can skip the AbstractBird altogether and directly implement Bird interface with more specific implementations of eat and layEggs . This also allows a new category of bird extend a new class or abstract class.
  • to add other kind of birds like WaterBirds * at a later date.

*

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
    }
}

And can create new interfaces like Diveable for birds that can dive into water to catch fish and Glideable for birds that can soar and glide. One can see Glideable is a special behaviour for flying birds. Also note the Glidable is also a behavior of HangGlider and is shared by both the bird and an aircraft.

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
}


NOTE: As of Java 8 one can consider using static and default methods in interfaces.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM