[創意菜色] 問題不是 Abstract 與 Interface 的區別這麼單純而已

前些陣子有人問我「Abstract 與 Interface 的區別?」,腦袋中閃過過去所有使用過 Abstract 及 Interface 的情境,整理之後給出我實務上設計的時候是怎麼操作 Abstract 與 Interface 的答案,回頭想想這樣有點文不對題,對方似乎也沒有得到答案,不過這樣的過程讓我有種見山不是山的感覺,促使我回頭想想在思考這個問題答案的過程當中不單純的點是什麼?以及為何我給出這樣的答案?

Abstract 與 Interface 的區別?

MSDN 的定義

來自 IBM developerWorks 的見解

IBM developerWorks 的文章裡面舉了一個例子

abstract class Door
{
    abstract void Open();

    abstract void Close();
}

interface IAlarm
{
    void Alarm();
}

class AlarmDoor : Door, IAlarm
{
    void Open() { }

    void Close() { }

    void Alarm() { }
}

概念性的 Door 要增加 Alarm 的功能,應該將 Alarm() 方法定義在 IAlarm 的 interface 裡面,而 AlarmDoor 這個 concrete class 則要繼承 Door、實作 IAlarm。

這個世界並不是這麼的單純

剛剛舉的例子乍看之下沒有問題,這是正確的設計方式,但是今天如果是一家做安全系統的公司,而這家公司所生產的門全部都有警報器,是不是就不那麼單純了?

不單純的點在於遇到的問題都不太一樣,沒辦法用一套解決方案適用所有的問題,如果公司所生產的門全部都有警報器,而那樣的設計可能就不太恰當了。

再舉個例子

abstract class Order
{
    public int Id { get; set; }

    public ICollection<Product> ProductList { get; set; }

    public abstract Customer GetCustomer(int customerId);
}

interface IDelivery
{
    string GetAddress();

    string GetStatus();
}

class AuctionOrder : Order, IDelivery
{
    public override Customer GetCustomer(int customerId)
    {
        DoSomething();
    }

    public string GetAddress()
    {
        DoSomething();
    }

    public string GetStatus()
    {
        DoSomething();
    }
}

概念性的訂單(Order) 有 Id, ProductList, GetCustomer() 屬性及方法,IDelivery 有 GetAddress(), GetStatus() 方法,而 AuctionOrder 繼承自 Order、實作 IDelivery,這樣設計好像還可以。

但是實務上我會這樣設計

abstract class Order
{
    public int Id { get; set; }

    public ICollection<Product> ProductList { get; set; }
}

interface IOrdering
{
    Customer GetCustomer(int customerId);
}

interface IDelivery
{
    string GetAddress();

    string GetStatus();
}

class AuctionOrder : Order, IOrdering, IDelivery
{
    public Customer GetCustomer(int customerId)
    {
        DoSomething();
    }

    public string GetAddress()
    {
        DoSomething();
    }

    public string GetStatus()
    {
        DoSomething();
    }
}

外加一個 IOrdering 定義 Order 的行為,C# 及 Java 的類別有一個限制,就是只能繼承自一個親生爸爸,但是可以有很多乾爸爸(Interface),在開發的過程當中,我經常需要重構、使用者經常變更需求,因此在設計時會率先使用 Interface 定義合約,視需求有必要時再抽出來定義成 Abstract。

不單純的點

可是有人就是會又有不同的意見,這個就是我要提的重點「問題不是 XXX 這麼單純而已」,每個人遇到的情境不一樣,在設計軟體的過程當中我們應該要不停地問自己三個問題:

  • 使用者的需求是什麼?
  • 使用者遇到的問題是什麼?
  • 有沒有辦法舉幾個實際例子?多給幾個情境?
我對使用者的定義比較廣「只要是需要接我寫的 API、Review 我寫的 Code、操作我設計的軟體,對我而言都是我的 User。」,因為「程式碼是寫給人看的,程式是設計給人用。」,只要當下的設計儘量讓人理解、滿足使用者的需求、解決使用者的問題,目的就達到了。

結論

學習模式三階段:「守、破、離」,如果我們一直處於在「守」的階段,當問題變得複雜時,難保不會發生這種情況。

這個工具應該怎麼被用?以及,如何使用這個工具?

上面這兩個問題我個人比較重視後者,當然前者也很重要,我們必須先透過學習,了解工具的使用方式,然後再透過思考解決方案的過程去解放我們對這個工具的成見,後者才是我學習這個工具的價值。

任何的理論、原理、技術都是工具而已,學習它們就好像在自己的意識中種下一顆種子,因緣具足成熟之時,自會有所連結而生根發芽、開花結果,人類的大腦普遍記憶力有限,但是連結力很強,連結愈多想像力就愈多,可選擇的項目就愈多,面對問題時連結力會帶領我們連結這些工具,產生解決方案,進而讓工具受到活用。

有黑、有白,有高、有矮,有男、有女…,併發出很多複雜的問題,這個世界才顯得精彩,太單純,這個世界就太單調了。

相關資源

C# 指南
ASP.NET 教學
ASP.NET MVC 指引
Azure SQL Database 教學
SQL Server 教學
Xamarin.Forms 教學