いもづる オブジェクト指向

第1回 数計カウンター

(2006/02/23)

 

▲このページのTOPへ

概要・仕様

野鳥や、交通量など数を数えるときにカチカチやるあのカウンターのクラスを作ってよう。
仕様はこうです。

▲このページのTOPへ

LUKIAくんの作成クラス

LUKIAくんの作成ソースです。ご覧ください。
(C#は未経験でC#での記述に悩んでいるということだったので、今回はVB.NETとなっています)

 1|Imports System.Windows.Forms
 2|
 3|Public Class Form1
 4|    Inherits System.Windows.Forms.Form
 5|
 6|    Private mobjMain As Main = New Main
 7|
 8|    'リセットボタン押下時
 9|    Private Sub btnReset_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) 
10|                                                                       _ Handles btnReset.Click
11|        If (MessageBox.Show("リセットしますか?", "情報", MessageBoxButtons.OKCancel)) Then
12|            Me.txtCounter.Text = "0"
13|        End If
14|    End Sub
15|    
16|    'カウンターボタン押下時
17|    Private Sub btnCounter_Click(ByVal sender As Object, ByVal e As System.EventArgs) 
18|                                                                       _ Handles btnCounter.Click
19|        Me.chkIsMust()
20|        Me.CountUp()
21|    End Sub
22|    
23|    Private Sub chkIsMust()
24|        If (Me.txtKeta.Text.Equals("")) Then
25|            MessageBox.Show("桁数を入力してください", "停止", MessageBoxButtons.OK)
26|            Me.txtKeta.Focus()
27|        End If
28|    End Sub
29|    
30|    'カウンターメイン処理
31|    Private Sub CountUp()
32|        If (chkMaxCount()) Then
33|            If (MessageBox.Show("最大値に達しました。0からスタートしますか?", "情報"
34|                                         _ , MessageBoxButtons.OKCancel) = DialogResult.OK) Then
35|                Me.txtCounter.Text = "0"
36|            Else
37|                '何もしない
38|            End If
39|        Else
40|            Me.txtCounter.Text = CType((CType(Me.txtCounter.Text, Integer) + 1), Integer)
41|        End If
42|    End Sub
43|    
44|    Private Function chkMaxCount() As Boolean
45|        mobjMain.mintKeta = Me.txtKeta.Text
46|
47|        Dim intCntKeta As Integer = 0
48|        Dim intCntCounterKeta As Integer = Me.txtCounter.Text.Length
49|        '最大値とカウンター値が同じなら以下を実行
50|        If (CType(Me.txtCounter.Text, Integer).CompareTo(CType(Me.txtMax.Text, Integer)) = 0) Then
51|            Return True
52|        End If
53|
54|        Return False
55|    End Function
56|    
57|    Private Sub txtKeta_Leave(ByVal sender As Object, ByVal e As System.EventArgs) 
58|                                                                        _ Handles txtKeta.Leave
59|
60|        If Not (Me.txtKeta.Text.Equals("")) Then
61|
62|            Dim intCntKeta As Integer = 0
63|            mobjMain.mintKeta = CType(Me.txtKeta.Text, Integer)
64|
65|            For intCntKeta = 1 To mobjMain.mintKeta
66|                '桁数分の最大値を挿入
67|                Me.txtMax.Text = Me.txtMax.Text + "9"
68|            Next
69|        End If
70|
71|    End Sub
72|End Class
73|
74|Public Class Main
75|
76|    Public mintKeta As Integer
77|
78|End Class

   ※ "Windows フォーム デザイナで生成されたコード" 内は省いています

いきなり仕様のみでフリーにクラスを作成してもらうのはちょっと難しかったようです。 LUKIAくんの作成したこのフォームは、アプリとしてはカウンターの動作は一応しますが、フォームにカウンターの 機能が組み込まれており、「カウンター」となるクラスが残念ながらいません。
しかし「カウンター」だけの機能であっても、実際このように1つのクラスとして実現するのは初めはなかなか難しいものです。 LUKIAくんも、最終的にカウンター動作するものは作れましたが、クラスとして実現(カウンターとしてのクラスを作り、 それを利用するフォームを作成)することができませんでした。 第一回開始当初は、作成したクラスの精度をここから上げていく予定でしたが、今回はここでストップし、 まずは、「クラス(クラス定義)」を考えることから再スタートしようと思います。

▲このページのTOPへ

いおの作成クラス

いおが作成した数計カウンタークラスのソースです。

 1|using System;
 2|
 3|namespace Goods0001
 4|{
 5|    public class NumberCounter
 6|    {
 7|        private long _maxCount = 0L;
 8|        private long _count    = 0L;
 9|        private long _digit    = 0L;
10|        private bool _rotation = true;
11|
12|        // コンストラクタ
13|        public NumberCounter(long digit)
14|        {
15|            if((digit < 1) || (10 < digit)) {
16|                throw new Exception("桁値は 1 から 10 の範囲で設定してください");
17|            }
18|            this._digit = digit;
19|            this._maxCount = (long)(System.Math.Pow(10, digit)) - 1;
20|        }
21|
22|        // カウンタ値リセット
23|        public void Reset()
24|        {
25|            this._count = 0L;
26|        }
27|
28|        // カウンタ値カウントアップ
29|        public void CountUp()
30|        {
31|            if(this._count < this._maxCount) {
32|                this._count++;
33|            } else if(this._count == this._maxCount) {
34|                if(this._rotation){ this.Reset(); }
35|            }
36|        }
37|
38|        // 現在のカウンタ値
39|        public long Count
40|        {
41|            get{ return this._count; }
42|        }
43|
44|        // カウンタの桁数
45|        public long Digit
46|        {
47|            get{ return this._digit; }
48|        }
49|
50|        // カウンタの最大値
51|        public long MaxCount
52|        {
53|            get{ return this._maxCount; }
54|        }
55|
56|        // 最大値を超えた場合0に戻るか?
57|        public bool Rotation
58|        {
59|            get{ return this._rotation;  }
60|            set{ this._rotation = value; }
61|        }
62|
63|    }
64|}

クラス名を、NumberCounter としています。このクラスの概要のクラス図と、ソースに対応するクラス図は以下です。

概要クラス図

詳細(ソースに対応している)クラス図

概要クラス図 詳細(ソースに対応している)クラス図

カウンターの生成時に「何桁のカウンターか」を指定します。 カウンター値が最大値を越えた後のカウントアップで0に戻るかの設定がRotationプロパティで行えます。 コンストラクタをもう1つ作り、これも初期指定できるようにしてもいいかもしれません。 あとは Countup() でカウンター値を1つあげる。Reset() でカウンター値を0にリセットします。 読み取り専用のプロパティで、現在のカウンター値、使用している(生成した)カウンターの桁数と最大値 (桁数が3の場合は、999)を取得できます。
というクラスなっています。実際このクラスがどこかで役に立つことがあるかというと、まずないですが、 先の仕様を満たすクラスを作るとこうなる。というのを、カプセル化の観点から見てもらえればと思います。 プログラム自体は難しいことは何一つしていません。

▲このページのTOPへ

LUKIAくんとのやりとり

今回、第一回目の作成を終了(一応)し、いおが作成したソース、その他指摘内容などをLUKIAくんに渡した上での やりとりを以下に抜き出しました。

LU:LUKIA
い:いお

LU:VB.NETで作成を完了しました。
  感想なんですけど、クラスの効果というものがさっぱり・・・です。
  その前に、クラスを意識して作ってみましたが、クラスになってないかも!?
い:確かに「カウンター」っていうモノがないね。フォームにカウンターの機能が一緒に混ざってるでしょ。
LU:そうですね。。
い:カウンターを作るんだから、「カウンタークラス」があるはずなんだけど、それがないよね。
  変わりに Main っていうクラスが1つ外出しされてるけどこれは?なぜこれだけ別クラスになっている?
LU:とにかく、クラスっていうくらいだから、基本部分と部品となる部分でわかれてるんだろうなぁってのは納得してました。
  とりあえず、Mainクラスという名でクラスの器を作ってたんですけど、結局何をクラスにするんだ??って止まってしまって、
  そのままになっちゃいました。
い:なるほど。。ってことは、どういうクラスを作るかを決定する前にそのままカウンターとして動くものを
  作ったってことになるね。
LU:そうなります。。
い:とりあえず作った「フォーム」で仕様を満たす「アプリ」になってはいるけど、このカウンターを
  別のところ(別のフォームなど)で使えるかっていうとできない。カウンターの「クラス」がないから。
  Stringクラスや、TextBoxクラスとかは、クラスとして既に存在していて、使いたいところで自由に生成して使えるよね。
  ここでの趣旨もそういった「機能」などを使いまわせる部品(クラス)にすることだからねぇ。。
LU:それは理解しているつもりなんですけど。。なかなか。。
い:ということで、今回の第1回はここまでにしましょう。
  第2回は、クラスの定義を考えるという風にしていってみよう。
LU:それがいいです。クラスを使うってのは、わかってるけど、クラスの作り方を知らないから、作れないんですよ。きっと。。。
い:了解。
  何かを作る際に、どういうクラスを作ればいいか、でやってみよう。
い:じゃあ、いおの作ったカウンターのクラスを見て、どういう感想をもった?
  実際にカチカチやるカウンターを思い浮かべながら。
LU:そうですねぇ、シロート感覚の言葉で言ってみると、細かな部品の固まりがクラスになっている。
  イベントを起こすと必ず、クラスが呼ばれて、クラスの細かな部品で処理が行われてて、
  最終的にイベントのソースで、結果だけを受け取ってて、非常にすっきりしています。
い:・・なんか固い感想だね。。
  とにかく、実際にあるカウンター自体を想像して、カウンターのクラスと何回も見比べてほしい。
い:ここまでいろんな説明を聞いたりしてきた上で、自分でつくった「カウンター」を見てどの辺が問題だと思う?
LU:クラスがないってのが1つ。これは、クラス内で部品を作るっていうことをしていない。
  まだ、部品とメイン処理の区別がついていないのかも!?
  クラスを使用するってのは、わかってるんですけどねぇ、部品はどれとどれが必要だ!!を考えないといけないですね --;)
い:そうだね。
  クラスを使うのはできても作ることができない。
  こうやって課題とかで作ると(実際のシステム開発でも多いケースだけど)、自分でクラスを作って、そしてそれを使う。
  「作る側」と「使う側」共に1人の人だから、両方とも一緒に考えてしまって使いまわせないクラスができるという事がほんとに多いからね。
  そこは常に意識しておいたほうがいいよ。
  作った(部品となる)クラスは、見ず知らずの多くの人に使われるんだということを念頭において考えよう。
LU:わかりました。

▲このページのTOPへ

まとめ

今回第一回ということで、簡単なカウンターの機能を持つクラスを、仕様といくつかの注意点を伝えた上で 自由に作ってもらいました。 しかし求めていた「数計カウンターのクラスがない」という結果となりました。 もう1つ前の段階、クラスの抽出や、どのようなクラスを作るか、といったところから第2回はスタートすることにしました。

今回はカプセル化をターゲットに絞りました。 カプセル化を確実に行い機能をクラスの中に収めることで、 クラスは仕様を満たしたモノ(部品)として使える形になります。 そして、使う人からは使いやすく、作る人としても安全な形で作れるという双方へのメリットが出てきます。

ソフトウェアは実際の物としては存在しませんが、クラスはその機能をもつ箱でその箱が目の前にある。 と思って(イメージして)考えると考えやすくまります。 今回の数計カウンターも、実際のあのカチカチやるカウンターを思い浮かべて、そのカウンターへの操作や カウンターから得られる情報などといったものを抜き出し、クラスという別のモノに形作ります。 そのモノの状態を知ったり、変更したりするプロパティ、操作を実行するメソッド、 ここでは仕様に含まれていませんが、クラスからの通知としてイベントがあり、これらをうまく公開する (public・protectedのスコープにおく)ことで使いやすいクラスを提供できます。 クラスを使う人が、わかりやすく使いやすい形で機能を提供するということが非常に大切です。 (それには直感的にわかりやすい名称をつける事も必要な要素となります)

オブジェクト指向言語を使用している人でクラスを使ったことがないという人はいません。みんなクラスを使ってクラスを作っているはずです。 しかし、人に使われるクラスを作るとなるととたんに手が止まってしまう。そういう例を今まで多々見てきています。
「あ、クラスってこういうことか!?」と思える、まずそういった視点で考えられるようになるまでが まず1つの壁(思想・発想の転換の第一歩の壁)のようです。Fight!

第2回目は、実際にLUKIAくんにコーディングまでしてもらうかどうかはまだ決めていませんが、 まずは、クラスのインターフェース、publicな操作や属性に何が必要か、などが見出せるようになることを目指して進めていこうと思います。

▲このページのTOPへ

LUKIAくんの感想

模範解答をみたら、なーんだと思いました。しかし、解答を見たら納得して、自分で考えると分からない。 クラスのイメージが出来ていないんでしょうか・・・

 

webmaster@e-ioo.net