设计模式 - 为什么很多人写 Java/Android 时,选择让同一个类实现多个接口,而不是用多个内部匿名类?
高洛峰
高洛峰 2017-04-17 18:00:20
0
9
286

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

让我以 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 的舒畅感觉(划掉)。

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

高洛峰
高洛峰

拥有18年软件开发和IT教学经验。曾任多家上市公司技术总监、架构师、项目经理、高级软件工程师等职务。 网络人气名人讲师,...

reply all(9)
Ty80

我个人比较喜欢最后一种写法,而且如果 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()

Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template
About us Disclaimer Sitemap
php.cn:Public welfare online PHP training,Help PHP learners grow quickly!