ObjectSquare

オブジェクト指向設計帖
巻之二
株式会社 オージス総研
オブジェクト・テクノロジー・ソリューション部
林 俊樹
Hayashi_Toshiki@ogis-ri.co.jp

目次

一、はじめに
二、責務とは
二 ノ 一、振る舞いに対する責務
二 ノ 二、知識に対する責務
二 ノ 三、UMLで責務を記述する
二 ノ 四、ソースコードになると
三、責務が適切に割り当てられていないと・・・
三 ノ 一、実体験に基づく例
四、再び責務とは
五、The Single Responsibility Principle
六、まとめ
七、参考資料
八、Appendix
八 ノ 一、Java によるソースコードの例
八 ノ 二、VB.NET によるソースコードの例

一、はじめに

今日のソフトウェアで扱う問題は非常に複雑であり、近年では、さらに複雑化かつ多様化する方向にあります。しかし困ったことに、われわれ生身の人間は、一度に考える範囲が限られている為、全ての問題を一度に取り掛かることは、現実問題として不可能です。そのため設計では、実現手段を考える上で、複雑な問題を分割する作業も必要になります。オブジェクト指向設計では、問題を構成する責務を意識して、クラスとして分割します。そして、そのクラスのインスタンスであるオブジェクトが協調作業することによって、提示された問題を解決するのです。

ここで、クラスをどうやって導出する、あるいは既存のクラスに何を実行させるか、ということを考える際に「責務(Responsibility)」という視点が非常に重要になってきます。もちろん、責務だけを考えればで優れた設計ができるわけではありません。しかしながら、責務を意識することは、オブジェクト指向設計において重要な要素の一つです。そして、オブジェクト指向設計において、クラスに対して責務を適切に割り当てることは、非常に重要な設計スキルだと言えます。なぜなら、この責務の割り当てがソフトウェアの品質に大きく影響するからです。

では、責務とは具体的に何なのでしょうか?私自身は、非常によく使われる言葉でありながら、曖昧にされてきた印象を持っていました。特に入門書においては、当たり前のように(かつ、さりげなく)使用されたり、役割と混同されて使用されるケースもあり、オブジェクト指向を勉強したての頃は混乱した記憶があります。そのおかげでいろいろな文献やサイトを調べる機会に恵まれ、現在に至っています。

今回は、この「責務」について考えてみましょう。

二、責務とは

まず、責務 (Responsibility) とは何なのでしょうか?
Booch は、責務を「オブジェクトが責任を持って行わなければならない振る舞い。責任はオブジェクトがある特定の振る舞いを提供しなければならない義務のことである。」として定義しています[BOOCH]
ここでのポイントは、そのクラスの持つ責務は、あるオブジェクトとの協調作用のなかで、決定される点です。
実際の設計においても、クラス図や相互作用図を作成するなかで、どのクラスにどういった責務を持たせるかということを決定していきます。そして、責務を持ちするぎるクラス (たとえば相互作用に参加する機会が多いクラス) を発見した場合は、そのクラスが持つ責務を分割し、他のクラスに割り当てます。
では、実際の設計において、どのようなものが責務として認識されるのでしょうか?クラスの持つ責務は、基本的に次の2種類に分類されます[CRC]。

以下、それぞれについて見てみましょう。

二 ノ 一、振る舞いに対する責務

これは、実践UML[LARMAN]では、「実行責務」という言葉で分類されているものです。
振る舞いに対する責務の種類には以下のようなものがあります。

ほとんどのクラスは何かしらの振る舞いを行います。
王道に従って、銀行のアプリケーションを例にとって考えてみましょう。
例を単純にするために、ここで登場するクラスを以下の2つに絞ります。

そして、「預金を引き出す (Withdraw) 」、「預金を預ける (Deposit) 」という相互作用を考えてみましょう。
それぞれの一番単純なコラボレーション図を以下に示します。 (Figure 2-1)

Figure 2-1 : 単純なコラボレーション図
Figure 1

身も蓋も無いコラボレーション図ですが、この図から読み取れる情報は以下のとおりです。

この責務が、口座クラスの「振る舞いに対する責務」になります。

二 ノ 二、知識に対する責務

これは、実践UML[LARMAN]では、「情報把握責務」という言葉で分類されているものです。
知識に対する責務の種類には以下のようなものがあります。

先程の例に戻ると、口座 は残高を有しているはずですし、預金者 は、名前や生年月日などの基本情報を有し、またそこから現在の年齢を導出することができます。 このとき、それぞれのクラスは以下のような責務を持つことになります。

この責務が、クラスの「知識に対する責務」になります。

二 ノ 三、UMLで責務を記述する

UML では責務を記述する個所が厳密に規定されているわけではありません。記述する場合は、操作の定義の下の区画に記述するか、ノートとして記述するかの2つの方法があります。しかし、私が日常的に使用している CASE ツールでは操作の下の区画は使用できないので前者の記法は使用できないため、以下の図 (Figure 2-3-1) のようにメモとステレオタイプを使用して記述するようにしています。

Figure 2-3-1 : 責務を追加した例
Figure Y

二 ノ 四、ソースコードになると

UML では、コメントとして責務を記述することになりましたが、ソースコードを記述する際には、責務の表現はどうなるのでしょうか?先程のクラス図をソースコードまでブレークダウンした場合は以下のようになります。

以下のソースコードは、私のお気に入りの言語のひとつである C# で 口座 クラスと 預金者 クラスを記述したものです。今回は、C# を知らない読者の為に、Java と VB.NET のソースコードを Appendix に掲載しておきます。C# は開発言語として非常に良くできていると思いますので、これを機に一度 C# を触ってみる事をおすすめします。

List 2-4-1 : 口座クラス(Account.cs)
  1:using System;
  2:
  3:namespace BankApplication
  4:{
  5:  /// <summary>口座クラス</summary>
  6:  public class Account
  7:  {
  8:    #region Constructors & Destructors
  9:    public Account(Depositor depositor)
 10:    { 
 11:      this.Depositor = depositor;
 12:    }
 13:    #endregion
 14:
 15:    #region Public Instance Properties
 16:    public decimal Balance
 17:    {
 18:      get { return balance; }
 19:      set { balance = value; }
 20:    }
 21:
 22:    public Depositor Depositor
 23:    {
 24:      get { return depositor; }
 25:      set { depositor = value; }
 26:    }
 27:    #endregion
 28:
 29:    #region Public Instance Methods
 30:    public decimal Withdraw(decimal amount)
 31:    {
 32:      balance -= amount;
 33:      return amount;
 34:    }
 35:
 36:    public void Deposit(decimal amount)
 37:    {
 38:      balance += amount;
 39:    }
 40:    #endregion
 41:
 42:    #region Private Instance Fields
 43:    private decimal balance;
 44:    private Depositor depositor;
 45:    #endregion
 46:  }
 47:}

List 2-4-2 : 預金者クラス(Depositor.cs)
  1:using System;
  2:
  3:namespace BankApplication
  4:{
  5:  /// <summary>預金者</summary>
  6:  public class Depositor
  7:  {
  8:    #region Constructors & Destructors
  9:    public Depositor(string name, DateTime birthday)
 10:    { 
 11:      this.Name     = name;
 12:      this.BirthDay = birthday;
 13:    }
 14:    #endregion
 15:
 16:    #region Public Instance Properties
 17:    public string Name
 18:    {
 19:      get { return name; }
 20:      set { name = value; }
 21:    }
 22:
 23:    public DateTime BirthDay
 24:    {
 25:      get { return birthday; }
 26:      set { birthday = value; }
 27:    }
 28:
 29:    public int Age
 30:    {
 31:      get
 32:      { 
 33:        DateTime today = DateTime.Today;
 34:        int age = today.Year - birthday.Year
 35:                  - ( today.DayOfYear > birthday.DayOfYear )
 36:                    ? 1
 37:                    : 0;
 38:      }
 39:    }
 40:
 41:    public Account Account
 42:    {
 43:      get { return account; }
 44:      set { account = value; }
 45:    }
 46:    #endregion
 47:
 48:    #region Private Instance Fields
 49:    private string name;
 50:    private DateTime birthday;
 51:    private Account account;
 52:    #endregion
 53:  }
 54:}

ソースコードには明示的な責務の記述が、どこにも存在していないことに注目してください。
C# においては「振る舞いに対する責務」は Account.Withdraw() や Account.Deposit() などのメソッドに、「知識に対する責務」は Account.Balance や Depositor.Name などのプロパティにそれぞれ暗黙的に対応付けられています。Java の場合では、「振る舞いに対する責務」、「知識に対する責務」ともにAccount.withdraw() や Account.getBalance() などのメソッドとして表現されるでしょう。ここでの重要なポイントは、これらメソッドやプロパティは責務とまったく同一のものではありませんが、責務を満たすためにメソッドやプロパティを使って実装を行う点です。

三、責務が適切に割り当てられていないと・・・

責務がどういうものかというイメージが掴めたところで、今度はなぜクラスに責務を適切に割り当てなければならないかを示したいと思いますが、その際には、責務が適切に割り当てられてない場合にどのようなデメリットがあるかを例に取るとイメージしやすいので、ひとつの例を題材に考えてみたいと思います。

三 ノ 一、実体験に基づく例

一応断っておきますが、この例は、私がオージス総研に入社する前の話です。
よくある話ですが、あるアプリケーションに機能を追加するという話が持ち上がりましたが、そのアプリケーションの担当者はプロジェクトを、ずいぶん昔に離れてしまっているので、誰かがメンテナンスをしなければなりません。そこで、私にそのお鉢が回ってきたのですが、ご多分に漏れずドキュメントも何も無い状態で、プログラムの解析から始めなければなりませんでした。
仮に "BogusApp" と命名し、そのアプリケーションの特徴を以下に示します。

アプリケーションの規模も手頃で、仕様も明確だったので、当時オブジェクト指向を独学で勉強していた私にとっては、学んだ知識を適用するには格好の餌食でした。さしあたり、教科書どおりに UML で既存のクラス図を書くことから始めましたが、ごくごくシンプルなものになりました。その結果を以下に示します。(Figure 3-1)

Figure 3-1 : 解析したクラス図
Figure ?

もちろん、オブジェクト指向設計に基づいて設計したものではないので、責務によるクラス分割は行われていません。また、MFC で用意されている些末なクラスは省いていますが、このクラス図からドメインのために定義したクラスは唯のひとつも存在していないことが判ります。
機能追加するに当たって、私がこのアプリケーションを修正するのに苦労した点は以下のとおりです。

当時のクラス図やソースコードを持っていないため、残念ながら修正結果をお見せすることはできませんが、最終的には、このドメインを20程のクラスに分割し処理を局所化した上で、機能追加のための修正を行うことにしました。要するに全面的な作り直しをしたわけです。
この例をまとめると、修正前のアプリケーションでは、一つのクラス (CBogusDlg) がドメインで必要な全ての責務を持っていた、つまり、責務による適切なクラス分割を行っていなかったため、以下のような弊害あったと考えています。

言い換えると、クラスに適切な責務を割り当てるのは「理解容易性」、「変更容易性」、「頑健性」、「再利用性」などの品質要因をソフトウェアに付与するためです。ここで注意しなければならないのは、責務を割り当てれば自然にそうなる訳ではなく、そうなるようにクラスに適切な責務を割り当てなければならないという点です。そうしなければ、逆にクラス数が増大してしまい、本来の目的を果たすことができません。

四、再び責務とは

このように責務はソースコードに直接的には表現されることはありませんが、責務の追加や変更は確実にソースコードに大きな影響を与えます。
これは構造化設計手法でいうところの「凝集度 (Cohesion)」と同様に捉えることができるかもしれません。
つまり、責務を別の視点で見ると、「機能追加や変更を理由にコードを修正する影響範囲」と捉えることができるということです。これに関して、Robert . C Martin は責務を "a reason for change"と定義しています。要するに、あるクラスに対する変更の動機 (a motive for changing a class) がひとつあるとすれば、クラスはひとつの責務を持っていることになる、というのです。そして、クラスに高い凝集性を付与するために、原則を適用することを推奨しています。それが、以下で紹介する "SPR (The single Responsibility Principle)"です[MARTIN]。

五、The Single Responsibility Principle

SPR の定義を以下に示します。

The Single Responsibility Principle
THERE SHOULD NEVER BE MORE THAN ONE REASON FOR A CLASS TO CHANGE.
(クラスを変更する理由は一つ以上存在してはならない。)

この原則について、先程の銀行アプリケーションの設計を例にとって考えてみましょう。
さっきの例はシンプルすぎるので「年利を計算する」という責務を追加したクラス図とソースコードを以下に示します。(Figure 5-1)(List 5-1-1)

Figure 5-1 : 「年利を計算する」責務を追加した例
Figure Y

List 5-1-1 : 変更した 口座クラス(Account.cs)
  1:using System;
  2:
  3:namespace BankApplication
  4:{
  5:  /// <summary>口座クラス</summary>
  6:  public class Account
  7:  {
  8:    #region Constructors & Destructors
 〜:    ・・・ 省略 ・・・
 13:    #endregion
 14:
 15:    #region Public Instance Properties
 〜:    ・・・ 省略 ・・・
 27:    #endregion
 28:
 29:    #region Public Instance Methods
 〜:    ・・・ 省略 ・・・
 40:
 41:    public decimal ComputeAnnualInterest()
 42:    {
 43:      return Decimal.Truncate(
 44:              balance * annualRate
 45:              / 365
 46:              * DateTime.Today.DayOfYear
 47:              );
 48:    }
 49:    #endregion
 50:
 51:    #region Private Instance Fields
 〜:    ・・・ 省略 ・・・
 54:    #endregion
 55:
 56:    #region Private Static Fields
 57:    private const decimal annualRate = 0.0003M;
 58:    #endregion
 59:  }
 60:}

どうでしょう?先程のクラスにメソッドと属性をそれぞれ一つ追加しただけです。自画自賛するつもりは毛頭ありませんが、メソッド数もそれほど多くないし、そんなに悪くない設計のように思えます。
しかし、要求定義で以下の要求が挙がっていたとします。

このような視点で、先程の 口座 クラスを見るとどうでしょうか?
そうすれば、口座 クラスには以下のような弊害が存在することがわかります。

また、C++ などの静的リンクを行う言語では、口座 クラスを参照しているクラスは、口座 クラスのヘッダファイルが変更する都度、全てコンパイルおよびリンクをし直さなければならず、あまりに非効率です。

つまり、現状の 口座 クラスには、SPR が上手く適用できていないと言えます。では、口座 クラスに SPR を適用してみましょう。適用結果を以下に示します。(List 5-2-1)(List 5-2-2)

List 5-2-1 : 口座クラス(Account.cs)
  1:using System;
  2:
  3:namespace BankApplication
  4:{
  5:  /// <summary>口座クラス</summary>
  6:  public class Account
  7:  {
  8:    ・・・List 2-4-1 と同様・・・
 46:  }
 47:}

修正した 口座 クラスに関しては、List 2-4-1 のソースコードとまったく同一になるため詳細は割愛します。
年利計算に関する責務は、新たに年利計算のためのクラスを作成し責務を割り当てました。(List 5-2-2)

List 5-2-2 : 年利計算クラス(AnnualRate.cs)
  1:using System;
  2:
  3:namespace BankApplication
  4:{
  5:  /// <summary>年利計算クラス</summary>
  6:  public class AnnualRate
  7:  {
  8:    #region Constructors & Destructors
  9:    public AnnualRate() { }
 10:    #endregion
 11:
 12:    #region Public Instance Method
 13:    public decimal ComputeAnnualInterest(Account account)
 14:    {
 15:      return Decimal.Truncate(
 16:        account.Balance * annualRate
 17:        / 365
 18:        * DateTime.Today.DayOfYear
 19:        );
 20:    }
 21:    #endregion
 22:
 23:    #region Private Static Fields
 24:    private const decimal annualRate = 0.0003M;
 25:    #endregion
 26:  }
 27:}

このように SPR を適用し変更理由による変更箇所を局所化したことによって、口座 クラスに対して変更を加えることなく、年利計算を自由に修正することができるようになりました。また、口座 クラスはシステム固有で変更する箇所が減った為、安定した状態になり再利用性が高まったと思います。

六、まとめ

このように責務を意識することは、オブジェクト指向設計において重要な視点であり、責務を注意深く割り当てることにより、ソフトウェアに「理解容易性」、「変更容易性」、「頑健性」、「再利用性」などの品質要因を付与することができます。

しかし、実際の設計においては複数の責務を実装クラスに割り当ててしまいがちです。
そこで、責務をある要求に対する変更要因と捉え、SPR を適用することによって、責務の分割を促すことができます。ただし、必要以上のクラス分割は逆に設計に複雑性をもたらすため、本当に必要なところにだけ適用することを心がけることが重要です。

今回は、「責務」というオブジェクト指向設計における重要な概念について考えてみました。次回は、設計をさらに洗練するために「役割」について考えてみたいと思います。

では、また次回にお会いしましょう。

七、参考資料

  1. [BOOCH] 『Booch法:オブジェクト指向分析と設計 第2版』 Grady Booch/著, 山城 明宏・井上 勝博・田中 博明・入江 豊・清水 洋子・小尾 俊之/訳, アジソン・ウェスレイ
  2. [CRC] 『The CRC Card Book』 David Bellin・Susan Suchman Simone/著 Addison-Wesley
  3. [LARMAN] 『実践UML』 Craig Larman/著, 依田 光江/訳, 今野 睦・依田 智夫/監訳 プレンティスホール
  4. [MARTIN] 『The Single Respolsibility Principle』 Robert C. Martin
    http://www.objectmentor.com/resources/articleIndex
    この記事は、2002年出版予定の書籍"The Principles, Patters, and Practices of Agile Softweare Development"の草稿だそうです。

八、Appendix

八 ノ 一、Java によるソースコードの例

List 8-1-1 : 口座クラス(Account.java)
  1:package BankApplication;
  2:
  3:import java.math.BigDecimal;
  4:
  5:public class Account
  6:{
  7:  public Account(Depositor depositor) {
  8:    setDepositor(depositor);
  9:  }
 10:
 11:  public BigDecimal getBalance() {
 12:    return balance;
 13:  }
 14:
 15:  public void setBalance(BigDecimal value) {
 16:    balance = value;
 17:  }
 18:
 19:  public Depositor getDepositor() {
 20:    return depositor;
 21:  }
 22:
 23:  public void setDepositor(Depositor value) {
 24:    depositor = value;
 25:  }
 26:
 27:  public BigDecimal withdraw(BigDecimal amount) {
 28:    balance = balance.subtract(amount);
 29:    return balance;
 30:  }
 31:
 32:  public void deposit(BigDecimal amount) {
 33:    balance.add(amount);
 34:  }
 35:
 36:  private BigDecimal balance = new BigDecimal(0.0);
 37:  private Depositor depositor;
 38:}

List 8-1-2 : 預金者クラス(Depositor.java)
  1:package BankApplication;
  2:
  3:import java.util.Date;
  4:import java.util.Calendar;
  5:
  6:public class Depositor
  7:{
  8:  public Depositor(String name, Date birthday) {
  9:    setName(name);
 10:    setBirthDay(birthday);
 11:  }
 12:
 13:  public String getName() {
 14:    return name;
 15:  }
 16:
 17:  public void setName(String value) {
 18:    name = value;
 19:  }
 20:
 21:  public Date getBirthDay() {
 22:    return birthday;
 23:  }
 24:
 25:  public void setBirthDay(Date value) {
 26:    birthday = value;
 27:  }
 28:
 29:  public int getAge() {
 30:    Calendar birth = Calendar.getInstance();
 31:    birth.setTime(birthday);
 32:    Calendar today = Calendar.getInstance();
 33:    today.setTime(new Date());
 34:
 35:    int age = today.get(Calendar.YEAR)
 36:              - birth.get(Calendar.YEAR)
 37:              - ( today.get(Calendar.DAY_OF_YEAR) > birth.get(Calendar.DAY_OF_YEAR) )
 38:                ? 1
 39:                : 0;
 40:  }
 41:
 42:  public Account getAccount() {
 43:    return account;
 44:  }
 45:
 46:  public void setAccount(Account value) {
 47:    account = value;
 48:  }
 49:
 50:  private String name;
 51:  private Date birthday;
 52:  private Account account;
 53:}

List 8-1-3 : 年利計算クラス(AnnualRate.java)
  1:package BankApplication;
  2:
  3:import java.math.BigDecimal;
  4:import java.util.Calendar;
  5:import java.util.Date;
  6:
  7:public class AnnualRate
  8:{
  9:  public AnnualRate() { }
 10:
 11:  public BigDecimal computeAnnualInterest(Account account) {
 12:    Calendar today = Calendar.getInstance();
 13:    today.setTime(new Date());
 14:
 15:    return account.getBalance().multiply(annualRate).divide(
 16:      new BigDecimal(365.0), BigDecimal.ROUND_HALF_UP).multiply(
 17:      new BigDecimal(today.get(Calendar.DAY_OF_YEAR))
 18:      ).setScale(0, BigDecimal.ROUND_DOWN);
 19:  }
 20:
 21:  private static final BigDecimal annualRate = new BigDecimal(0.0003);
 22:}

八 ノ 二、VB.NET によるソースコードの例

List 8-2-1 : 口座クラス(Account.vb)
  1:Public Class Account
  2:#Region "Constructors & Destructors"
  3:  Public Sub New(ByVal Depositor As Depositor)
  4:    Me.Depositor = Depositor
  5:  End Sub
  6:#End Region
  7:
  8:#Region "Public Instance Properties"
  9:  Public Property Balance() As Decimal
 10:    Get
 11:      Return m_Balance
 12:    End Get
 13:    Set(ByVal Value As Decimal)
 14:      m_Balance = Value
 15:    End Set
 16:  End Property
 17:
 18:  Public Property Depositor() As Depositor
 19:    Get
 20:      Return m_Depositor
 21:    End Get
 22:    Set(ByVal Value As Depositor)
 23:      m_Depositor = Value
 24:    End Set
 25:  End Property
 26:#End Region
 27:
 28:#Region "Public Instance Methods"
 29:  Public Function Withdraw(ByVal Amount As Decimal)
 30:    m_Balance -= Amount
 31:    Return m_Balance
 32:  End Function
 33:
 34:  Public Function Deposit(ByVal Amount As Decimal)
 35:    m_Balance += Amount
 36:  End Function
 37:#End Region
 38:
 39:#Region "Private Instance Fields"
 40:  Private m_Balance As Decimal
 41:  Private m_Depositor As Depositor
 42:#End Region
 43:End Class

List 8-2-2 : 預金者クラス(Depositor.vb)
  1:Public Class Depositor
  2:#Region "Constructors & Destructors"
  3:  Public Sub New(ByVal Name As String, ByVal BirthDay As Date)
  4:    Me.Name = Name
  5:    Me.BirthDay = BirthDay
  6:  End Sub
  7:#End Region
  8:
  9:#Region "Public Instance Properties"
 10:  Public Property Name() As String
 11:    Get
 12:      Return m_Name
 13:    End Get
 14:    Set(ByVal Value As String)
 15:      m_Name = Value
 16:    End Set
 17:  End Property
 18:
 19:  Public Property BirthDay() As Date
 20:    Get
 21:      Return m_BirthDay
 22:    End Get
 23:    Set(ByVal Value As Date)
 24:      m_BirthDay = Value
 25:    End Set
 26:  End Property
 27:
 28:  Public ReadOnly Property Age() As Integer
 29:    Get
 30:      Dim today As Date = DateTime.Today
 31:      Dim intAge As Integer = today.Year - m_BirthDay.Year
 32:
 33:      intAge += CInt(today.DayOfYear > m_BirthDay.DayOfYear)
 34:      Return intAge
 35:    End Get
 36:  End Property
 37:
 38:  Public Property Account() As Account
 39:    Get
 40:      Return m_Account
 41:    End Get
 42:    Set(ByVal Value As Account)
 43:      m_Account = Value
 44:    End Set
 45:  End Property
 46:#End Region
 47:
 48:#Region "Private Instance Fields"
 49:  Private m_Name As String
 50:  Private m_BirthDay As Date
 51:  Private m_Account As Account
 52:#End Region
 53:End Class

List 8-2-3 : 年利計算クラス(AnnualRate.vb)
  1:Public Class AnnualRate
  2:#Region "Constructors & Destructors"
  3:  Public Sub New()
  4:  End Sub
  5:#End Region
  6:
  7:#Region "Public Instance Method"
  8:  Public Function ComputeAnnualInterest(ByVal Account As Account) As Decimal
  9:    Return Decimal.Truncate(Account.Balance * c_AnnualRate _
 10:                             / 365 _
 11:                             * Date.Today.DayOfYear _
 12:                             )
 13:  End Function
 14:#End Region
 15:
 16:#Region "Private Static Fields"
 17:  Private Const c_AnnualRate As Decimal = 0.0003
 18:#End Region
 19:End Class


記事の内容を5点満点で評価してください。
1点 2点 3点 4点 5点
記事に関するコメントがあれば併せてご記入ください。
  

© 2002 OGIS-RI Co., Ltd.
Prev. Index
Prev. Index