継承とカプセル化
目次
継承とカプセル化
継承
理解編
継承
継承 とは、あるクラスを土台として新しいクラスを作ることである。新しいクラスは継承元のメンバーを引き継ぐことができる。また、新しいメンバーを追加することもできる。 継承元のクラスを基本クラス 、新しいクラスを 派生クラス と呼ぶ。1つの基本クラスから派生クラスを作ることとを単一継承 と呼ぶ。一方、複数の基本クラスから派生クラスを作ることを 多重継承と呼ぶが、C# では使用できない。 Javaとかなら使用できるのだろうか?
継承は派生クラスの後ろに:
を記述し、その後ろに継承元の基本クラスを指定する。
// 基本クラスの定義 class 基本クラス { 処理内容 } // 派生クラスの定義 class 派生クラス : 基本クラス { 処理内容 }
継承クラスのコンストラクタ
基本クラスと派生クラスにコンストラクタが存在する場合、基本クラスのコンストラクタ、派生クラスのコンストラクタの順番で呼び出される。
引数なしのコンストラクタはインスタンス生成時に自動的に呼び出される。一方、引数ありのコンストラクタでは、コンストラクタ初期化子と呼ばれるbase
キーワードを使用し、基本クラスに渡す引数を指定する必要がある。
隠蔽
隠蔽とは、派生クラスのメンバーを基本クラスのメンバーと同じ名前に定義することで、基本クラスのメンバーを強制的に上書きすることである。隠蔽する場合は、メンバー宣言の前にnew
をつければよい。
基本クラスを参照していることを示すbase
を使用することで、派生クラスでメンバーを隠蔽しつつ基本クラスのメンバーにアクセスできる。
そうなると隠蔽するメリットは何なのだろうか?
隠蔽に似た機能で オーバーライド というものがある。オーバーライドは基本クラスのメソッドを派生クラスで書き換えるものであるが、隠蔽とは違うらしい。
実践編
継承
// 基本クラスの生成 class Music { public int Type = 3; public string Name = "Music"; public void BaseInfo() { Console.WriteLine("BaseInfo is OK"); } } // 派生クラスの生成 class Song : Music { public string Key = "Song"; // 新しいメソッドの追加 public void DrvInfo() { Console.WriteLine("DrvInfo is OK"); } } class MainClass { static void Main(string[] args) { // 派生クラスのインタスタン生成 Song test = new Song(); // 基本クラスのインタスタン生成 Music baseInst = new Music(); Console.WriteLine("----Access to derived class----"); // 派生クラスのメンバーアクセス Console.WriteLine("Key is " + test.Key); Console.WriteLine("Type is " + test.Type); test.DrvInfo(); test.BaseInfo(); Console.WriteLine("----Access to base class----"); // 基本クラスのメンバーアクセス Console.WriteLine("Type is " + baseInst.Type); baseInst.BaseInfo(); Console.ReadKey(); } }
結果を次に示す。 派生クラスでは、基本クラスのメンバーに加えて派生クラスにて追加したメンバーも使用できることが分かる。
----Access to derived class---- Key is Song Type is 3 DrvInfo is OK BaseInfo is OK ----Access to base class---- Type is 3 BaseInfo is OK
継承クラスのコンストラクタ
引数なしのコンストラクタ
class Music { public Music() { Console.WriteLine("Base class Constracter"); } } class Pop : Music { public Pop() { Console.WriteLine("Derived class Constracter"); } } class Program { static void Main(string[] args) { Pop instPop = new Pop(); } }
結果を次に示す。
Base class Constracter Derived class Constracter
引数ありのコンストラクタ
class Music { public Music(string str) { Console.WriteLine(str); } } class Pop : Music { public Pop(string str1, string str2) : base(str1) { Console.WriteLine(str2); } } class Program { static void Main(string[] args) { Pop instPop = new Pop("Music", "Pop"); } }
結果を次に示す。
Music Pop
隠蔽
隠蔽のコードを次に示す。
// 基本クラスの定義 class Music { public int value = 5; } // 派生クラスの定義 class Pop : Music { // valueを隠蔽 new public int value = 10; public void ViewValue() { // 基本クラスにアクセス Console.WriteLine(base.value); // 自分のメンバーにアクセス Console.WriteLine(value); } } class Program { static void Main(string[] args) { Pop instPop = new Pop(); instPop.ViewValue(); } }
結果を次に示す。
5 10
カプセル化
理解編
カプセル化
カプセル化とは、クラスへの外部アクセスを制限し、安全に使いやすくするためのものである。
- アクセス制限はアクセス修飾子を使用する。
- アクセス修飾子によりアクセス制限のレベルが設定できる。
アクセス修飾子 | レベル |
---|---|
private | クラスの内部のみアクセス可能 |
public | アクセス制限なし |
protected | クラスの内部のみアクセス可能、または派生クラスに制限される |
フィールドのアクセス制限
クラス内のフィールド値を安全に変更するには、外部から直接アクセスしない方がよい。その方法として、アクセサーメソッド を使用する方法と プロパティ を使用する方法がある。
とは書いたが、なぜフィールド値を直接書き換えしてはいけないのか理解できていない。リード/ライドの方向指定ができること、値のエラーチェックがことがメリットなのだろうか。
アクセサーメソッド
アクセサーメソッドとはフィールド値の変更や取得を行うためだけのメソッドのことである。メソッド名がSetxx
やGetxx
となっているものが、アクセサーメソッドに該当するらしい。Setxx
メソッドで入力値のエラーチェックが行えるので、有効な値を他のメソッドに渡すことができる。
ただし、フィールド変数の数だけアクセサーメソッドの数が増えることから、可読性が悪くなるというデメリットがあるらしい。そこで C# では、デメリットを解消する方法として、プロパティ と呼ばれる機能がある。
プロパティ
プロパティは『クラスの内部ではメソッドのように振る舞うが、クラス外からはフィールドのようにアクセスできる』ものである。プロパティは、set
と set
を使用し、次のように定義すること使用できる。
アクセス修飾子 データ型 プロパティ { set { // 値代入時の処理を記述 フィールド名 = value; // 代入値の妥当性チェックの記述も可能 } get { // 値参照時の処理を記述 // 返却値の妥当性チェックの記述も可能 return フィールド名; } }
set
メソッドは値を設定するものなので戻り値を指定できない。また、これらメソッドは値の妥当性チェックや演算も行うことができる。 確かに使ってみると変数と同じ扱いで済むので便利である!
自動プロパティ
値の設定・取得をするだけならプロパティの中身を省略して記述できる。このプロパティを 自動プロパティ と呼ぶ。自動プロパティに初期値を与えることもできる。初期値が不要な場合は、次のうち= 初期値
を削除すればよい。
データ型 プロパティ名 {get; set;} = 初期値;
実践編
カプセル化
class Music { private int type = 0; // protected :クラス内部と派生クラスの内部からのみアクセス可能 protected string name = "Music"; public void SetType(int setType) { // "this"により、Musicクラスのメンバーを指定する。 // "this"は、他メンバーにパラメータを渡す場合に用いる。 // 同じクラス内なので、Typeにアクセス可能 this.type = setType; } // フィールドの値を出力するメソッド public void Printname() { Console.WriteLine(name); Console.WriteLine(type); } } class Pop : Music { new public void Printname() { // nameのアクセス修飾子はprivate、派生クラスなのでアクセスできない // base.type = 100; // nameのアクセス修飾子はprotected、派生クラスなのでアクセス可能 this.name = "Pop"; // "base"により、基本クラスのメソッドを呼び出している。 // Printnameのアクセス修飾しはpublic なのでアクセス可能 base.Printname(); } } class MainClass { static void Main(string[] args) { Pop pop = new Pop(); pop.SetType(10); // protected にアクセスしようとするとエラーになる // アクセスできない保護レベルとなっている // pop.name = "test"; pop.Printname(); Console.ReadKey(); } }
次に結果を示す。
Pop 10
フィールドのアクセス制限
アクセサーメソッド
アクセサーメソッドを用いたコードを次に示す。
class Bmi { // --フィールド値 // アクセス修飾子をprivateにして、クラス外からのアクセスを禁止する private double height = 0; private double weight = 0; // -- heightを取得するためのアクセサーメソッド public double GetHeight() { return this.height; } // -- weightを取得するためのアクセサーメソッド public double GetWeight() { return this.weight; } // -- 取得したheightをセットするためのアクセサーメソッド public void SetHeight(double setHeight) { if (setHeight <= 0) { Console.WriteLine("Error : invalid value"); } else { this.height = setHeight; Console.WriteLine($"set height is {height} cm"); } } // -- 取得したweightをセットするためのアクセサーメソッド public void SetWeight(double setWeight) { if (setWeight <= 0) { Console.WriteLine("Error : invalid value"); } else { this.weight = setWeight; Console.WriteLine($"set weight is {weight} kg"); } } // -- BMIを計算するメソッド public double CalcBmi() { return GetWeight() / ((GetHeight() / 100) * (GetHeight() / 100)); } } class Program { static void Main(string[] args) { Bmi bmiInst = new Bmi(); Console.Write("enter height (cm) :"); double height = double.Parse(Console.ReadLine()); Console.Write("enter weight (kg) :"); double weight = double.Parse(Console.ReadLine()); // 下記のようにメンバーを直接アクセスすることができない。 // bmiInst.weght = 64; // アクセサーメソッドを用いてメンバー値の変更を行う。 bmiInst.SetHeight(height); bmiInst.SetWeight(weight); Console.WriteLine($"BMI is {bmiInst.CalcBmi()}"); } }
heiht:174、weight:65 を入力したときの結果を次に示す。
enter height (cm) :174 enter weight (kg) :65 set height is 174 cm set weight is 65 kg BMI is 21.4691504822301
プロパティ
class Bmi { // --フィールド値 // アクセス修飾子をprivateにして、クラス外からのアクセスを禁止する private double height = 0; private double weight = 0; // -- Weightプロパティ public double Weight { set { if (value <= 0) { Console.WriteLine("Error : invalid value"); } else { this.height = value; Console.WriteLine($"set height is {height} cm"); } } get { return this.height; } } public double Height { set { if (value <= 0) { Console.WriteLine("Error : invalid value"); } else { this.weight = value; Console.WriteLine($"set weight is {weight} kg"); } } get { return this.weight; } } // -- BMIを計算するメソッド public double CalcBmi() { return Weight / ((Height / 100) * (Height / 100)); } } class Program { static void Main(string[] args) { Bmi bmiInst = new Bmi(); Console.Write("enter height (cm) :"); double height = double.Parse(Console.ReadLine()); Console.Write("enter weight (kg) :"); double weight = double.Parse(Console.ReadLine()); // 下記のようにメンバーを直接アクセスすることができない。 // bmiInst.weght = 64; // アクセサーメソッドを用いてメンバー値の変更を行う。 bmiInst.Height = height; bmiInst.Weight = weight; Console.WriteLine($"BMI is {bmiInst.CalcBmi()}"); } }
heiht:174、weight:65 を入力したときの結果を次に示す。 結果を次に示す。
enter height (cm) :174 enter weight (kg) :65 set weight is 174 kg set height is 65 cm BMI is 21.4691504822301
自動プロパティ
自動プロパティのコードを次に示す。
class CallProperty { // 自動プロパティ public int Type { set; get; } = 10; } class Program { static void Main(string[] args) { CallProperty call = new CallProperty(); Console.WriteLine(call.Type); call.Type = 100; Console.WriteLine(call.Type); } }
結果を次に示す。
10 100