第1章 オブジェクト指向とは

オブジェクト指向とは、現実世界にあるモノをコンピュータ上にモデル化して、システムを構築する手法です。モデル化の対象は、社員や会社などの物理的なモノだけではありません。予約や注文といった概念もモノとしてモデル化します。現実世界を素直にモデル化することによって、複雑なシステムが理解しやすくなります。また、オブジェクト指向の基本概念に基づいてモデル化すれば、再利用性、拡張性、保守性の高いシステムが構築できます。

本書のテーマであるUML(Unified Modeling Language)は、オブジェクト指向に基づいて作成されたモデルを図示するための表記法です。つまり、UMLはオブジェクト指向開発のためのツールです。したがって、UMLを使用するには、まずオブジェクト指向の基本概念を理解しておかなければなりません。

ここでは、オブジェクト指向の基本的な考え方と、その実装について解説します。

1.1 | 手続き指向からオブジェクト指向へ

従来のシステム開発は、「手続き」を中心に考えていました。目的の仕事をいくつかの手続き(処理)に分割し、それらを組み合わせることによってシステムを構築するという考え方です。このような開発手法を、手続き指向と呼びます。

一方、オブジェクト指向とは、問題領域に登場するモノ(物理的なモノや概念的なモノ)をコンピュータ上にモデル化してシステムを構築する開発手法です。問題領域とは、システム化の対象となる現実世界の分野や領域のことです。問題領域でモデル化されたモノをオブジェクトと呼びます。オブジェクト指向で構築したシステムでは、いくつかのオブジェクトが相互作用することによって、目的の仕事を行います。

1つのシステムをいくつかの部分に分割して実装するという意味では、手続き指向もオブジェクト指向も基本的な考え方は同じです。複雑な問題を、いくつかの単純な問題に分割して解決するのは、工学分野で一般的に使われる手法です。手続き指向とオブジェクト指向の違いは、分割の単位が、手続き指向では「手続き」であり、オブジェクト指向では「オブジェクト」であるという点です。

それでは、なぜ今、開発手法がオブジェクト指向へシフトしつつあるのでしょうか? それは、現代のシステムの要求に応えるためと言えます。今日のシステムは、ハードウェア性能の急激な向上とあいまって、非常に複雑かつ大規模化しています。また、時代の激しい変化に応えるため、システムへの要求は短期間に変化し、開発期間の短縮が求められます。この現状に対処するためには、次のようなシステムを構築しなければなりません。

  • 複雑で大規模でも理解しやすいこと
  • 要求の変化にすばやく対応できる拡張性と保守性を備えること
  • 再利用による生産性の向上が図れること

オブジェクト指向は、現実世界を素直にモデル化するという発想に基づいています。現実に即してシステムを構築すればよいので、複雑で大規模なシステムでも理解しやすく表せます。また、現実世界に存在する普遍的な概念をモデル化すれば、システムへの要求が変化しても、設計の枠組みにそれほど影響は出ないはずです。さらに、次章以降で説明するカプセル化汎化ポリモーフィズムといったオブジェクト指向の基本概念に基づいてモデル化することによって、拡張性、保守性、再利用性の高いシステムを構築できます。オブジェクト指向は、今日のシステム開発が抱える課題を解決するために、たいへん有効な開発手法であると言えるでしょう。

1.2 | モデル化とは

オブジェクト指向では、現実世界にあるモノを、データ振る舞いという2つの構成要素でモデル化して、コンピュータ上に表現します。つまり、オブジェクトは、データと振る舞いという2つの構成要素を持ちます。データとは、モデル化の対象となるモノの性質や状態などを表す情報です。また、振る舞いとは、そのモノが提供するサービス(機能)や、データを使用する手段を表します。

たとえば、銀行口座を考えて見ましょう。山田太郎さんの銀行口座は、現実世界にあるモノです。これをモデル化して、「山田太郎さんの銀行口座」オブジェクトを作成してみましょう。それには、まず、このオブジェクトのデータと振る舞いを考えます。

銀行の預金通帳には必ず「口座番号」、「名義人」、「預金残高」が書かれています。これらがないと銀行口座は成り立ちません。そこで、山田太郎さんの「口座番号=5566778」、「名義人=山田太郎」、「預金残高=5000」を「山田太郎さんの銀行口座」オブジェクトのデータとします。

一方、銀行口座を使うのは「引き出し」、「預け入れ」、「残高照会」などのときです。これら機能を持っていれば、一応、銀行口座としての役割を果たします。そこで、「引き出し」、「預け入れ」、「残高照会」を「山田太郎さんの銀行口座」オブジェクトの振る舞いにします。これは、「山田太郎さんの銀行口座」オブジェクトが、「引き出し」、「預け入れ」、「残高照会」という要求に応えてくれること意味します。このようなオブジェクトに対する要求をメッセージと呼びます。オブジェクトはお互いにメッセージを送り合い、オブジェクト同士が自分に送られてきたメッセージに応答し合いながら、相互作用によって仕事を進めていきます。

実際の銀行口座には、他にもさまざまなデータや振る舞いが存在するでしょう。しかし、モデル化の際には、現実世界にあるモノを忠実にコンピュータ上に再現する必要はありません。問題領域において着目したい側面だけを取り出してモデル化すればよいのです。

Dd297675.fig1-1(ja-jp,MSDN.10).gif

1-1 モデル化

1.3 | クラスとオブジェクト

こうして、図1-1のように「山田太郎さんの銀行口座」オブジェクトが定義できました。では、「鈴木花子さんの銀行口座」オブジェクトはどう定義すればよいでしょうか? 今度は、鈴木花子さんの「口座番号=2233445」、「名義人=鈴木花子」、「預金残高=20000」をデータにして、「引き出し」、「預け入れ」、「残高照会」を振る舞いにすればよさそうです。山田太郎さんの銀行口座と鈴木花子さんの銀行口座は、まったく別のモノなので、それぞれ別々のオブジェクトになります。しかし、どちらも銀行口座ですから、「口座番号」、「名義人」、「残高」という共通のデータ項目を持っています。また、「引き出し」、「預け入れ」、「残高照会」という振る舞いも共通です。オブジェクトごとに異なるのは、データ項目の値だけです。そこで、個々のオブジェクトに共通なデータ項目と振る舞いを定義したテンプレートを用意しておけば、山田太郎さんや鈴木花子さんだけでなく、いろいろな人の銀行口座オブジェクトを簡単に作成できるでしょう。このように、いくつかのオブジェクトに共通なデータ項目と振る舞いを抽出してテンプレート化したものをクラスと呼びます。言い換えると、個々の人の具体的な銀行口座を表すのが「銀行口座オブジェクト」で、銀行口座という概念を表すのが「銀行口座クラス」ということになります。そして、オブジェクトのように具体例を表すものをインスタンスと呼びます。

図1-2は、銀行口座クラスと個々の銀行口座オブジェクトの関係を図示したものです。左側の四角形が銀行口座クラスです。銀行口座クラスを表す四角形の上段がクラス名で、中段には銀行口座が持つべきデータ項目(口座番号、名義人、預金残高)を定義しています。これらを銀行口座クラスの属性と呼びます。また、下段は、銀行口座が持つべき振る舞い(引き出し、預け入れ、残高照会)です。これらを銀行口座クラスの操作と呼びます。

銀行口座クラスの各属性に具体的な値を代入することによって、山田太郎さんや鈴木花子さんの銀行口座オブジェクトが生成できます。右側の四角形がオブジェクトです。各属性に代入した具体的な値は属性値と呼びます。個々の銀行口座オブジェクトの上段はオブジェクト名です。アンダーラインが付いているのは、クラス名でなくオブジェクト名であることを示しています。

Dd297675.fig1-2(ja-jp,MSDN.10).gif

1-2 銀行口座クラスと銀行口座オブジェクト

1.4 | オブジェクトの実装

システムの開発では、現実世界のモノをオブジェクトにモデル化したら、次は、そのオブジェクトをコンピュータ上に実装します。実装にはオブジェクト指向言語を使います。オブジェクト指向言語とは、オブジェクト指向の基本概念に準拠したプログラミング言語の総称です。本書ではMicrosoft Visual Basic .NET(以下Visual Basic .NET)を使用します。Visual Basicの新バージョンVisual Basic .NETは、これまでのVisual Basic 6.0以前の言語構文を踏襲しつつ、本格的なオブジェクト指向言語として大きく生まれ変わりました。

それでは、Visual Basic .NETを使って、先ほどの銀行口座を実装してみましょう。次のリストは、図1-2の「山田太郎さんの銀行口座」オブジェクトを使用する簡単なサンプルプログラムです。Visual Basic .NETでは、新たにコンソールアプリケーションが作れるようになりましたので、ここではコンソールアプリケーションとして実装しています。オブジェクトを生成して、その後、3つの操作(引き出し、預け入れ、残高照会)を確認します。このプログラムは次のように、大きく2つのファイルに分かれています。

ファイル名: Module1.vb


Imports System.Console
'山田太郎さんの銀行口座の動作を確認する
Module Module1
    Sub Main()
        Dim theBankAccount As BankAccount
        '山田太郎さんの銀行口座オブジェクトを作成する
        theBankAccount = New BankAccount("5566778", "山田太郎")
        'お金を預け入れる
        theBankAccount.Deposit(10000)
        '現在の預金残高を表示する
        WriteLine("現在の預金残高:" & theBankAccount.GetBalance())
        'お金を引き出す
        theBankAccount.Withdraw(5000)
        '現在の預金残高を表示する
        WriteLine("現在の預金残高:" & theBankAccount.GetBalance())
        WriteLine("よろしいですか?")
        ReadLine()
    End Sub
End Module

ファイル名: BankAccount.vb


'銀行口座クラス
Public Class BankAccount
    Private accountID As String     '口座番号
    Private owner As String         '名義人
    Private balance As Integer      '預金残高
    'コンストラクタ
    Public Sub New(ByVal id As String, ByVal name As String)
        accountID = id
        owner = name
        balance = 0
    End Sub
    '引き出し
    Public Sub Withdraw(ByVal amount As Integer)
        balance -= amount
    End Sub
    '預け入れ
    Public Sub Deposit(ByVal amount As Integer)
        balance += amount
    End Sub
    '残高照会
    Public Function GetBalance() As Integer
        Return balance
    End Function
End Class

リスト 1-1 銀行口座クラスと山田太郎さんの銀行口座オブジェクト

オブジェクトを生成するためには、まずそのテンプレートとなるクラスを定義しなければなりません。ここでは「山田太郎さんの銀行口座」オブジェクトを生成するために、「銀行口座」クラスを定義します。通常は、1クラスを1ファイルとしてコーディングします。リストの2番目のファイルBankAccount.vb内が、銀行口座クラスの定義です。クラス名はBankAccountとしました。Public Class BankAccount~End Classまでが、銀行口座クラスを実装したコードです。

最初の3行で、3つの属性「口座番号(accountID)」、「名義人(owner)」、「預金残高(balance)」に対応する変数とその型を宣言しています。このように属性などを表す変数を定義したコード部分を、クラスのフィールドと呼びます。

次に、Newという名前のプロシージャが実装されています。これは、コンストラクタといい、そのクラスのオブジェクトを作成するときに使用する特別なプロシージャです。通常はここで属性値の初期化などを行います。BankAccountクラスのNewプロシージャでは、accountIDとownerを、引数(idとname)として渡されてきた値でそれぞれ初期化し、balanceは0で初期化しています。

その後には、「引き出し(Withdraw)」、「預け入れ(Deposit)」、「残高照会(Get Balance)」の各操作に対応した3つのプロシージャが実装されています。Withdrawでは、引数amountとして渡された金額をbalanceから差し引きます。Depositでは、引数amountとして渡された金額をbalanceに加えます。GetBalanceではbalanceをそのまま返します。このようにクラスの操作を実装したものをクラスのメソッドと呼びます。ここでは、New、Withdraw、Deposit、GetBalanceの4つがメソッドです。

もう1つのファイルModule1.vbを見てみましょう。メインプログラムは、Module1モジュール内のMainプロシージャです。ここでは、先ほど定義した銀行口座クラスBankAccountを使って実際に「山田太郎さんの銀行口座」オブジェクトを生成し、その動作を確認しています。最初の行で、山田太郎さんの銀行口座オブジェクトを格納するための変数theBankAccountを宣言します。そして、次のtheBankAccount = New BankAccount("5566778", "山田太郎")ステートメントによって、山田太郎さんの銀行口座オブジェクトを生成し、theBankAccount に格納します。

次のtheBankAccount.Deposit(10000)ステートメントでは、山田さんの銀行口座オブジェクトに対して、Depositメソッド(預け入れ)を実行します。その結果、山田さんの口座に10000円が預け入れられます。次のWriteLineステートメントでは、現在の預金残高を表示して預け入れの結果を確認します。預金残高は、theBankAccount.GetBalanceによって取得します。次のtheBankAccount.Withdraw(5000)ステートメントでは、山田さんの銀行口座オブジェクトに対して、Withdrawメソッド(引き出し)を実行します。その結果、山田さんの口座から5000円が引き出されます。次のWriteLineステートメントでは、現在の預金残高を表示して引き出しの結果を確認します。

このように、オブジェクト指向における実装では、(1)まずクラスを定義して、(2)そのクラスのオブジェクトを生成し、(3)そのオブジェクトに対してメソッドを実行する、という3段階の手順を踏みます。リスト1-1の(1)~(3)で、その対応を確認してください。

VB .NET

クラスのメソッドは、SubプロシージャまたはFunctionプロシージャによって実装します。Subは戻り値のないメソッド、Functionは戻り値のあるメソッドです。

1.5 | 事例:見積作成プログラム1

ここまでで、オブジェクト指向の基本的な考え方と、実装の仕方の概要がお分かりいただけたと思います。ここからは、次に示す事例1を使って、手続き指向とオブジェクト指向の違いを具体的に見ていきましょう。

[事例1]カスタマイズパソコンの見積作成プログラム1

パソコン販売会社で使用するごく簡単な見積作成プログラムを作ります。このプログラムは、ユーザーが画面からウィザード形式でCPUの種類を選択すると、その結果を計算して見積を表示します。見積は本体価格とCPUの価格だけで,消費税等の計算は含めません。

パソコンの本体価格は50000円。CPUは次の3種類から選択できます。

  • (1) Pentium4 1.8GHz19000円

  • (2) Celeron 1.7GHz9000円

  • (3) Celeron 1.3GHz7500円

1.5.1|手続き指向の見積作成プログラム

まず、このプログラムを従来型の「手続き指向」で組み立ててみます。手続き指向では、問題を手続き、つまり処理単位に分割します。この事例を処理のまとまりに分割すると、次のようになるでしょう。

     (1) CPUオプションのリストを作成する。

        

          ↓

     (2) ユーザーにオプションリストを提示し、オプションを選択させる。

          ↓

     (3) 見積結果を表示する。

基本的には、この3つの処理をそれぞれプロシージャとして実装すればよいことになります。それが次のリストです。ここでは簡単にコンソールアプリケーションとして作成しました。1~3の3つの処理は、それぞれ(1)がInitEstimate、(2)がDoSelection、(3)がShowResultという名前のプロシージャとして実装しています。(2)+(3)を1つにまとめて、見積作成の本体部分を表すEstimateというプロシージャも作りました。

このプログラムは2つのファイルに分かれています。最初のファイルModule1.vbのModule1モジュール内で、全体の流れを制御するメインプログラム(Mainプロシージャ)を記述しています。メインプログラムを見ると、InitEstimateプロシージャで見積作成のための初期化を行い(1)、Estimateプロシージャで見積を作成する(2+3)という処理の流れがわかります。ユーザーが希望すれば何度でも見積をやり直すことができるように、EstimateプロシージャはWhileループの中に置きました。

一方、2番目のファイルWizard.vbのWizardモジュール内では、1、2、3の個々の処理を実装しています。リスト内の1、2、3が該当箇所です。InitEstimateプロシージャ(1)ではユーザーに提示するCPUオプションのリストを一次元配列(ArrayList)にセットします。また、Estimateプロシージャ(2+3)の中では、DoSelectionプロシージャ(2)でユーザーにオプションリストを提示してオプションを選択させ、ShowResultプロシージャ(3)で見積結果を表示します。

このように、手続き指向では、目的の仕事をいくつかの手続き(処理)に分割して実装し、それらを組み合わせて、一連の流れとして、全体の仕事を実行します。

ファイル名: Module1.vb


Imports System.Console   'WriteLine、Write、ReadLineのためのインポート
'見積作成のメインプログラム(手続き指向的な実装)
Module Module1
    Sub Main()
        '初期化
        InitEstimate()
        Do While (True)
            '見積作成を実行する
            Estimate()
            Dim repeat As String
            Write(">>> 見積をやり直しますか?(y/n) ===>")
            repeat = ReadLine()
            If (Not repeat.Equals("y")) Then
                Exit Do
            End If
            WriteLine("")
        Loop
    End Sub
End Module

ファイル名: Wizard.vb


Imports System.Console
Imports System.Collections'ArrayList のためのインポート
'オプション候補を表すデータ型
Public Structure OptionItem
    Public name As String
    Public cost As Decimal
End Structure
Module Wizard
    Dim baseCost As Decimal = 50000 'パソコンの本体価格
    Dim optionID As Integer = 0     '選択されたオプションのID
    Dim options As ArrayList        'オプションリスト(サイズが動的に変化する一次元配列)
    '初期化
    Sub InitEstimate()
        'オプションリストのオブジェクトを作成する
        options = New ArrayList()
        'オプションリストにオプション候補を登録する
        'id=0 はエラー用に「不明」にしておく
        addOption("不明", 0)
        addOption("Pentium 4 1.8GHz", 19000)
        addOption("Celeron 1.7GHz", 9000)
        addOption("Celeron 1.3GHz", 7500)
    End Sub
    'オプションリストにオプション候補を追加する
    Public Sub addOption(ByVal name As String, ByVal cost As Decimal)
        Dim item As OptionItem = New OptionItem()
        item.name = name
        item.cost = cost
        options.Add(item)
    End Sub
'見積作成を実行する
    Sub Estimate()
        WriteLine("*** 見積を開始します ***")
        WriteLine("")
        'オプションを選択する
        DoSelection()
        '見積結果を表示する
        ShowResult()
    End Sub
    'オプションを選択する
    Sub DoSelection()
        Dim i As Integer
        WriteLine(">>> CPU を選択してください。")
        WriteLine("")
        For i = 1 To options.Count - 1
            Dim item As OptionItem = options.Item(i)
            WriteLine("[" & i & "] " & item.name & ":" & item.cost & "円")
        Next
        WriteLine("")
        'ユーザーからの入力を取得してオプションを設定する
        Dim id As String
        Write("選択番号 ==>")
        id = ReadLine()
        optionID = CInt(id)
        If optionID < 1 Or optionID >= options.Count Then
            optionID = 0
        End If
        WriteLine("")
    End Sub
    '見積結果を表示する
    Sub ShowResult()
        WriteLine(">>> 見積結果は以下の通りです。")
        WriteLine("")
        '見積アイテムリストを取得して表示する
        Dim item As OptionItem = options.Item(optionID)
        WriteLine("[1] 本体価格:" & baseCost & "円")
        WriteLine("[2] CPU(" & item.name & "):" & item.cost & "円")
        '見積合計金額を表示する
        Dim total As Decimal = baseCost
        total += item.cost
        WriteLine(" ------------------------------------")
        WriteLine("      見積合計金額:" & total & "円")
        WriteLine("")
    End Sub
End Module

リスト 1-2 手続き指向の見積作成プログラム

1.5.2|オブジェクト指向の見積作成プログラム

■■ オブジェクトの決定

同じ事例1を、今度は「オブジェクト指向」で考えてみましょう。オブジェクト指向では、問題を手続き単位ではなくオブジェクト単位に分割します。では、この事例1の中でオブジェクトとはいったい何でしょうか。

オブジェクトとは問題領域に登場するモノをモデル化したものであることは、すでに説明しました。しかし、実際には問題領域に実にさまざまなモノが登場します。そのため、どの程度細かいモノまでオブジェクトにするか、どのような切り口でオブジェクトをとらえるかは一概に判断できません。ただし、オブジェクトは、データと振る舞いの2つの構成要素で表現するものです。したがって、ある程度の情報のまとまり(データ)を持ち、何らかの機能(振る舞い)を持つモノをオブジェクトに選びます。選び方は一通りとは限りません。また、単純な値や性質は、通常はオブジェクトにしません。

この事例では、見積作成ウィザードを実行すると、カスタマイズパソコンの見積りが作成されます。その動作を素直にモデル化して、ここでは、「見積作成ウィザード」と「カスタマイズパソコン」の2つをオブジェクトとします。この2つのオブジェクトの相互作用によって、見積作成という仕事を実現します。

■■ クラスの関係

2つのオブジェクトには、それぞれのオブジェクトのテンプレートとなるクラスが必要です。そこで、「見積作成ウィザードクラス」と「カスタマイズパソコンクラス」を定義します。

ここでは、見積作成ウィザードオブジェクトが、カスタマイズパソコンオブジェクトとメッセージのやり取りをしながら見積作成を行うようにプログラムを実装します。見積作成ウィザードオブジェクトがカスタマイズパソコンオブジェクトにメッセージを送るには、見積作成ウィザードからカスタマイズパソコンが参照できなければなりません。それを実現するには、見積作成ウィザードクラスに、カスタマイズパソコンを参照する手段としてのフィールドを定義します。このように、2つのオブジェクトがメッセージのやり取りをするには、それに対応するクラスの間にも構造的な関係が成り立ちます。

UMLは、オブジェクト指向に基づいて作成したモデルを、図によってわかりやすく表現するための表記法です。クラス間の関係もUMLを使って明確に示すことができます。UMLについては第2部で詳しく説明しますが、第1部でも、プログラムの構造や動作を説明する手段としてさっそくUMLを活用していきます。

■■ クラス図

UMLには、さまざまな視点からモデルを表現できるように9種類の図が用意されています。ここでは、まずその中のクラス図を使って、クラスの定義やクラス間の関係を表現します。

先ほどの「見積作成ウィザードクラス」と「カスタマイズパソコンクラス」の2つのクラスの関係をクラス図で表すと、図1-3のようになります。これは一番簡単なクラス図の書き方です。四角形はクラスを表します。四角形の中に表示されているのがクラス名です。見積作成ウィザードクラスとカスタマイズパソコンクラスは、実線の矢印で接続されています。これは関連といい、見積作成ウィザードがカスタマイズパソコンにメッセージを送りながら見積作成の仕事を行うための関係を表します。また、矢印の先には「見積対象のパソコン」という名前が書かれています。これは、ロール名と呼ばれ、見積作成ウィザードから見たカスタマイズパソコンの役割や立場を表します。関連を表す実線の両端に表示されている「1」という数字は、多重度と呼ばれ、一方のクラスのオブジェクトに対応する、他方のクラスのオブジェクトの数を表します。見積作成ウィザードのオブジェクトから見ると、それに対応するカスタマイズパソコンのオブジェクトは1個存在し、逆にカスタマイズパソコンのオブジェクトからみると、それに対応する見積作成ウィザードのオブジェクトも1個存在するので,この場合は両方とも「1」となります。

▲図1-3 見積作成ウィザードとカスタマイズパソコンの関係を表すクラス図

1-3 見積作成ウィザードとカスタマイズパソコンの関係を表すクラス図

クラスの書き方は、これ以外にもいくつかありますが、それはまた別途説明します。表記法の定義については「付録A UML 1.4 クイックリファレンス」にまとめてありますので、参照してください。

■■ オブジェクト指向の実装

事例1をオブジェクト指向で実装したものが、次のプログラムです(図1-3のクラス図を頭に置いて、プログラムを見てみましょう)。

ファイル名: Module1.vb

Imports System.Console
'見積作成のメインプログラム
Module Module1
    Sub Main()
        '見積作成ウィザードのオブジェクトを作成する
        Dim wizard As EstimateWizard
        wizard = New EstimateWizard()
        Do While (True)
            '見積作成を実行する
            wizard.Estimate()
            Dim repeat As String
            Write(">>> 見積をやり直しますか?(y/n) ===>")
            repeat = ReadLine()
            If (Not repeat.Equals("y")) Then
                Exit Do
            End If
            WriteLine("")
        Loop
    End Sub
End Module

ファイル名: EstimateWizard.vb

Imports System.Console
Imports System.Collections
'見積作成ウィザードクラス
Public Class EstimateWizard
    Private thePC As PC     '見積対象のカスタマイズパソコン
    'コンストラクタ
    Public Sub New()
        'PCオブジェクトを作成する
        thePC = New PC()
    End Sub
    '見積を作成する
    Public Sub Estimate()
        WriteLine("*** 見積を開始します ***")
        WriteLine("")
        'ユーザーにオプションリストを提示し、オプションを選択させる
        DoSelection()
        '見積結果を表示する
        ShowResult()
    End Sub
    'ユーザーにオプションリストを提示し、オプションを選択させる
    Public Sub DoSelection()
        'オプションリストを取得してユーザーに表示する
        Dim list As ArrayList
        Dim i As Integer
        WriteLine(">>> CPU を選択してください。")
        WriteLine("")
        list = thePC.GetOptions()
        For i = 0 To list.Count - 1
            WriteLine("   " & list.Item(i))
        Next
        WriteLine("")
        'ユーザーからの入力を取得してオプションを設定する
        Dim id As String
        Write("選択番号 ==>")
        id = ReadLine()
        thePC.SetOptionID(CInt(id))
        WriteLine("")
    End Sub
    '見積結果を表示する
    Public Sub ShowResult()
        WriteLine(">>> 見積結果は以下の通りです。")
        WriteLine("")
        '見積アイテムリストを取得して表示する
        Dim list As ArrayList
        Dim i As Integer
        list = thePC.GetItems()
        For i = 0 To list.Count - 1
            WriteLine("   " & list.Item(i))
        Next
        '見積合計金額を表示する
        WriteLine(" ------------------------------------")
        WriteLine("      見積合計金額:" & thePC.GetTotal() & "円")
        WriteLine("")
    End Sub
End Class

ファイル名: PC.vb

Imports System.Collections
'オプション候補を表すデータ型
Public Structure OptionItem
    Public name As String
    Public cost As Decimal
End Structure
'カスタマイズパソコンクラス
Public Class PC
    Private baseCost As Decimal = 50000 'パソコンの本体価格
    Private optionID As Integer = 0     '選択されたオプションのID
    Private options As ArrayList        'オプションリスト
    'コンストラクタ
    Public Sub New()
        'オプションリストのオブジェクトを作成する
        options = New ArrayList()
        'オプションリストにオプション候補を登録する
        'id=0 はエラー用に「不明」にしておく
        AddOption("不明", 0)
        AddOption("Pentium 4 1.8GHz", 19000)
        AddOption("Celeron 1.7GHz", 9000)
        AddOption("Celeron 1.3GHz", 7500)
    End Sub
    'オプションリストにオプション候補を追加する
    Public Sub AddOption(ByVal name As String, ByVal cost As Decimal)
        Dim item As OptionItem = New OptionItem()
        item.name = name
        item.cost = cost
        options.Add(item)
    End Sub
    'CPUオプションの一覧を取得する
    Public Function GetOptions() As ArrayList
        Dim list As ArrayList = New ArrayList()
        Dim i As Integer
        For i = 1 To options.Count - 1
            Dim item As OptionItem = options.Item(i)
            list.Add("[" & i & "] " & item.name & ":" & item.cost & "円")
        Next
        Return list
    End Function
    'CPUオプションを設定する
    Public Sub SetOptionID(ByVal i As Integer)
        If i >= 1 And i < options.Count Then
            optionID = i
        Else    'エラーの場合はidを0にする
            optionID = 0
        End If
    End Sub
    '見積内容を取得する
    Public Function GetItems() As ArrayList
        Dim items As ArrayList = New ArrayList()
        Dim item As OptionItem = options.Item(optionID)
        items.Add("[1] 本体価格:" & baseCost & "円")
        items.Add("[2] CPU(" & item.name & "):" & item.cost & "円")
        Return items
    End Function
    '見積合計金額を取得する
    Public Function GetTotal() As Decimal
        Dim total As Decimal = baseCost
        Dim item As OptionItem = options.Item(optionID)
        total += item.cost
        Return total
    End Function
End Class

リスト 1-3 オブジェクト指向の見積作成プログラム

このプログラムは3つのファイルから構成されています。最初のファイルModule1.vbには、プログラム全体の流れを制御するメインプログラム(Mainプロシージャ)を記述しています。2番目のファイルEstimateWizard.vbには、見積作成ウィザード(EstimateWizard)クラスを実装しています。3番目のファイルPC.vbには、カスタマイズパソコン(PC)クラスを実装しています。

 

VB .NET

クラスを実装する際には、通常、1クラス1ファイルとしてコーディングします。拡張子を除くファイル名はクラス名と同じにします。拡張子は常に.vbです。
たとえば、PCクラスのファイル名は、PC.vbとなります。

2番目のファイルの見積作成ウィザード(EstimateWizard)クラスの実装から見てみましょう。このクラスでは、最初の行で、thePCというカスタマイズパソコンクラス(PC)の変数を宣言しています。この部分がフィールドです。EstimateWizardは、この変数に見積対象となるカスタマイズパソコンのオブジェクトを格納し、カスタマイズパソコンオブジェクトのメソッドを利用しながら見積作成を行います。その準備として、EstimateWizard のコンストラクタ(New)では、thePC = New PC( )ステートメントによって、PCオブジェクトを作成し、それを変数thePCに格納しています。このように、EstimateWizardからPCオブジェクトが参照できるようになっている構造が、図1-3の見積作成ウィザードクラスとカスタマイズパソコンクラスの関連に対応します。また、thePCという変数名は、図1-3のロール名「見積対象のパソコン」に対応します。

次に、EstimateWizardの操作を見てみましょう。EstimateWizardは、見積作成に関わる仕事を担当するクラスですから、見積の作成に必要な機能を持たせなければなりません。見積作成の機能とは、ユーザーにCPUのオプションリスト提示し、オプションを選択させて、見積結果を表示することです。そこで、EstimateWizardクラスには、次の4つの操作を定義しています。属性はありません。

1-1 EstimateWizard クラスの操作

  名前 解説

操作

New

コンストラクタ

Estimate

見積を作成する(DoSelectionとShowResultをまとめたもの)

DoSelection

ユーザーにオプションリスト提示し、オプションを選択させる

ShowResult

見積結果を表示する

3番目のファイルのカスタマイズパソコン(PC)クラスの実装を見てみましょう。カスタマイズパソコンに関わるデータには、パソコンの本体価格、CPUオプションのリスト、ユーザーが選択したオプションの3つがあります。そこで、これらに対応するデータ項目をPCクラスの属性にしています。PCクラスには、その内部に保持しているCPUオプションのリストを参照したり、ユーザーが選択したオプションを設定する手段が必要です。また、PCがカスタマイズされた結果(見積内容と見積合計金額)を提供する機能も必要です。以上より、PCクラスには、次の属性と操作を定義しています。AddOptionは、CPUのオプションリストにオプション候補を追加するために内部的に使用する操作です。

1-2 PC クラスの属性と操作

  名前 解説

属性

baseCost

パソコンの本体価格

optionID

ユーザーが選択したオプションのID

options

CPUのオプションリスト

操作

New

コンストラクタ

AddOption

オプションリストにオプション候補を追加する

GetOptions

CPUオプションの一覧を取得する

SetOptionID

CPUオプションを設定する

GetItems

見積内容を取得する

GetTotal

見積合計金額を取得する

これら2つのクラスの属性と操作を明記したクラス図が図1-4です。図1-3で、日本語で記述されていたクラス名とロール名は、コードに書かれている英語名に書き換えています。クラスを表す四角形は3段に分かれ、中段の区画には属性が、下段には操作が表示されています。また、属性の型や初期値、操作の引数や戻り値の型も明記されています。このように、詳細なクラス図を見ると、クラス間の関係と、それぞれのクラスの属性と操作が一目でわかります。

▲図1-4 見積作成プログラムの詳細なクラス図

1-4 見積作成プログラムの詳細なクラス図

■■ シーケンス図

図1-4のクラス図によって、見積作成プログラムの構造が明らかになりました。今度は、このプログラムの動作を見ていきましょう。それには、UMLの図の一種であるシーケンス図が役立ちます。シーケンス図とは、オブジェクト間のメッセージのやり取りを時間に沿って表すための図です。見積作成プログラムのシーケンス図を見る前に、リスト1-1の銀行口座の例を使って、シーケンス図の見かたを説明しておきます。

▲ 図 1-5 銀行口座プログラムのシーケンス図

1-5 銀行口座プログラムのシーケンス図

図1-5は、リスト1-1の銀行口座プログラムの動作を表すシーケンス図です。シーケンス図では、時間的な流れは上から下へ向かいます。

「Main」と「theBankAccount:BankAccount」という2つの四角形は、オブジェクトを表します。オブジェクトの名前は、通常オブジェクト名 : クラス名の形式で表現し、アンダーラインを付けます。ここでは、メインプログラムも「Main」という名前のオブジェクトとして表現しています(クラス名はなし)。このプログラムは、MainとtheBankAccountの2つのオブジェクトの相互作用によって動作することがわかります。オブジェクトを表す四角形の下に垂直に伸びている破線は、ライフラインと呼ばれ、そのオブジェクトが存続している期間を表します。Mainは最初から最後まで存続していることがわかります。

水平方向に描かれている実線の矢印は、オブジェクト間でやり取りされるメッセージを表します。メッセージは、オブジェクトのメソッド呼び出しに対応し、上から順番に実行されます。リスト1-1のメインプログラムでは、最初にNewメソッドによって、山田太郎さんの銀行口座オブジェクト(theBankAccount)を生成しています。図1-5では、そのようすが、MainからのNewメッセージの矢印の先にtheBankAccountオブジェクトを表す四角形を配置することで表現されています。リスト1-1で順番に実行される、theBankAccountオブジェクトに対するDeposit、GetBalance、Withdraw、GetBalanceメソッドの呼び出しは、図1-5では、上から順番にDeposit、GetBalance、Withdraw、GetBalnaceメッセージとして表現されています。

ライフラインの上に、部分的に帯のように描かれている矩形は、活性区間と呼ばれ、オブジェクトがひとまとまりの処理を実行している期間を表します。たとえば、Depositメッセージの矢印の先から始まる活性区間(theBankAccountオブジェクトのライフライン上にある)は、Depositメッセージの処理期間を表しています。

■■ 見積作成プログラムのシーケンス図

それでは、シーケンス図を使って、見積作成プログラムの動作を見てみましょう。リスト1-3の見積作成プログラムのシーケンス図は、図1-6のようになります。

まずメインプログラムMainでは、大きく分けて2つのことをやっています。1つは、wizard = New EstimateWizardステートメントで、EstimateWizardクラスのオブジェクトを生成することです。もう1つは、変数wizardに格納されているEstimateWizardオブジェクトに対してEstimateメソッドを実行し、見積を作成することです。ユーザーが希望すれば見積は何度でもやり直すことができるようにEstimateメソッドの実行をWhileループの中に記述しています。これは、手続き指向による実装のメインプログラムと同じです。

ここまでを図1-6のシーケンス図で確認してみましょう。このプログラムは、Main(メインプログラム)、wizard(EstimateWizardオブジェクト)、thePC(PCオブジェクト)の3つのオブジェクトの相互作用によって動作することがわかります。

Mainから出るメッセージを上から順番に見てみましょう。Mainは、Newメッセージによって、wizardオブジェクトを生成し、次にwizardにEstimateメッセージを送っています。Estimateメッセージの前に付加されている「*」は、Estimateメッセージが、Whileループの中で繰り返し送られることを表します。このように、wizardオブジェクトへのメッセージは、メソッド呼び出しに対応しています。

▲ 図 1-6 見積作成プログラムのシーケンス図

1-6 見積作成プログラムのシーケンス図

今度はリスト1-3のEstimateWizardクラスのEstimateメソッドを見てみましょう。ここでは、DoSelectionを実行して、ユーザーにオプションリストを提示してオプションを選択させ、ShowResultを実行して見積結果を表示します。この部分は、図1-6のシーケンス図では、wizardオブジェクトが自分自身に送っているDoSelectionメッセージとShowResultメッセージに対応します。自分自身へのメッセージになっているのは、DoSelectionメソッドとShowResultメソッドが、同じEstimateWizardクラスのメソッドだからです。

DoSelectionメソッドの中では、GetOptionsで、PCオブジェクトからオプションリストを取得してユーザーに提示し、ユーザーが選択した番号を、SetOptionIDでPCオブジェクトに設定します。これで、オプションの選択は完了です。ShowResultメソッドの中では、GetItemsでPCオブジェクトからカスタマイズ後の見積内容を取得して表示します。次に、GetTotalで、カスタマイズ後の見積合計金額を取得して表示します。この一連のやり取りは、wizardオブジェクトとthePCオブジェクトの間でやり取りされているメッセージに対応します。

メソッドが実行されている期間は、それに対応するメッセージ矢印の先から始まる活性区間で表現しますが、メソッドの呼び出しが入れ子になっていることも、活性区間からわかります。たとえば、Estimateメソッドの中で、DoSelectionメソッドとShowResultメソッドが呼び出されるようすは、Estimateメッセージの活性区間の範囲内から、DoSelectionメッセージとShowResultメッセージの矢印が出ていることで表現します。

このようにして、プログラムの動作をシーケンス図に表すと、メインプログラム、EstimateWizardオブジェクト、PCオブジェクトの3つがぞれぞれメッセージを送り合って、見積が作成されていくようすがよくわかります。

シーケンス図を初めて見る人にとっては、少々難しく感じるかもしれません。UMLの図については、第2部でまた詳しく説明しますので、ここでは、「シーケンス図は、目的の仕事を実現するために必要なオブジェクトと、それらの間のメッセージのやり取りを、時間に沿って表す図である」ということだけ頭に入れておいてください。

1.5.3|手続き指向とオブジェクト指向の比較

こうして、手続き指向の実装とオブジェクト指向の実装を比べてみると、オブジェクト指向の方が現実の世界の動きにずっと近いことが実感できると思います。

オブジェクト指向の実装では、見積作成のために必要なデータと処理を、EstimateWizardクラスとPCクラスが分担しています。しかも、カスタマイズパソコンに関連する部分はPCクラスに、見積作成に関連する部分はEstimateWizardクラスにというように、現実世界に即した分担になっています。こうすることで、プログラムの構造が直感的に理解しやすくなります。複雑または大規模なシステムを実現する際には、このわかりやすさが非常に重要になってくるのです。

一方、EstimateWizardクラスとPCクラスで、データと処理を分担したことによって、手続き指向の実装よりも、プロシージャの呼び出し回数が増えています。このため、オブジェクト指向の実装は、手続き指向よりもリソースを消費します。ただし、現在のハードウェアの性能を考えれば、この程度のオーバーヘッドはほとんど問題になりません。オブジェクト指向には、リソース消費のオーバーヘッドを負っても余りあるメリットがあります。それについては、次章以降で説明します。