避免用于更新 UI 的静态方法(可能在 Util class 中)的合适方法

The suitable way for avoiding static method (in Util class maybe) which use for updating UI

我是 Android 的初学者,我阅读并查看使用 Util class 中的静态方法更新 UI 是否不利于单元测试。如何以适当的方式避免它以维护代码和单元测试?

示例:

class ActivityA {
    private View view;
    private MyListener myListener;
    public void methodB() {
        Util.callLogicB(this, view, myListener);
    }
}

class ActivityB {
    private View view;
    private MyListener myListener;
    public void methodC() {
        Util.callLogicB(this, view, myListener);
    }
}

class Util {
    public static void callLogicB(Context context, View view, MyListener listener) {
    // do something with view
    }
}

AFAIK,最好将 Utility classes 专门用于静态辅助方法,这些方法通常 return 原始数据和参考数据,而不是仅更新 UI在活动中。例如,示例 Utility class 将涉及一系列方法,例如:

  • 一种从 HTTP 请求 URL 中以字符串格式构建 URL 对象的方法
  • 一种在成功连接到 HTTP 服务器后 return 可读 JSON 数据的方法
  • 一种提取 JSON 数据并将其存储在 returnable ArrayList
  • 中的方法
  • 一个 public 静态方法,它调用了上述所有方法,最终 return 通过其后台线程将数据的 ArrayList Activity 稍后呈现 UI通过它的UI线程

我认为实现这一点的最佳方法是将您的观点(您的 activity)与您的逻辑 class 完全分离。这可以使用 接口 .

来完成

查看下面的代码以了解接口的工作原理。我创建了一个带有 textView 作为布局的 mainActivity。我创建的 class LogicWithTextUpdate 能够在您的 mainActivity 中更新 textView 而无需直接引用它

public class MainActivity extends Activity implements MyViewListener {
    //Your logic class, that is capable of updating a textview
    LogicWithTextUpdate logicWithTextUpdate;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        //This is just to create a layout (with a textview inside)
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //This creates the class using constructor (and set listener)
        logicWithTextUpdate = new LogicWithTextUpdate(this);

        //Update or execute your logic. This will update textview using interface MyViewListener (onUpdateTextListener)
        //This can be executed using a button (Onclick...)
        logicWithTextUpdate.doSomeLogic(2);
    }

    @Override
    public void onUpdateTextListener(String text) {
        //Only here code runs inside your activity class
        TextView textView = (TextView) findViewById(R.id.textView);
        textView.setText(text);
    }
}

如你所见。 MainActivity 将使用 "listener" onUpdateTextListener 更新 textview。这是在接口 class:

中描述的
public interface MyViewListener {
    //to be implemented inside your mainActivity
    public void onUpdateTextListener(String text);
}

现在我可以实现一个 class 可以执行一些逻辑并 触发侦听器 :

public class LogicWithTextUpdate {
    MyViewListener myViewListener;

    //Constructor here is used to set myViewListener (but you can use a setter like setMyViewListener)
    public LogicWithTextUpdate(MyViewListener myViewListener) {
        this.myViewListener = myViewListener;
    }

    public void doSomeLogic(int a) {
        //Some logic
        a = a * 2;
        a = a + 1;

        //Update text using listener. This will update mainActivity, because it is implementing MyViewListener
        myViewListener.onUpdateTextListener(String.valueOf(a));
    }
}

方法 doSomeLogic 是从您的 activity 执行的,但逻辑在 class 中。执行逻辑后,它 触发 myViewListener.onUpdateTextListener,在您的 activity 中执行 onUpdateTextListener

一切都断开连接。您可以在 LogicWithTextUpdate 中更改视图而不进行任何修改,或者 在 Activy 中更改逻辑而不进行修改。始终是一个好习惯。

静态 class 不利于单元测试,因为您不能模拟或存根它。但是静态实用程序 class 可能是非常有用的方法。我建议您将一些接口引用包装到静态实用程序 class 中,您仍将拥有相同的实用程序 class 签名,但它允许您更改实用程序 classes 的行为测试或其他目的:

final class Util {
    static IUtil util = new DefaultUtil();
    private Util() {}

    public static void callLogicB(Context context, View view, MyListener listener) {
        util.callLogicB(context, view, listener);
    }

}


interface IUtil {
    public void callLogicB(Context context, View view, MyListener listener);
}

final class DefaultUtil implemets IUtil {
    @Override
    public void callLogicB(Context context, View view, MyListener listener) {
    // do something with view
    }
}

final class TestUtil implemets IUtil {
    @Override
    public void callLogicB(Context context, View view, MyListener listener) {
    //your test logic
    }
}

@Before
void setUp() {
    Util.util = new TestUtil();
}