輪講コメント5/7

輪講資料テーマ2

短いプログラムは可視性を上げるために、コンパクトさが要求されます。 一方、大きなプログラムを作るには、自然な形でプログラムの分割をする ことが要求されます。

今回提示してもらったプログラムは輪講の教材として適切でしたが、大規 模開発に応用する仕方についてコメントします。

それでは提示されたプログラムについて見てみましょう。

MainActivity.java


package com.example.rinkou02;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity implements View.OnClickListener{

    int select = 0;
    Button[] btn = new Button[4];
    TextView txt01;
    TextView txt02;

    int ct_round = 0;
    int ct_win = 0;
    int ct_lose = 0;
    int ct_draw = 0;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //リソースIDの設定
        btn[0] = (Button)findViewById(R.id.button01);
        btn[1] = (Button)findViewById(R.id.button02);
        btn[2] = (Button)findViewById(R.id.button03);
        btn[3] = (Button)findViewById(R.id.button04);
        txt01 = (TextView)findViewById(R.id.text01);
        txt02 = (TextView)findViewById(R.id.text02);
        //Listenerの設定
        for (int i=0 ; i< btn.length ; i++)
            btn[i].setOnClickListener(this);
        //TextViewの初期化
        txt01.setText(getString(R.string.init_txt));
        txt02.setText(getString(R.string.text_2, 0, 0, 0, 0));
    }

    @Override
    public void onClick(View view) {
        //ボタンを押したときの動作
        switch (view.getId()){
            case R.id.button01:{
                select = 0;
                break;
            }
            case R.id.button02:{
                select = 1;
                break;
            }
            case R.id.button03:{
                select = 2;
                break;
            }
            case R.id.button04:{
                select = -1;
                break;
            }
        }
        //更新
        if(select >= 0){
            Janken j = new Janken(getResources(),select);
            ct_round++;
            if (j.getResultId() == 0) ct_draw++;
            if (j.getResultId() >  0) ct_win++;
            if (j.getResultId() <  0) ct_lose++;

            txt01.setText(j.toStringResult());
        }
        else{
            ct_round = 0;
            ct_win = 0;
            ct_lose = 0;
            ct_draw = 0;

            txt01.setText(getString(R.string.init_txt));
        }

        //表示更新
        txt02.setText(getString(R.string.text_2, ct_round, ct_win, ct_lose, ct_draw));
    }
}

Inner Class の勧め

Java ではクラスやインターフェイスを宣言しないとプログラムが組めま せんが、このクラスやインターフェイスは型でもあります。 そして、継承(extends)や実装(implements)を行うことで、それらの型を 引き継ぐことになります。

継承や実装は便利な機能であるとともに、様々な副作用を伴うため、乱用 はトラブルの元です。 よく言われているのは、「一クラス一役務」というもので、一つのクラス が、複数の立場で仕事をするのは防ぐという立場です。

Android 開発において MainActivity はプログラムのスタート地点である 一方、スレッドの根でもあり、ユーザインタフェースを受信するところで もあります。 そのため、サンプルプログラムや入門書では、オブジェクト指向の様々な マナーを振り回さず、MainActivity に様々な役務を行わさせるようなプ ログラムがよく見られます。 これらはサンプルプログラムという立場としては役に立ちますが、そのま ま大規模なプログラムへ移行すると、「大きな泥団子」や「マジックボタ ン」と呼ばれるアンチパターン(失敗の定石)に陥ります。

このプログラムでまずお進めしたいのは、「MainActivity にインターフェ イスを実装しない」ことです。 これを実現するには、どこに onClick メソッドをどこに置くかがポイントになり ます。 onClick メソッドをよく見ると、MainActivity で宣言している様々なフィー ルド変数を使っています。 この様な場合、まずは、onClick メソッドのみをくくるクラスを MainActivity の中へ作ります。 このように、クラスの中に作られたクラスをインナークラス と言います。 インナークラスは親クラスの実装を減らし、役務を切り分ける働きがある 一方で、インナークラスからはフィールドへアクセスできます。

インナークラス化したプログラムを示します。

MainActivity.java-1


package com.example.rinkou02;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    int select = 0;
    Button[] btn = new Button[4];
    TextView txt01;
    TextView txt02;

    int ct_round = 0;
    int ct_win = 0;
    int ct_lose = 0;
    int ct_draw = 0;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //リソースIDの設定
        btn[0] = (Button)findViewById(R.id.button01);
        btn[1] = (Button)findViewById(R.id.button02);
        btn[2] = (Button)findViewById(R.id.button03);
        btn[3] = (Button)findViewById(R.id.button04);
        txt01 = (TextView)findViewById(R.id.text01);
        txt02 = (TextView)findViewById(R.id.text02);
        //Listenerの設定
        View.OnClickListener myListener = new MyOnClickListener()
        for (int i=0 ; i< btn.length ; i++)
            btn[i].setOnClickListener(myListener);
        //TextViewの初期化
        txt01.setText(getString(R.string.init_txt));
        txt02.setText(getString(R.string.text_2, 0, 0, 0, 0));
    }
class MyOnClickListener implements View.OnClickListener {
    @Override
    public void onClick(View view) {
        //ボタンを押したときの動作
        switch (view.getId()){
            case R.id.button01:{
                select = 0;
                break;
            }
            case R.id.button02:{
                select = 1;
                break;
            }
            case R.id.button03:{
                select = 2;
                break;
            }
            case R.id.button04:{
                select = -1;
                break;
            }
        }
        //更新
        if(select >= 0){
            Janken j = new Janken(getResources(),select);
            ct_round++;
            if (j.getResultId() == 0) ct_draw++;
            if (j.getResultId() >  0) ct_win++;
            if (j.getResultId() <  0) ct_lose++;

            txt01.setText(j.toStringResult());
        }
        else{
            ct_round = 0;
            ct_win = 0;
            ct_lose = 0;
            ct_draw = 0;

            txt01.setText(getString(R.string.init_txt));
        }

        //表示更新
        txt02.setText(getString(R.string.text_2, ct_round, ct_win, ct_lose, ct_draw));
    }
}
}

小さなメソッドの勧め

次に、オブジェクト指向プログラミングの一歩として、着目してほしいの は、よく似たプログラムの操作、特に、.(ピリオド)が着いた操作です。 これをつまらないと思っても、一つのメソッドとして独立させましょう。 特に、コメントがある部分というのは、プログラムの意味がコメントに書 いてあって、プログラム自体に直感性が無い場合が多いので、コメントが 書かれている部分を、コメントがメソッド名になるようなメソッドにしま す。

メソッド化したものが次のようになります。

MainActivity.java-2


package com.example.rinkou02;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

    int select = 0;
    Button[] btn = new Button[4];
    TextView txt01;
    TextView txt02;

    int ct_round = 0;
    int ct_win = 0;
    int ct_lose = 0;
    int ct_draw = 0;

    private setResourceID(){
        //リソースIDの設定
        btn[0] = (Button)findViewById(R.id.button01);
        btn[1] = (Button)findViewById(R.id.button02);
        btn[2] = (Button)findViewById(R.id.button03);
        btn[3] = (Button)findViewById(R.id.button04);
        txt01 = (TextView)findViewById(R.id.text01);
        txt02 = (TextView)findViewById(R.id.text02);
    }
    private setListener(){
        //Listenerの設定
        View.OnClickListener myListener = new MyOnClickListener()
        for (int i=0 ; i< btn.length ; i++)
            btn[i].setOnClickListener(myListener);
    }
    private void clearHand(){
            txt01.setText(R.string.init_txt);
    }
    private void showHand(Janken j){
            txt01.setText(j.toStringResult());
    }
    private void updateScore(){
        txt02.setText(getString(R.string.text_2, ct_round, ct_win, ct_lose, ct_draw));
    }
    private void clearScore(){
        txt02setText(getString(R.string.text_2, 0, 0, 0, 0));
    }
    private initializeTextView(){
        //TextViewの初期化
        clearHand();
        clearScore();
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //リソースIDの設定
        setResourceID();
        //Listenerの設定
        setListener();
        //TextViewの初期化
        initializeTextView();

    }
class MyOnClickListener implements View.OnClickListener {
    @Override
    public void onClick(View view) {
        //ボタンを押したときの動作
        switch (view.getId()){
            case R.id.button01:{
                select = 0;
                break;
            }
            case R.id.button02:{
                select = 1;
                break;
            }
            case R.id.button03:{
                select = 2;
                break;
            }
            case R.id.button04:{
                select = -1;
                break;
            }
        }
        //更新
        if(select >= 0){
            Janken j = new Janken(getResources(),select);
            ct_round++;
            if (j.getResultId() == 0) ct_draw++;
            if (j.getResultId() >  0) ct_win++;
            if (j.getResultId() <  0) ct_lose++;

            showHand(j);
        }
        else{
            ct_round = 0;
            ct_win = 0;
            ct_lose = 0;
            ct_draw = 0;

            clearHand();
        }

        //表示更新
        updateScore();
    }
}
}

その他

この他、スコアやボタンでクラスを作れば、そのクラスへプログラムを移譲できそうな部分があります。 このようにして、MainActivity からさまざまな作業を他のクラスへ移譲すれば、最終的には、初期化と、イベントの交通整理のみのクラスになり、保守性が上がります。


坂本直志 <sakamoto@c.dendai.ac.jp>
東京電機大学工学部情報通信工学科