オブジェクト指向設計 PR

C#でインタフェースを使ったオブジェクト指向設計の実装方法を解説!

記事内に商品プロモーションを含む場合があります

オブジェクト指向設計で大切な3つのこと「カプセル化」「インタフェース」「継承」とありますが,その中でも一番大切なものインタフェースです。

インタフェースの使い方は少しクセがあり,初心者の方には一番難しく感じるテーマかもしれませんが,慣れてしまえば,それほど難しい技術ではありません。

ここでは,インタフェースの使い方がよくわからないという方向けに,実装方法と,使いどころを解説して行きます。

オブジェクト指向設計のインタフェースとは?

オブジェクト指向設計の中に出てくる「インタフェース」とは,実装を強制する規格を定義するものです。

家の中にある電気のコンセントも,ちゃんと規格があって,メーカーごとにばらばら何てことないですよね?

メーカーごとにばらばらだと,家電ごとにさせるコンセントとさせないものが出てきます。
そうならないために,コンセントの規格があって,どんなメーカーの家電も,家のコンセントに挿せるようになっています。

オブジェクト指向設計のインタフェースも同じです。
ある事柄に関する決め事を定義しておいて,その規格に定まっていない実装ができなくなるように強制する力のある実装方法なのです。

インタフェースの使い方 実装例1

次のような画面を実装することを想定してみましょう。
3つの会員区分(シルバー,ゴールド,プラチナ)があって,
計算ボタンを押下すると「数値入力エリア(1000となっている)」に一定の割引処理をして結果ラベルに表示するという仕様にしてみます。

会員区分ごとの割引率は次のとおりとしておきます。

シルバー 0%
ゴールド 20%
プラチナ 40%

インタフェースを使わない場合

インタフェースを使用せずに,画面プログラムに処理をベタ書きすると次のような感じになります。

    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void ExecutionButton_Click(object sender, EventArgs e)
        {
            var value = Convert.ToInt32(InputTextBox.Text);
            if(SilverRadioButton.Checked)
            {
                this.ResultLabel.Text = value.ToString();
            }
            else if (GoldRadioButton.Checked)
            {
                this.ResultLabel.Text = (value * 0.8f).ToString();
            }
            else if (PlatinumRadioButton.Checked)
            {
                this.ResultLabel.Text = (value * 0.6f).ToString();
            }
        }
    }

これでもとりあえず問題なく動作します。

インタフェースを使った場合

インタフェースを使った例を次に示します。

    public interface IMember
    {
        float Rate { get; }
    }

    public sealed class SilverMember : IMember
    {
        public float Rate => 1.0f;
    }

    public sealed class GoldMember : IMember
    {
        public float Rate => 0.8f;
    }

    public sealed class PlatinumMember : IMember
    {
        public float Rate => 0.6f;
    }

IMemberというインタフェースを作って,そこに掛け率である「Rate」プロパティを作りました。 これでIMemberインタフェースを実装するクラスは,必ず「Rate」プロパティを実装する必要があります。 「SilverMember」「GoldMember」「PlatinumMember」のそれぞれのクラスはIMemberを実装しているため,「Rate」プロパティを実装しており,それぞれの会員区分にあった割引率を返すようにしています。

    public partial class Form2 : Form
    {
        public Form2()
        {
            InitializeComponent();
        }

        private void ExecutionButton_Click(object sender, EventArgs e)
        {
            var value = Convert.ToInt32(InputTextBox.Text);

            IMember member;
            if(SilverRadioButton.Checked)
            {
                member = new SilverMember();
            }
            else if (GoldRadioButton.Checked)
            {
                member = new GoldMember();
            }
            else
            {
                member = new PlatinumMember();
            }

            ResultLabel.Text = (value * member.Rate).ToString();
        }
    }

画面側は,チェックボックスの状態で切り分けるのは,先ほどのインタフェースを使用しない例と同じですが,直接テキストに値を設定するのではなくIMember変数を宣言し,「SilverMember」「GoldMember」「PlatinumMember」のいずれかのクラスが生成されて「member」変数に格納されるようになっています。 最後の行の「ResultLabel.Text = (value * member.Rate).ToString();」では,「member」にどの会員区分が設定されたかはわかりませんが,必ずRateプロパティを持っていることが保障されているため,このようなコーディングをできるようになっています。 この段階で会員区分にあった割引率を取得し,計算結果をResultLabelに表示しています。

新人
新人
インタフェースを使った例の方がコード量が多くて読みづらいだけだと思います。最初の例の方がシンプルでいいんじゃないでしょうか?

アンダーソン
アンダーソン
確かにこの単純な例だとインタフェースを使用する旨みはないですね。オブジェクト指向設計はどれもそうですが,処理が複雑化するほど効果を発揮します。次の有効的な利用方法を参考にしてみてください。

有効的な利用方法

先ほどの例は単純すぎて,インタフェースを使う意味がわかりづらかったかも知れません。ただ,インタフェースの使い方(書き方)を解説するには,シンプルなほうがよいかと思いそうしました。今回は少しだけ処理を複雑にしてみましょう。

仕様

先ほどはひとつの画面に会員区分と計算処理がありましが,今回はそれを分割します。ログイン画面で「会員区分」を選択し,その後の画面で金額を入力し,「割引後の値段」を表示するようにしてみましょう。

    public static class LoginInfo
    {
        private static IMember _member;

        public static IMember Member
        {
            get
            {
                if(_member == null)
                {
                    return new SilverMember();
                }

                return _member;
            }

            set
            {
                _member = value;
            }
        }
    }

LoginInfoクラスはStatic(静的)な値を保持するクラスとして,アプリケーションのどこからでも参照できるクラスとして,IMemberを保持します。

    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void ExecutionButton_Click(object sender, EventArgs e)
        {
            if (SilverRadioButton.Checked)
            {
                LoginInfo.Member = new SilverMember();
            }
            else if (GoldRadioButton.Checked)
            {
                LoginInfo.Member = new GoldMember();
            }
            else
            {
                LoginInfo.Member = new PlatinumMember();
            }

            using (var f = new Form2())
            {
                f.ShowDialog();
            }
        }
    }

ログイン画面は入力された会員区分をLoginInfoクラスのMemberに設定し,計算画面を表示します。

    public partial class Form2 : Form
    {
        public Form2()
        {
            InitializeComponent();
        }

        private void ExecutionButton_Click(object sender, EventArgs e)
        {
            var value = Convert.ToInt32(InputTextBox.Text);
            ResultLabel.Text = (value * LoginInfo.Member.Rate).ToString();
        }
    }

計算画面はLoginInfoを参照し,ログインされている会員区分で計算処理をします。 どうでしょうか?こういった使い方なら,最初のログイン画面で会員区分が設定されて以降,すべての処理はその会員区分の情報で処理することができますから,あらゆる画面で,同じ思想で値引き処理が行われることになります。

まとめ

インタフェースはコンセントと同じで,ある決まった使い方をする種類ごとに定義して,実装を強制する。 同じような特性を持ち,それぞれに異なる処理をする場合はインタフェース越しに処理を行う。 よくあるのはデータベースとの接続部分だったりします。SQLServeとOracleどちらにも対応したり,ダミーデータとの差し替えを可能にしたりする使い方はよくします。

オブジェクト指向
Udemyで販売しているC#のコースを 1つプレゼントします!