LoginSignup
2
0

More than 5 years have passed since last update.

継承 (七週目の学習)

Last updated at Posted at 2018-11-02

モチベーションも大事だと思うので覚書として使用します。

七週目の感想
七週目は、継承を学習。
学習書/ガイドラインとして参考にしている「猫でもわかるC#」にて序盤に軽く触れた継承が、
約30P分の量で再登場したので、じっくり臨みました。(書籍内でも主要な要素であろう事が伺えた・・・)
派生クラスやその多層化で、最初に比べればオブジェクト指向の片鱗位は理解できたと思います。

               

継承

継承は、OOP三原則(継承・ポリモーフィズム・カプセル化)の1つであり非常に重要な概念です。
変更に対して柔軟に対応するため、可能な限りこの三原則を用いて設計・保守していく事になります。
継承は、引き継ぐ事を意味するが、それは元となる規格であり機能ではない事に注意。
・参考: オブジェクト指向と10年戦ってわかったこと



継承は概念ですが、C#での具体例をあげるとすれば、
元のクラスを引き継いだまま新しいクラスを作成する事ができます(便利!)
元のクラスを基本クラス(base class)
新しく作成されたクラスを派生クラス(derived class)

といいます。

構文は以下のように書きます。
class 派生クラス名:基本クラス名

※基本クラスのpublicなメンバは派生クラスから呼び出す事ができます。
ただ全てpublicにしてしまうと、派生クラス以外に依存関係を作る恐れがあるため、
その場合protectedprivate protectedを用いる事で基本クラスとその派生クラスにだけ影響を与えます。

基本的なコード例をここに示します
using System;

class MyBase// 基本クラス
{
    public int a = 10;
    public void BaseMethod()
    {
        Console.WriteLine("ここは基本クラス");
    }
}

class MyDerived : MyBase// 派生クラス作成
{
    public int b = 20;
    public void DerivedMethod()
    {
        Console.WriteLine("ここは派生クラス");
    }
}

class Inheritance01
{
    public static void Main()
    {
        MyDerived md = new MyDerived();// 派生クラスのインスタンス
        md.BaseMethod();// 基本クラスのpublicなメンバは派生クラスから呼び出せる
        md.DerivedMethod();
        Console.WriteLine($"md.a={md.a}");
        Console.WriteLine($"md.b={md.b}");
        MyBase mb = new MyBase();
        mb.BaseMethod();
        Console.WriteLine($"mb.a={mb.a}");
    }
}

image.png

このように継承(inheritance)とは「引き継ぐ」ことを意味します。



他の継承に関連する機能を見ていきましょう。


名前の隠蔽

名前の隠蔽により、派生クラスでも基本クラスのメンバと同名のメンバを持つ事が可能になります。
使用には、newを頭につけるだけです(この時後ろの()は必要ありません)
以降のメンバ名は全てnewメンバが呼び出され元のメンバは隠されます。

※隠蔽された元のメンバを使いたい場合、base.メンバ名を用いる事で呼び出せます。
(但しstaticなメソッドでは扱えません)

基本的なコード例をここに示します
namespace Inpei
{
    class Base
    {
        public int x = 10;
        protected void BaseMethod()
        {
            Console.WriteLine("Baseクラス");
        }
    }

    class Derived : Base
    {
        new public int x = 20;// 名前の隠蔽
        new public void BaseMethod()// 名前の隠蔽
        {
            Console.WriteLine("Derivedクラス");
            Console.WriteLine($"base.x={base.x}, x={x}");//base.メンバ名と更新されたメンバ
        }
    }

    class Inheritance
    {
        public static void Main()
        {
            Derived d = new Derived();// static method内はnewインスタンス作成
            Console.WriteLine($"x={d.x}");// 20

            //base はstaticでは使用できないnewインスタンス作成しても無理
            //Base b = new Base();
            //Console.WriteLine($"base.x={base.x}, {x}");

            d.BaseMethod();
        }
    }
}

image.png

このように、newメンバにより元のメンバが隠される事を、名前の隠蔽といいます。


オーバーライド

オーバーライドは、派生クラスで同名のメソッド等を作る事ができます。
引数の違うメソッドを多重定義できるオーバーロード(読み込む)(引数違う)とは違い、
オーバーライド(上書き)(引数同じ)は、再定義をする事ができます。

また、base.メンバ名と同じく、staticなメソッドでは定義できません。
メソッドの他、プロパティやインデクサでも使用可能です。
元のメソッドを仮想メソッド(virtual method)
新しいメソッドをオーバーライドメソッド(override method)
といいます。

使用には、アクセス修飾子の後ろにvirtualをつけ、
派生クラスでは、overrideと同じ引数で再定義します。

※引数は同じでなければなりません。

基本的なコード例をここに示します

※Mainメソッドで参照変数と代入する分のnewインスタンスを全て作成し、実行前どのクラスの参照を代入するかによってm.Nakigoe()の動作が異なっている事に注意。
namespace Overrider
{
    class Mamma1// Mamma 哺乳類
    {
        protected readonly int leg = 4;// readonly 読み込み専用にして変更不可
        protected string koe;// 規格

        public virtual string Nakigoe()// virtual koe返す
        {
            return koe;
        }
        public int Leg()// 全部4本足なのでoverride不必要 leg返す
        {
            return leg;
        }
    }

    class Cat : Mamma1// 派生クラス
    {
        public override string Nakigoe()// Override
        {
            koe = "ニャー";
            return koe;// 呼出元(仮想メソッド)にkoe返す
        }
    }

    class Dog : Mamma1// 派生クラス
    {
        public override string Nakigoe()// Override
        {
            koe = "ワン";
            return koe;// 呼出元(仮想メソッド)にkoe返す
        }
    }

    class Override01
    {
        public static void Main()
        {
            Mamma1 m; //参照変数mを宣言
            Cat cat = new Cat();// 代入用インスタンス作成
            Dog dog = new Dog();// 代入用インスタンス作成

            m = cat; // 参照変数mに代入
            Console.WriteLine($"猫の脚は{m.Leg()}本で鳴き声は{m.Nakigoe()}です");// m(cat)の鳴き声

            m = dog;// どのクラスの参照を代入したかによってm.Nakigoe()の動作が異なる
            Console.WriteLine($"犬の脚は{m.Leg()}本で鳴き声は{m.Nakigoe()}です");// m(dog)の鳴き声
        }
    }
}

このように、規格となるvirtual methodから各パーツであるoverride methodが作成されます。


クラスの多層階層

多層階層は、派生クラスを元に制限なく何層も派生クラスを作成できます。
使用には、新しい派生クラス:基となる派生クラスとします。

基本的なコード例をここに示します
namespace BaseDelived
{
    class MyBase// 基本クラス
    {
        protected int x = 10;
        public virtual void Show()
        {
            Console.WriteLine($"x={x}");
        }
    }

    class Derived1 : MyBase// 派生クラス
    {
        protected int y = 20;// 派生クラスでy定義しただけ。意味なし
    }

    class Derived2 : Derived1// 上の派生クラスを継承した派生クラス
    {
        int z = 30;// 本来はx、上書きされたz定義↓
        public override void Show()// MyBase.Showをoverrideしてx=xをz=zに上書き
        {
            Console.WriteLine($"z={z}");
        }
    }

    class inheritance
    {
        public static void Main()
        {
            MyBase mb;
            Derived1 d1 = new Derived1();
            Derived2 d2 = new Derived2();

            mb = d1;
            mb.Show();//d1時点では本来のxなので10

            mb = d2;
            mb.Show();//d2でoverrideされたのでx=10でなくz=30
        }
    }
}

image.png

このように、派生クラスを階層化しながらメソッドのoverrideが可能です。

クラスの継承とコンストラクタ

コンストラクタは、インスタンス生成時に呼び出されるメソッドで、
インスタンスが他クラスで使われる前に初期化処理してくれます。
この場合newで呼び出されたクラスのメソッドを指します。

使用上のルールとして、
・メソッド名(コンストラクタ名)はクラス名と同じ。
・戻り値は指定しない。
・呼び出されるタイミングはインスタンス生成時のみ。

基本的なコード例をここに示します
    public class Test
    {
        public string Tag;

        public Test() // こ
        {        // の
            Tag = "A";// 部
        }        // 分
    }
// この部分がコンストラクタになります。
// コンストラクタは戻り値voidを定義しません。
// さらに、メソッド名はクラス名と同じである必要があります。

    static void Main(string[] args)
    {
        Test test = new Test();
    }
// newにより呼び出されたメソッドがコンストラクタです。
// test.Tag には ”A" という値がセットされる事になります。

では本題ですが、派生クラスのコンストラクタはそれぞれどのように実行されていくのでしょうか?

基本的なコード例をここに示します
namespace Inheritance
{
    class MyBase
    {
        protected int x;

        public MyBase()// MyBaseクラスのconstructor
        {
            Console.WriteLine("ここはMyBase");
            x = 10;
        }
    }

    class Derived1 : MyBase
    {
        public Derived1()// Derived1クラスのconstructor
        {
            Console.WriteLine("ここはDerived1");
            x = 20;
        }
    }

    class Derived2 : Derived1
    {
        public Derived2()// Derived2クラスのconstructor
        {
            Console.WriteLine("ここはDerived2");
            x = 30;
        }
        public void Show()
        {
            Console.WriteLine($"x={x}");
        }
    }

    class Inheritance
    {
        public static void Main()
        {
            Derived2 d2 = new Derived2();// constructor呼出
            d2.Show();
            // 代入された値(d2)は、そのShowメソッドを呼び出す際、
            // xは遡って元の10、派生の20、その派生の30と基本クラスから順に呼び出される事になる。
            // 従ってShowメソッドは30となった。
        }
    }
}

image.png

このように、コンストラクタは基本クラスから順に呼び出される事になります。


抽象 abstract

抽象メソッド(abstract method)は、派生クラス毎に個別定義するメソッドになります。
以前の再定義するオーバーライドや多重定義するオーバーロードとは違います。
抽象メソッドは仕様(規格)であり中身は記述せず、実装クラスにて初めて記述します。

使用には、アクセス修飾子の後ろにabstractをつけ、クラス名の前にもabstractをつけます。
派生クラスでは、その仕様(規格)にそった個別定義ができます。

※基本クラスでの使用の場合、実装クラスで必ずoverrideにする。

基本的なコード例をここに示します
namespace Abstracting
{
    abstract class MyAbst// 抽象クラス&基本クラス
    {
        public abstract double Discri(double a, double b, double c);
    }

    class MyDscri : MyAbst// 派生クラス
    {
        public override double Discri(double a, double b, double c)// 必ずoverride
        {
            return Math.Pow(b, 2.0) - 4.0 * a * c;
        }
    }

    class Abst
    {
        public static void Main()
        {
            MyDscri md = new MyDscri();// 代入用インスタンス
            double d = md.Discri(1.0, 2.0, 3.0);// 代入
            Console.WriteLine(d);
        }
    }
}

image.png

このように、abstractしてれば異なる処理も個別に設定しリターンできる。


分割定義 partial

クラスの分割定義

クラスの分割定義は、クラスが肥大化した時に分割する事でパフォーマンスが向上します。
例えば、自動生成されるコードを処理したり何らかのバグでコードが変更された場合も分散により最小限の被害に抑えられます。
またクラスの分割定義は、他クラス、他ファイルをまたいで分散させる事もできます。

使用には、分割するクラスの頭にpartialをつけます。

基本的なコード例をここに示します
//File1.cs 内:
namespace PC
{
    partial class A
    {
        int num = 0;
        void MethodA() { }
        partial void MethodC();
    }
}

//File2.cs 宣言内:
namespace PC
{
    partial class A
    {
        void MethodB() { }
        partial void MethodC() { }
    }
}

メソッドの分割定義

メソッドも分割定義できます。但しpartial class内に限ります。
また、暗黙的にprivateになります。
使用には、分割するメソッドの頭にpartialをつけます。

基本的なコード例をここに示します
namespace PM
{
    partial class A
    {
        partial void Am(string s);
    }

    partial class A
    {
        partial void Am(String s)
        {
            Console.WriteLine($"Am: {s}");
        }
    }
}



//--
partial class Earth : Planet, IRotate { }
partial class Earth : IRevolve { }
// これらは、次の宣言と等価です。

class Earth : Planet, IRotate, IRevolve { }

このように、コードをまたいで分割定義できます。


2
0
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0