搜尋

首頁  >  問答  >  主體

设计模式 - 为什么很多人写 Java/Android 时,选择让同一个类实现多个接口,而不是用多个内部匿名类?

呃…… 标题不太好。让我在问题描述里解释一下。

让我以 Android 开发中一个简单的例子说明:在一个 Activity 中有多个可点击的按钮时,很多人会这么写:

public class ExampleActivity extends Activity implements OnClickListener {

    @Override
    public void onCreate(final Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_example);

        findViewById(R.id.first_button).setOnClickListener(this);
        findViewById(R.id.second_button).setOnClickListener(this);
    }

    @Override
    public void onClick(final View v) {
        switch (v.getId()) {
            case R.id.first_button:
                // bla bla bla
                break;
            case R.id.second_button:
                // bra bra bra
        }
    }
    
}

事实上,Android 官方有些 sample 里面也是这么写的。然而在我看来,这么写代码是非常不优雅的,因为一个 OnClickListener 的实现,只应该关注和点击事件本身相关的内容,它的含义和 Activity 的含义是截然无关的,让同一个类继承/实现他们,会使得这个类的含义变得不清晰。同时,这样还给 ExampleActivity 类增加了一个 public 的方法,削弱了这个类的封闭性。

所以如果像下面这样,会好不少:

public class ExampleActivity extends Activity {

    private OnClickListener onClickListener = new OnClickListener() {
        @Override
        public void onClick(final View v) {
            switch (v.getId()) {
                case R.id.first_button:
                    // bla bla bla
                    break;
                case R.id.second_button:
                    // bra bra bra
            }
        }
    };

    @Override
    public void onCreate(final Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_example);

        findViewById(R.id.first_button).setOnClickListener(onClickListener);
        findViewById(R.id.second_button).setOnClickListener(onClickListener);
    }
    
}

这样写体现了 composition over inheritance 的思想。它避免了上面的所有问题,看起来舒服得多。

不过,这样还是让阅读代码时很不方便——看到 onCreate 里面时,还不得不经常滚动到声明 onClickListener 的地方去,并且在 onClick 中艰难的寻找真正和某个特定按钮相关的代码。当然这两个问题之前那个版本也都无法避免。

另一件糟糕的事情是,不同按钮的 listener 逻辑很可能是相对独立的,放到同一个 onClickListener 里,还是很丑陋。

所以为了进一步避免这几个问题,我一向都是用下面这样的写法:

public class ExampleActivity extends Activity {

    @Override
    public void onCreate(final Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_example);

        findViewById(R.id.first_button).setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(final View v) {
                // bla bla bla
            }
        });
        findViewById(R.id.second_button).setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(final View v) {
                // bra bra bra
            }
        });
    }
    
}

这样的话,不同逻辑间相对独立,看起来非常舒服,便于阅读,并且让我找回了写 JavaScript 的舒畅感觉(划掉)。

那么问题来了:为什么有的人不使用最后一种写法呢,倒反是使用第一种写法呢?

高洛峰高洛峰2888 天前391

全部回覆(9)我來回復

  • 巴扎黑

    巴扎黑2017-04-17 18:02:20

    我也比較贊同第二種寫法,其優勢你也都說到了,就不在贅述。但一直奇怪為什麼Google官方的例子為什麼是用第一種方式寫的,後來在Android的文檔中看到一段話,大致內容是:Java創建對像其實開銷是很大的,如果所有的監聽都創建單獨的對象,這樣會使得程式產生非常多的額外開銷。就是如此Google選擇用同一個物件來完成所有的監聽,這主要是因為早期Android設備性能不是特別好,只能在這種細節上節省性能,而現在設備都已經不在需要太關心這些了,所以用第二種方式也已經無傷大雅。

    回覆
    0
  • PHP中文网

    PHP中文网2017-04-17 18:02:20

    我個人比較喜歡最後一種寫法,而且如果 OnClickListener 要做的事情特別多,我可能還會單獨寫成命名類別。

    不過確實有很多第一種寫法,我猜原因一個是示例誤導,因為示例代碼通常比較少,這樣寫也沒什麼問題,代碼也不會很長,但實際業務的話,代碼可能要多得多;另一個原因可能是有人覺得寫類別太重量級了,當然實際上寫類別並不一定就比寫方法重量級,這是有理論和實際依據的,有人做過分析,我就不多說了。

    在C# 中,是採用委託的實作不同button 的Click 事件,而每個委託對應於當前Form(如果是WinForm 程式的話,ASP.NET 應用類似)的某一個XxxxButton_Click 方法,這種寫法是從古老的VB/VC 遺傳下來的,當然也不能排除UI Designer 要這麼幹的事實。由於Lambda 的普及,也越來越多的人開始轉用類似樓主的第三種寫法,只不過實現的不是一個接口,僅僅是個Lambda——而且還有個前提,在不使用UI Designer 生成代碼的情況下。

    為什麼說到 C#,因為 Java 已經意識到了 Lambda 的優越,所以也實現了 Lambda,以單方法介面的形式模擬了 C# 的委託,那麼樓主第3種寫法還可以更簡捷一些。

    不過任何事務都有兩面性,Lambda 提供方便的同時也帶來一些不便,尤其是邏輯複雜的時候。所以對於邏輯比較複雜的事件處理,我仍然建議寫單獨的處理類,即可以是一個事件介面的實作(例如OnClickListener 的實作),也可以是一個獨立的(或一堆相關的)業務處理類,在onClick 中一句話調用(例如new Business(params).Go()

    回覆
    0
  • 怪我咯

    怪我咯2017-04-17 18:02:20

    composition over inheritance
    這個話怎麼理解?組合一定優於繼承?因為繼承的一些不好名聲,導致大家現在看到繼承就先入為主的拒絕繼承。
    但是實際上真是如此?我不認同。
    在OO中物件關係中大致分為,關聯,依賴,組合,聚合,繼承,泛化等幾種關係,這幾種關係約定了物件之間的關係。
    也就是說對象之間關係該是什麼關係就是什麼關係,準確的說絕對沒有組合由於繼承的概念,不知道是因為繼承的易於實現,導致大家都喜歡使用繼承去復用,其實本質上是對於對象抽象的不夠導致。
    所以有些人就乾脆提出組合優於繼承說法來簡化大家的思考。

    在回到題主的問題,為什麼google會這樣實現?在業務角度來看,一個activity的生命週期大致會處在幾個階段,整個物件的生命週期交給android來實現,但是對於幾個關鍵點透過模板的方式來進行暴露,是一個很合理的設計,不明白題主為什麼會有這樣的想法。

    回覆
    0
  • 巴扎黑

    巴扎黑2017-04-17 18:02:20

    這就是老外對程式開銷的深刻理解。已經深入骨髓了。

    回覆
    0
  • 巴扎黑

    巴扎黑2017-04-17 18:02:20

    我一直用第二種寫法,剛開始學Android的時候是第三種,但是listener內容多的話,看起來也不是太好

    回覆
    0
  • 怪我咯

    怪我咯2017-04-17 18:02:20

    我只在listener很少的時候才寫匿名類,一般都實現接口,如果點擊事件代碼量大,就單獨寫個方法再放在onClick中執行它。我認為這樣程式碼邏輯會比較清晰整潔。

    回覆
    0
  • 伊谢尔伦

    伊谢尔伦2017-04-17 18:02:20

    把事件處理抽象化到一個入口處理,再抽象化成一個通用介面。這是類的概念,不具體到業務。
    你後面的寫法 明顯是具體到物件概念。

    我也是比較習慣這樣寫,反正本來就是具體的業務,具體的對象,也不需要做通用

    回覆
    0
  • ringa_lee

    ringa_lee2017-04-17 18:02:20

    用一次的對象,直接用匿名!

    回覆
    0
  • 天蓬老师

    天蓬老师2017-04-17 18:02:20

    無非就是內部類別會持有外部類別的實例,容易造成記憶體洩漏
    所以就效能方面 第一種寫法優於第二種寫法,第二種寫法優於第三種寫法

    回覆
    0
  • 取消回覆