C#でpublicとprivateを使ってカプセル化をする方法

前回の記事ではオブジェクト指向における最重要概念である「カプセル化」のやり方を簡単に見ていきました。この今回はカプセル化を実現する上で必ず必要になってくるアクセス修飾子・publicとprivateを、オブジェクト指向の観点から見直してみていきます。単純に「公開範囲」を設定するだけではなく、「意図がある」ということを知ってもらえればなと思います。

オブジェクト指向で重要なのは「まとめること」ではありますが、それと同じくらい重要になるのが「外部公開する範囲」になります。これを管理するのがアクセス修飾子なのですが、どうして要素や処理の公開範囲を決める必要があるのか、そうした点にも踏み込みたいと考えています。

publicとprivateの概要

まずはアクセス修飾子である「public」と「private」について簡単に解説していきます。publicというアクセス修飾子はクラスを構成するプロパティ・フィールド・メソッドなどをクラスの外側からも参照・設定できるようにすることができます。特定のアクセスを制限することなくクラスの要素を操作できるようになる公開設定です。それに対してprivateは、修飾子を付与したクラス内からの参照・設定しか許さず、クラス外からは操作することができないようにする修飾子になります。

アクセス修飾子としては、他にも継承したクラスしか参照させない設定(protected)や同じdllからしか参照を許さない設定(internal)などがありますが、まずは最大と最小の公開範囲であるpublicとprivateを押させておくことが重要になります。

アクセス修飾子の意味

アクセス修飾子を技術的に解説すると「単にクラス内外からの操作可否を設定するだけ」になってしまいますが、オブジェクト指向におけるアクセス修飾子は重要な意味を持っています。オブジェクト指向は「まとめる」と「公開範囲」が重要になると説明してきましたが、この「公開範囲」は基本的にアクセス修飾子で制限することになります。

そうした中でアクセス修飾子が持つ意味合いは、オブジェクト指向においては重要になります。オブジェクト指向では機能などをクラスなどにまとめて、「部品(オブジェクト)」として提供できるようにする設計手法になります。部品として再利用可能を目指す以上、「知る必要のないことは知らなくていい」というスタンスが必要です。

以前、これはリモコンで例えていますが、「ボタン」か「本体の中」かを分けるのがアクセス修飾子の役割になるということです。いうなれば、publicを設定したものは「ボタン」として外側からの操作を許容し、privateが付与されているものは「本体」として内側でひっそりと処理をする黒子的な役割に徹することを示唆しているのです。

publicの持つ意味

クラスの構成要素にpublicを付与した場合、それはどのような意味を持っているのでしょうか。それは「外から使用して欲しい」という「外部へのメッセージ」であると捉えられます。publicにしている、ということは「クラス外部から使用されることを前提」として実装されているということになります。

public class Calculation
{
    public void Add(int input)
    { /* 処理は省略 */ }
}

このようなメソッドがあるとします。このAddというメソッドは「外部から操作されること」を前提に用意されています。クラスの外側から安心して使ってもよいという暗黙のメッセージを持っているということになります。

privateの持つ意味

クラスの構成要素にprivateを付与するということは、クラス外部から見えてほしくないし、操作してほしくもないという暗黙のメッセージになります。定義したクラスの外部から操作されるというのは、思わぬ影響を与えてしまう可能性があります。例えば予期せぬ所から値が設定されていたり、クラス専用に作成したのに別のクラスでも使用されていたというのはバグの元になります。

public class Calculation
{
    private int total;
}

としている場合は「total」という変数は外側から参照させたくないという意思でもあります。それは同時に、クラスの外から値が設定されるのではなく、クラスの内側からのみの変更を許容するという意味になります。

public class Calculation
{
    private int total;
    public void Add(int input)
    {
        total += input;
    }
}

上記のようにしている場合、totalはAddメソッドを通じてのみ影響が及ぼされるので、影響範囲が掴みやすく余計な処理が入り込む余地がありません。しかしながら以下のようにした場合はどうなるでしょうか。

public class Calculation
{
    public int total;
    public void Add(int input)
    {
        total += input;
    }
}

上記のように定義してしまうと、クラス外部からの変更とAddメソッドの変更の両方の影響を受けることになります。例えば以下のような感じです。

public class Service
{
    public void main()
    {
        var calc = new Calculation();
        calc.total = 100;
        calc.Add(50);
    }
}

このように記述できるようになると、totalの変更を許容する範囲がグッと広がってしまい、どこでtotal変数が使用されているか分かりづらくなりますね。それに外部からtotalに値をセットされると、これまでに行われてきた計算結果が上書きされてしまいます。

こうなってしまうと、一つの変更が巨大な範囲に影響を及ぼす可能性を許してしまうため、修正による影響が膨大となりテスト工数や思わぬデグレなどを発生させてしまうかもしれません。

そうならないように、totalはprivateのままとして外部の公開を許さないようにするべきなのです。totalは内部変数ですので、外部にどうしても公開したい場合は参照だけを許すようなプロパティにしたり、値を返却するメソッドを作成するといった方法が考えられます。

影響範囲を小さくする = カプセル化

これまでに解説してきた通り、オブジェクト指向におけるpublicやprivateはとても重要な役割を担っています。必要な部分、影響が限定される部分だけ外部に公開して、公開する必要のないものなどは「可能な限り公開しない」という思想です。こうした思想のことを「カプセル化」と呼んでいます。

クラスを殻のようなイメージとして必要なモノだけを外部に公開して、それ以外は隠してしまうのです。このメリハリの聞いた実装技法・手法をカプセル化としており、オブジェクト指向を支える重要な概念として提唱されています。

プログラミングに慣れる前の初心者の段階では、外部からの公開を制御する意味が分かりませんでした。私自身も「なんでもかんでもpublicにしてしまえばいいじゃん」と思っていましたし、新人研修の時の課題アプリはほとんどpublicで書いていた記憶があります。しかしながら、少しづつ案件に参画していったり、プログラミングを勉強していく中で「影響範囲」を考えることは重要だと気づきました。

それらを体現して、より変更に対して柔軟に対応できる手段が「カプセル化」ということになります。オブジェクト指向では「再利用可能な部品」として実装することに重きをおいていますが、それは言い換えると「他に与える影響が少なくなるように実装する」ということでもあります。したがってカプセル化を理解することは重要になるのです。