👌

getter/setterはなぜカプセル化?

2023/07/12に公開

はじめに

この記事は説明をする!というわけではく私が思った疑問を書き綴った記事になります。
なので、TechではなくIdeaに分類させていただきます。

とはいえ、Techな話をするなというわけではありませんので
何か間違いや気になったことがあればお気軽にコメントしていただければと思います。

対象読者

私が辿り着いた答え

getter/setterがカプセル化というわけではなく、getter/setterはカプセル化の一部である。

getter/setterってなんでいるの?

私がgetter/setterという言葉を知ったのは大学でJavaの授業を受けていた時です。

User.java
package jp.co.test.beans;

public class User {
	
	// フィールド(private修飾子のため、外部からアクセスできない)
	private String name;
	
	// アクセスするには、下記getter/setterを使用する
	
	// getter(name)
	public String getName() {
		return name;
	}

	// setter(name)
	public void setName(String name) {
		this.name = name;
	}	
}
Main.java
package jp.co.test.hello;

import jp.co.test.beans.User;

public class Main {

	public static void main(String[] args) {
		User user = new User();
		user.setName("たぬきむーん");
		user.getName();
	}

}

この時思ったことは、隠蔽(カプセル化)っていうけど、
publicメソッドで挟んでいるだけなら誰でもアクセスできるやん!でした。

下記にようにフィールド変数をpublicにしても同じじゃない?そう思いました。

User.java(フィールドをpublicに変更)
package jp.co.test.beans;

public class User {

	// フィールド(public修飾子なので誰でもアクセスできる)
	public String name;	
}
Main.java
package jp.co.test.hello;

import jp.co.test.beans.User;

public class Main {

	public static void main(String[] args) {
		User user = new User();
		user.name = "たぬきむーん";
	}

}

テンプレートメソッドを知る

以前「抽象クラス」と「インターフェース」を学習した際にまとめた記事を書きました。
https://zenn.dev/tanukimoon/articles/2014e280ceae2c
この時、私はテンプレートメソッドパターンを知りました。
https://ja.wikipedia.org/wiki/Template_Method_パターン

下記は簡単な例となります。

AbstractHuman.java(親クラス)
package jp.co.test.service;


public abstract class AbstractHuman {
	
	// 全人類共通の1日の振る舞い
	public void dayProcess() {
		morningProcess();
		dayProccess();
		freeTimeProcess();
		nightProcess();
	}
	
	abstract void freeTimeProcess();
	
	private void morningProcess() {
		System.out.println("おはようございます!");
	}

	private void nightProcess() {
		System.out.println("おやすみなさい~");
	}
		
	private void dayProccess() {
		System.out.println("仕事をします");
	}

}
TanukiMoonService.java(私のクラス)
package jp.co.test.service;

public class TanukiMoonService extends AbstractHuman{
	
	// 私は自由時間にゲームをするぞ!
	@Override
	void freeTimeProcess() {
		System.out.println("ゲームをするぞ");
	}

}	
YamadaService.java(山田さんのクラス)
package jp.co.test.service;

public class YamadaService extends AbstractHuman{
	
	// 私は自由時間にサッカーをするぞ!
	@Override
	void freeTimeProcess() {
		System.out.println("サッカーをするぞ");
	}

}	
Main.java
package jp.co.test.hello;

import jp.co.test.service.TanukiMoonService;

public class Main {

	public static void main(String[] args) {
		TanukiMoonService tanukiService = new TanukiMoonService();
		/**
		 * おはようございます!
		 * 仕事をします
		 * ゲームをするぞ
		 * おやすみなさい~
		 */
		tanukiService.dayProcess();
		
		YamadaService YamadaService = new YamadaService();
		/**
		 * おはようございます!
		 * 仕事をします
		 * サッカーをするぞ
		 * おやすみなさい~
		 */
		YamadaService.dayProcess();
	}

}

さて、テンプレートメソッドを実装したわけですが、
私が注目したのはクラスごとの見え方です。

今回「子クラス」[1]となる2つのクラスはpublic修飾子で定義したメソッドはありません。
では「親クラス」[2]はというと、「dayProcess()」メソッドのみがpublicで
それ以外のメソッドはabstractを除くと全てprivateです。

つまり、Main.javaで定義したServiceから視える(使うことができる)メソッドは
「dayProcess()」メソッドのみとなります。

これの何がいいの?というとMain.javaができる振る舞いを制限することができます。
例えば、下記のように自由時間の振る舞いをpublicにします。

自由時間の振る舞いをpublicに変更
package jp.co.test.db;

public class TanukiMoonService extends AbstractHuman{

	@Override
	public void freeTimeProcess() {
		System.out.println("ゲームをするぞ");
	}

}	

Main.java
package jp.co.test.hello;

import jp.co.test.service.TanukiMoonService;

public class Main {

	public static void main(String[] args) {
		TanukiMoonService tanukiService = new TanukiMoonService();
		// ゲームをするぞ!
		tanukiService.freeTimeProcess();
	}

}

あれれ?これじゃ私は仕事をサボってゲームばかりできちゃうようになりましたね。
つまり、外部から見て私が自由時間の振る舞いを呼び出すことは好ましくないですよね?
あ、じゃあできないしよう!!!(隠そう!!!)
ということがカプセル化の1つの大きな役割なのかと私は気づきました。

また、子クラスから見たdayProcess()メソッドもカプセル化なのかも?と思ってます。
というのも、子クラスから見て1日の振る舞いは何も考慮しなくていいからです。
唯一、自由時間の振る舞いを定義しないといけないのですが
抽象メソッドとして定義しているので意識せず実装することができます。

こういった子クラスや外部クラスから見て優しい(易しい)クラス設計が重要であり、
その設計を実装するためにカプセル化が重要なファクターになっているんだと思います。

カプセル化に少し触れて、getter/setterを振り返って・・・

話が少し逸れましたが、私はこういった出来事でカプセル化に触れお近づきになれた気がしています。

そして、改めてgetter/setterを振り返ってやっと分かりました。
こいつらは、カプセル化の一部なんだなと。

例として、最初に説明したユーザークラスに「パスワード」フィールドを追加します。

User.java(パスワードを追加)
package jp.co.test.beans;

public class User {
	private String name;
	
	private String password;

	
	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}	
}

パスワードはセキュリティの観点上どこでも使うことは好ましくないと思います。
そのために考えられることは幾つかあると思います。

getterを定義しない
setterのみを定義してgetterは定義しない方法です。

User.java(setterメソッドと認証メソッドを追加)
	public void setPassword(String password) {
		this.password = password;
	}
	
	public boolean isAuthority() {
		// 実際の振る舞いは別クラスで定義してデータを渡すのが無難?
		// いわばラッパークラスとして用意する。
	}

ラッパーメソッドを用意して出力する
setter/getterは定義するが、クラス内で振る舞いを限定する方法です。

User.java(setterメソッドと認証メソッドを追加)
	public void setPassword(String password) {
		this.password = password;
	}
	
	// 暗号化されたパスワードを取得
	public String getCipherPassword(){
		// フィールドのパスワードを暗号化して返却する
	}

このように実装することで必要以上にパスワードが外部で使用されることはなくなり、
振る舞いも限定することができました。

今回実装を考えて思ったことは「getter」メソッドは隠蔽しやすいなということです。
裏を返せば外部から隠したいとのことなのでなんでもかんでもlombokで@Data[3]せず
きちんとフィールドを精査して実装しないといけないなと思いました。

まとめ

私がずっと疑問に思っていたgetter/setterとカプセル化の関係について
少し理解できて良かったなと思います。

個人的に、getter/setterはカプセル化という教え方ではなく
カプセル化という概念があって重要な要素としてgetter/setterがあると教えていただければ
もう少し気づくことが早かったのかなぁとも思いました。

とはいえ、過ぎたことは仕方がないのでこれから働くにつれ
後輩と触れ合う機会も増えると思うので、その時に分かりやすく正しいコトを伝えられるように
これからも学習を続けて行きたいと思います。

長々となりますが、以上になります。
間違いはないという自信はぶっちゃけないので、ご指摘ありましたらぜひぜひお願いします!!!!!!

でわでわ

脚注
  1. TanukiMoonService,YamadaServiceのこと ↩︎

  2. AbstractHumanのこと ↩︎

  3. よくある構文をアノテーション(@マーク)を使うことで省略できる便利なやつ ↩︎

Discussion