C#でクラスを作ろう(2)/メソッド

3月 17, 2020

このシリーズについて

C言語やC++言語などを学んではいるけどクラスをあまり作ったことが無い、という方を対象にしています。
このシリーズでは、C#でクラスを作るための基本的な構文を解説しています。C++やJavaなどと共通している概念も多いですが、サンプルコードは基本的にC#で解説します。ところどころ、C++特有の概念を解説することもあります。

クラスはデータの集まりであるということをC#でクラスを作ろう(1)/構造体とクラスで解説しました。クラスオブジェクトに対してはいろいろな操作が発生します。その操作は、メソッド(メンバ関数)という形で定義します。

スポンサーリンク

オセロの盤面の例

オセロの盤面を表わすクラスを考えてみます。

//石の種類を表わす列挙型
public enum OthelloStone
{
    None,   //何も置かれていない
    White,  //白石
    Black   //黒石
}
 
//オセロの盤面を表わすクラス
public class OthelloField
{
    private const int FW = 8;
    private const int FH = 8;
    private OthelloStone[,] _field
  
    public OthelloField()
    {
        //OthelloFieldクラスのコンストラクタ
        _field = new OthelloStone[FW, FH];
        InitField();
    }
    public void InitField()
    {
        //盤面を初期配置にする
        //一旦全てを空白にする
        for (int ix = 0; ix < FW; ix++)
            for (int iy = 0; iy < FH; iy++)
                _field[ix, iy] = OthelloStone.None;
        //中央に初期の白石と黒石を配置
        _field[3, 3] = OthelloStone.White;
        _field[3, 4] = OthelloStone.Black;
        _field[4, 3] = OthelloStone.Black;
        _field[4, 4] = OthelloStone.White;
    }
}

C#ではこのようになります。C++などの他の言語の場合でも似たような感じになりますので、適宜読み替えてください。また、変数名などの命名規約もお好みに合わせて読み替えてください。

さて、オセロの盤面を表わすこのOthelloFieldクラスですが、8×8のマス目に何が置かれているかを実際に保持しているのは、"private OthelloStone[,] _field"という2次元配列です。"private"についての解説はここでは省略します。

この後に定義されている"OthelloField(クラス名と同名のもの)"と、"InitField"が、メソッドと呼ばれるものです。C++ではメンバ関数と呼んだりしますが、同じ意味です。

メソッドとは要するに、関数です。一連の処理のコードから成り、引数を受け取ったり返り値を返したりすることもできます。

まず最初に2つ目の"InitField"メソッドですが、これはオセロの盤面を初期状態にするメソッドです。8×8のマス目を一旦全て空白にしたあと、中央に初期状態の白石と黒石を配置します。

初期状態

オセロの盤面を初期化するというのは、まさにクラスオブジェクトに対する操作です。このような操作を、メソッドという形で次々と定義していきます。

クラス名と同名のメソッド"OthelloField"は、コンストラクタと呼ばれる特殊なメソッドです。コンストラクタはクラスオブジェクト生成時に必ず自動的に実行されます。

OthelloField ofd = new OthelloField(); //ここで自動的にコンストラクタが実行されている

コンストラクタでは、正常な形でクラスオブジェクトを生成するための一番基本的なコードを記述します。この例では、オセロの盤面の中身を表わす8×8の2次元配列を生成しています。この2次元配列が無ければそもそもオセロの盤面は成立しないので、そのような一番基本的な処理をコンストラクタに書くわけです。

そしてこのコンストラクタではさらに、InitFieldメソッドを呼んでいます。生成と同時に、ついでに盤面を初期状態にしておくわけですね。

石を置く処理

OthelloFieldクラスに、白か黒の石を置いて、ルール通りにひっくり返す処理を考えてみます。これもまた、「オセロの盤面」というクラスオブジェクトに対する操作です。つまり、メソッドとして定義するのがいいですね。

public class OthelloField
{
    //中略・・・
 
    public bool PutStone(OthelloStone stone, int x, int y)
    {
        //座標(x, y)に、stone(WhiteまたはBlack)を置く。
        //ひっくり返せる石があるときはルールに従って石をひっくり返し、
        //このメソッドはtrueを返す。
        //ひっくり返せる石が無いときは置くことができないので、
        //このメソッドはfalseを返す。
 
        //以下、アルゴリズム・・・
        return true; //またはfalse
    }
}

つまり、初期状態に対してPutStone(OthelloStone.White, 5, 3)とすれば、

PutStone後

こうなって欲しいわけです。その実際の処理を、コードの中の「//以下、アルゴリズム」のところに書いていきます。余力のある人は実際にそのコードを考えてみてください。ちょっとした頭の体操になりますよ。

今はそれは本題ではないので、とりあえずがんばって数十行かそこらの石をひっくり返す処理が完成したとしましょう。

クラスの外側の処理

まだまだいろいろ不十分ですが、とりあえず盤面の初期化と、石を置く処理だけができるOthelloFieldクラスが定義できました。

では、その外側の処理を書いてみましょう。

public class Form1 : Form
{
    private _ofd = new OthelloField();      //オセロ盤面クラスオブジェクト
    private _nowStone = OthelloStone.White; //現在のプレイヤーの色
 
    private void BtnInitField_Click(object sender, EventArgs e)
    {
        //初期化ボタンを押したとき
        _ofd.InitField();
    }
 
    private void BtnPutStone_Click(object sender, EventArgs e)
    {
        //石を置くボタンを押したとき
        //置く座標をTextBoxから取得
        int x = int.Parse(txtPutX.Text);
        int y = int.Parse(txtPutY.Text);
        if (_ofd.PutStone(_nowStone, x, y) == true)
        {
            //置くことに成功したときは、次のプレイヤーに交代
            _nowStone = (_nowStone == OthelloStone.White) ? OthelloStone.Black : OthelloStone.White;
        }
    }
}

現在のプレイヤーを_nowStone(WhiteまたはBlackの値を取る)に保持しておき、画面上のbtnPutStoneボタンを押すと、指定座標に石を置きます。置くことに成功したときは、プレイヤーを交代します。

「石を置く操作をしたとき」という処理を書いているわけですが、そのコードを書いているときのプログラマーの関心事は一体何だと思いますか? それは日本語で書くと、

  • 石を置く座標を決定する
  • 石を置いて盤面を変化させる
  • 石を置くことに成功したときは、プレイヤーを交代する

ということです。上のコードをもう一度見てみてください。今ここに書いた日本語が、ほとんどそのまま書かれていますよね? 2次元配列がどうとか、隣に違う色があるかどうかの判定だとか、白ならば黒、黒ならば白にひっくり返すとか、そういう内部処理は一切書かれていません。そんな内部処理は、「石を置く操作をしたとき」の関心事ではないのです。

これはまさに、PutStoneというメソッドによってクラスの外側と内側の関心事を別々に分けており、外側の操作を書いているときは外側だけを考えればよく、内側の内部処理を書いているときは内側だけを考えればいいようになっています。

関心事を分けると、コードを書いているときに使う脳のリソースを減らすことができます。どんなに難しいことでも、単純なものに分けて考えることができます。

さらに、コードの可読性も上がります。仮に、指定座標を取得する処理も、石が置けるかどうか判定する処理も、実際に石をひっくり返す処理も、プレイヤーを交代する処理も、ずらーっと並べて書いてあったら読みにくいですよね? でも、クラスの外側と内側で処理を分けておけば、コードはとても読みやすくなります。

このあたりの考え方は、メソッドの中の人と外の人で解説した内容にも通ずるところがあります。クラスメソッドの場合はそれに加えて、クラス内部のデータの保持の仕方さえも内部に隠す情報隠蔽というメリットがあります。情報隠蔽については、また別の機会に解説します。

どちらから書いてもよい

クラスメソッドの内側か外側か、どちらから書けばよいかというと、「どちらでもよい」が答えになります。

プレイヤーの交代だとかは一旦置いておいて、内側の「石をひっくり返す処理」を先に書いてもかまいません。特にその部分は難しいアルゴリズムを使う部分なので、じっくりと先に完成させておきたい気持ちのときもあると思います。

逆に、難しいアルゴリズムは一旦置いておいて、外側の「ボタンを押したら石を置く」という処理とか、プレイヤーを交代させる処理とかを先に書いてもかまいません。その場合、PutStoneメソッドの中身はまだ完成していない状態ですが、とりあえず中身スカスカのPutStoneメソッドの定義だけ作っておいて、外堀から完成させていくということも十分に可能です。

たくさんクラスやメソッドを作ろう

最初は、何をどのくらいの規模でメソッドにすればいいか、感覚がなかなか掴めないかもしれません。

何が正解とかを考えるより、まずはとにかくたくさんのクラスやメソッドを作って、クラスというものの雰囲気を掴んでいきましょう。

スポンサーリンク