網頁

2021/4/9

Java OOP面試問題 動物園

本篇記錄在上次面試中問到如何以OOP設計各種動物類別的問題及想法如下。

動物園許多動物,目前有老虎(Tiger),企鵝(Penguin),老鷹(Eagle)三種動物。所有的動物都會吃(eat),老虎和企鵝會走(walk),老鷹會飛(fly)。請設計這些類別。

當下的做法是先把三種具體的動物類別先訂出來。分別為老虎Tiger、企鵝Penguin、老鷹Eagle

Tiger

public class Tiger {}

Penguin

public class Penguin {}

Eagle

public class Eagle {}

接著定義行為介面,分別為會吃Eatable,會走Walkable,會飛Flyable

Eatable

public interface Eatable {
    void eat();
}

Walkable

public interface Walkable {
    void walk();
}

Flyable

public interface Flyable {
    void fly();
}

我原本是寫EatWalkFly,但面試官認為擁有行為的介面名稱應該以able結尾,並舉jdk中的Runnable為例。這點我也很認同,所以修改如上。

然後讓動物類別實作具有的行為。

老虎會吃會走。

Tiger

public class Tiger implements Eatable, Walkable {
    @Override
    public void eat() {
        System.out.println("Tiger eat");
    }

    @Override
    public void walk() {
        System.out.println("Tiger walk");
    }
}

企鵝會吃會走。

Penguin

public class Penguin implements Eatable, Walkable {

    @Override
    public void eat() {
        System.out.println("Penguin eat");
    }

    @Override
    public void walk() {
        System.out.println("Penguin walk");
    }
}

老鷹會吃會飛。

Eagle

public class Eagle implements Eatable, Flyable {

    @Override
    public void eat() {
        System.out.println("Eagle eat");
    }

    @Override
    public void fly() {
        System.out.println("Eagle fly");
    }
}

面試官說怎麼沒有動物(animal)類別?當時我是想因為Java不允許多重繼承,所以繼承抽象類別來達到共有行為可能造成後續修改困難,且Effective Java寫組合優於繼承,因此能不用就不用才會直接實作多個介面來定義行為。或許他希望看到用抽象類別,所以再把所有動物都有的特性抽象出來到動物Animal抽象類別。

Animal

public abstract class Animal implements Eatable {

    protected String name;

    public Animal(String name) {
        this.name = name;
    }

    @Override
    public void eat() {
        System.out.println(name + " eat");
    }
}

TigerPenguinEagle皆繼承Animal

Tiger

public class Tiger extends Animal implements Eatable, Walkable{

    public Tiger() {
        super("Tiger");
    }

    @Override
    public void walk() {
        System.out.println(name + " walk");
    }
}

Penguin

public class Penguin extends Animal implements Eatable, Walkable{

    public Penguin() {
        super("Penguin");
    }

    @Override
    public void walk() {
        System.out.println(name + " walk");
    }
}

Eagle

public class Eagle extends Animal implements Eatable, Flyable {

    public Eagle() {
        super("Eagle");
    }

    @Override
    public void fly() {
        System.out.println(name + " fly");
    }
}

接著面試官說現在要多一個獨角獸(Unicorn)會吃會走也會飛,所以新增一個獨角獸類別Unicorn

Unicorn

public class Unicorn extends Animal implements Eatable, Walkable, Flyable{

    public Unicorn() {
        super("Unicorn");
    }

    @Override
    public void walk() {
        System.out.println(name + " walk");
    }

    @Override
    public void fly() {
        System.out.println(name + " fly");
    }
}

到這邊面試問題就結束了。面試時程式碼只有寫出大概,上面是我現在整理後的結果。


以下為看到各動物的同行為的實作內容重複了,所以再抽出為行為能力,分別為吃Eat、走Walk、飛Fly介面及基本的實作類別EatingWalkingFlying。其實就是策略模式(Strategy Pattern)

Eat

public interface Eat {
    void eat(String name);
}

Eating

public class Eating implements Eat {
    @Override
    public void eat(String name) {
        System.out.println(name + " eat");
    }
}

Walk

public interface Walk {
    void walk(String name);
}

Walking

public class Walking implements Walk {
    @Override
    public void walk(String name) {
        System.out.println(name + " walk");
    }
}

Fly

public interface Fly {
    void fly(String name);
}

Flying

public class Flying implements Fly {
    @Override
    public void fly(String name) {
        System.out.println(name + " fly");
    }
}

建構動物類別時注入行為能力的實作,並將行為的實作委派給行為能力物件。

Animal

public abstract class Animal implements Eatable {

    protected String name;
    protected Eat eat;

    protected Animal(String name, Eat eat) {
        this.name = name;
        this.eat = eat;
    }

    @Override
    public void eat() {
        eat.eat(name);
    }
}

Tiger

public class Tiger extends Animal implements Eatable, Walkable {

    private Walk walk;

    public Tiger(Eat eat, Walk walk) {
        super("Tiger", eat);
        this.walk = walk;
    }

    @Override
    public void walk() {
        walk.walk(name);
    }
}

Penguin

public class Penguin extends Animal implements Eatable, Walkable {

    private Walk walk;

    public Penguin(Eat eat, Walk walk) {
        super("Penguin", eat);
        this.walk = walk;
    }

    @Override
    public void walk() {
        walk.walk(name);
    }
}

Eagle

public class Eagle extends Animal implements Eatable, Flyable {

    private Fly fly;

    public Eagle(Eat eat, Fly fly) {
        super("Eagle", eat);
        this.fly = fly;
    }

    @Override
    public void fly() {
        fly.fly(name);
    }
}

Unicorn

public class Unicorn extends Animal implements Eatable, Walkable, Flyable {

    private Walk walk;
    private Fly fly;

    public Unicorn(Eat eat, Walk walk, Fly fly) {
        super("Unicorn", eat);
        this.walk = walk;
        this.fly = fly;
    }

    @Override
    public void walk() {
        walk.walk(name);
    }

    @Override
    public void fly() {
        fly.fly(name);
    }
}

使用如下。

Tiger tiger = new Tiger(new Eating(), new Walking());
tiger.eat();
tiger.walk();

Penguin penguin = new Penguin(new Eating(), new Walking());
penguin.eat();
penguin.walk();

Eagle eagle = new Eagle(new Eating(), new Flying());
eagle.eat();
eagle.fly();

Unicorn unicorn = new Unicorn(new Eating(), new Walking(), new Flying());
unicorn.eat();
unicorn.walk();
unicorn.fly();

執行結果

Tiger eat
Tiger walk
Penguin eat
Penguin walk
Eagle eat
Eagle fly
Unicorn eat
Unicorn walk
Unicorn fly

參考github


2 則留言:

  1. 版主 想請問Animal已經implements Eatable了 為何三種動物繼承了Animal還需要 implements Eatable??

    回覆刪除
  2. 參考ArrayList,其extends AbstractList也implment List。我想這樣設計是讓類別的特性變得清楚,Animal看不出擁有吃的特性。大概是這樣吧。我抄來的哈哈。

    回覆刪除