本篇記錄在上次面試中問到如何以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();
}
我原本是寫Eat
、Walk
、Fly
,但面試官認為擁有行為的介面名稱應該以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");
}
}
Tiger
、Penguin
、Eagle
皆繼承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
介面及基本的實作類別Eating
、Walking
、Flying
。其實就是策略模式(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
版主 想請問Animal已經implements Eatable了 為何三種動物繼承了Animal還需要 implements Eatable??
回覆刪除參考ArrayList,其extends AbstractList也implment List。我想這樣設計是讓類別的特性變得清楚,Animal看不出擁有吃的特性。大概是這樣吧。我抄來的哈哈。
回覆刪除