#9 オブジェクト指向
C#はオブジェクト指向型言語です.どういうことかというと,オブジェクト指向という考え方で設計したモデルをプログラムの中で表現することに長けた言語,ということです.
オブジェクト指向というのはいくつものオブジェクト(部品)が集まって,それぞれのオブジェクトが作用することで,一つのシステムを作るという考え方です.次の例を見てみましょう.
例としてRPGを作る場合を考えます.勇者とスライムがいて勇者がスライムに攻撃するとしましょう.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ObjectOrientedSample
{
class Program
{
static void Attack(string attackerName, int attackerAttack, string attackedName, ref int attackedHitPoint)
{
attackedHitPoint -= attackerAttack;
Console.WriteLine(attackerName + "の攻撃!");
Console.WriteLine(attackedName + "に" + attackerAttack + "のダメージ!");
Console.WriteLine(attackedName + "の残りHP : " + attackedHitPoint);
}
static void Main(string[] args)
{
string braveNmae = "勇者";
int braveHitPoint = 512;
int braveAttack = 64;
string slimeNmae = "スライム";
int slimeHitPoint = 128;
int slimeAttack = 16;
Attack(braveNmae, braveAttack, slimeNmae, ref slimeHitPoint);
}
}
}
勇者の攻撃!
スライムに64のダメージ!
スライムの残りHP : 64
ref
キーワードは変数の参照を渡すもので,関数内での操作が実引数として渡した変数にも適用されるというものです.
この時点でもうかなり面倒ですが,ここで「新キャラとして武闘家と僧侶を,敵モンスターにドラゴンも追加しよう」となるとかなり大変なのは容易に想像できるでしょう.さらにその後「キャラクター全員に武器や装備を設定できるようにして,モンスターの種類が増えたからスライム種やドラゴン種などの項目を追加で設けよう」なんてことになると担当プログラマが発狂しかねません.しかしオブジェクト指向で書くと後からの変更に強いプログラムを書けるので,ちょっとした機能の変更ならば簡単に対応できるようになります.
##クラス
クラスというのは,構造体と同じで複数の型をまとめて扱うことができるというもので,大体構造体と変わりません.異なるのはデータの保持方法で,構造体が値型であることに対して,クラスは参照型です.次の例を見るとわかりやすいかもしれません.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ObjectOrientedSample
{
struct MyStruct
{
public int num;
public MyStruct(int num)
{
this.num = num;
}
}
class MyClass
{
public int num;
public MyClass(int num)
{
this.num = num;
}
}
class Program
{
static void Main(string[] args)
{
MyStruct s1 = new MyStruct(2);
MyStruct s2 = s1;
s2.num = 3;
Console.WriteLine("s1.num = " + s1.num + ", s2.num = " + s2.num);
MyClass c1 = new MyClass(2);
MyClass c2 = c1;
c2.num = 3;
Console.WriteLine("c1.num = " + c1.num + ", c2.num = " + c2.num);
}
}
}
s1.num = 2, s2.num = 3
c1.num = 3, c2.num = 3
MyStruct
もMyClass
もnum
を2で初期化した1つ目のインスタンスを2つ目のインスタンスにコピーし,2つ目のインスタンスのnum
に3を代入しました.すると,s1.num
は2,s2.num
は3と当然の結果が出ています.しかし,c1.num
とc2.num
は共に3になっています.これは,c2
にc1
を代入した時,コピーされたものが値ではなく,アドレスだったからです.つまり,c1
とc2
は同じインスタンスであるということになります.
構造体とクラスは似ていますが,インスタンスをコピーする際,構造体が中の変数までごっそりコピーするのに対し,クラスはインスタンスの参照のみをコピーするので早いということと,クラスにはできて構造体にはできないことがあるので,基本的にはクラスを使った方が良いと言われています.
また,クラスは関数を持たせることができます(実は構造体にも持たせられますが).
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ObjectOrientedSample
{
class Person
{
public string name;
public int age;
public Person(string name, int age)
{
this.name = name;
this.age = age;
}
public void SelfIntroduction()
{
Console.WriteLine($"I'm {name}.");
Console.WriteLine($"I'm {age} years old.");
}
}
class Program
{
static void Main(string[] args)
{
Person kyohei = new Person("Kyohei", 20);
kyohei.SelfIntroduction();
}
}
}
I'm Kyohei.
I'm 20 years old.
上の例を見るとわかると思いますが,クラス内に定義した関数(メソッドと言います)はインスタンス内の変数を使った処理ができます.メソッドを使えば先ほどのRPGをもっと柔軟に書けます.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ObjectOrientedSample
{
class Player
{
public string name;
public int hitPoint;
public int attack;
public Player(string name, int hitPoint, int attack)
{
this.name = name;
this.hitPoint = hitPoint;
this.attack = attack;
}
public void Attack(Enemy enemy)
{
enemy.hitPoint -= attack;
Console.WriteLine(name + "の攻撃!");
Console.WriteLine(enemy.name + "に" + attack + "のダメージ!");
Console.WriteLine(enemy.name + "の残りHP : " + enemy.hitPoint);
}
}
class Enemy
{
public string name;
public int hitPoint;
public int attack;
public Enemy(string name, int hitPoint, int attack)
{
this.name = name;
this.hitPoint = hitPoint;
this.attack = attack;
}
public void Attack(Player player)
{
player.hitPoint -= attack;
Console.WriteLine(name + "の攻撃!");
Console.WriteLine(player.name + "に" + attack + "のダメージ!");
Console.WriteLine(player.name + "の残りHP : " + player.hitPoint);
}
}
class Program
{
static void Main(string[] args)
{
Player brave = new Player("勇者", 512, 64);
Player fighter = new Player("格闘家", 256, 32);
Enemy slime = new Enemy("スライム", 128, 16);
Enemy dragon = new Enemy("ドラゴン", 2048, 1024);
brave.Attack(slime);
}
}
}
勇者の攻撃!
スライムに64のダメージ!
スライムの残りHP : 64
クラスの定義が少し面倒ですが,定義を済ませてしまえば容易に扱えるというのがMain関数が物語っています.もし仕様の変更があれば,クラスの定義をいじってしまえば全インスタンスに適用されます.
次回はオブジェクト指向型プログラミングで重要とされる継承について説明します.
##練習問題
今回使ったPlayerクラスをいじって,武器を装備できるようにしてください.
解答例
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ObjectOrientedSample
{
class Weapon
{
public readonly string name;
public readonly int attack;
public Weapon(string name, int attack)
{
this.name = name;
this.attack = attack;
}
public static Weapon None()
{
return new Weapon("なし", 0);
}
}
class Player
{
public string name;
public int hitPoint;
public int attack;
public Weapon weapon;
public Player(string name, int hitPoint, int attack)
{
this.name = name;
this.hitPoint = hitPoint;
this.attack = attack;
weapon = Weapon.None();
}
public Player(string name, int hitPoint, int attack, Weapon weapon)
{
this.name = name;
this.hitPoint = hitPoint;
this.attack = attack;
this.weapon = weapon;
}
public void Attack(Enemy enemy)
{
enemy.hitPoint -= attack + weapon.attack;
Console.WriteLine(name + "の攻撃!");
Console.WriteLine(enemy.name + "に" + attack + "のダメージ!");
Console.WriteLine(enemy.name + "の残りHP : " + enemy.hitPoint);
}
}
class Enemy
{
public string name;
public int hitPoint;
public int attack;
public Enemy(string name, int hitPoint, int attack)
{
this.name = name;
this.hitPoint = hitPoint;
this.attack = attack;
}
public void Attack(Player player)
{
player.hitPoint -= attack;
Console.WriteLine(name + "の攻撃!");
Console.WriteLine(player.name + "に" + attack + "のダメージ!");
Console.WriteLine(player.name + "の残りHP : " + player.hitPoint);
}
}
class Program
{
static void Main(string[] args)
{
Weapon woodSword = new Weapon("木の剣", 4);
Player brave = new Player("勇者", 512, 64, woodSword);
Enemy slime = new Enemy("スライム", 128, 16);
brave.Attack(slime);
}
}
}