- 投稿日:2021-04-03T20:50:06+09:00
継承って、どうやって使うの?
継承って難しい?
オブジェクト指向言語でプログラミングしていると、何がと出くわす「継承」という言葉。継承の概念は勉強はしたことがあるという人は多いとは思いますが、実際のソースコード上でどう生かしていけばよいかわかりずらいものです。
しっかりと継承の機能を使えれば、オブジェクト指向のメリットを享受できるものですが、どの場面でどう実装していけばよいか、自分の中に落とし込むのにはなかなか難しい、、、そのような人のために、私のつたない知識ではありますが、ちょっとでも「継承」について理解の橋渡しができれば、と思っています。
まずは定義から
よくある定義はこちら
継承(けいしょう、英: inheritance、インヘリタンス)とはオブジェクト指向を構成する概念の一つである。あるオブジェクトが他のオブジェクトの特性を引き継ぐ場合、両者の間に「継承関係」があると言われる。
主にクラスベースのオブジェクト指向言語で、既存クラスの機能、構造を共有する新たなクラス(派生クラス、派生型)を定義すること(subclassing、サブクラス化)ができる。またそのような派生クラスは「親クラス(スーパークラス)を継承した」という。具体的には変数定義(フィールド)や操作(メソッド)などが引き継がれる。またJavaやC#などのインタフェース実装のように、機能セットの仕様(プロトコル)のみを引き継ぐ場合もある。
一般的に、BがAを継承する場合、"B is a A."(BはAの一種である)という意味的な関係(Is-a関係)が成り立つ。従って、同じふるまいを持つからと言って、意味的に無関係なクラス間に継承関係を持たせるのは適切でない場合が多い。
「継承関係」にあるクラス、「Is-a関係」にあるクラスというのは、よく聞く話。確かに、フィールドやメソッドを継承できるのだから、同じ処理を書かずに処理を利用することはできそうですね。
処理が一か所にまとまっているのだから、共通の処理に何か変更があった場合には、そこを修正するだけで済むというメリットも生まれます。うん、何かに役立ちそうだ。あと、よく出てくるのが、下の図。
犬も猫も動物なので、どちらも食べるし歩く。ということを表現している図になります。確かに、「動物」クラスに
食べる()、歩く()というメソッドがあるので、犬クラスを呼んでも、猫クラスを呼んでも、どちらでも2つのメソッドを使うことができます。、、、、
それでは、ここまでの知識で実装してみましょう!
となったとき、コードに落とし込めるでしょうか?なかなか難しいですよね。ここがオブジェクト指向の難しいところだとつくづく感じます。そう、概念だけを理解しても、わからないものなのですねぇ、、
どういうときにこの考え方が有効なのかを知って、体験して、試行錯誤することで身につくものなのかなとも思います。継承を使うといい場面
さて、継承を使うタイミングはどういうときでしょうか?それは、共通処理があり、それらを一か所にまとめておきたい場合です。
「それってさっき言っていましたよね?」
そうです。もう一度言いました。
継承を使う理由はこれに限ると思うのです。でも、単に処理をまとめたいというよりは、一連の似たようなの処理の流れがあるが、一部分に個別の処理が挟まれている場合に効果を発揮します。
たとえば、次のような図の時です。
パターンAという処理のほかに、パターンBという処理の流れが非常によく似たパターンがあるとします。大まかには同じなのですが、2番目と4番目の処理をパターン別に違った処理をさせたいのです。継承を使わなくても書けませんか?
普段業務ロジックを書かれている読者の方々にはこのような場面に出くわしたことがあるのが多いのではないでしょうか。この図の処理を、単純に実装しようとすると次のようになるかと思います。
継承を使わない実装方法public class NoInheritance { public void executePattern(Pattern pattern) { System.out.println("共通処理1"); if (pattern == Pattern.PATTERN_A) { System.out.println("個別処理2-1"); } else if (pattern == Pattern.PATTERN_B) { System.out.println("個別処理2-2"); } System.out.println("共通処理3"); if (pattern == Pattern.PATTERN_A) { System.out.println("個別処理4-1"); } else if (pattern == Pattern.PATTERN_B) { System.out.println("個別処理4-2"); } System.out.println("共通処理5"); } } enum Pattern { PATTERN_A, PATTERN_B }処理も1つのクラスにまとまっていて頭の考えていることを簡潔に言い表しているのではないのでしょうか。確かに、
new NoInheritance().executePattern(Pattern.PATTERN_A);
と実行すれば共通処理1
個別処理2-1
共通処理3
個別処理4-1
共通処理5となり、
new NoInheritance().executePattern(Pattern.PATTERN_B);
と実行すれば共通処理1
個別処理2-2
共通処理3
個別処理4-2
共通処理5ということが確認できます。期待通りの処理ができていますね。
このくらいのコード量からすれば、見通しやすいですし、見る場所も一か所で済んでいますので問題がないようにも感じます。
しかし実際には、共通処理や個別処理が十数行・数十行にわたってしまうことがほどんどです。そうなると途端に見通しがわくるなるのが必至です。「処理が多いのであれば、共通処理として別メソッドに書き出せばよいのではないですか?」
そう聞こえてきそうです。よくある手法としては、共通ユーティリティクラスを作り、処理を委嘱します。
共通ユーティリティクラスを使用public class UseUtil { public void executePattern(Pattern pattern) { CommonUtil.commonProcess1(); if (pattern == Pattern.PATTERN_A) { System.out.println("個別処理2-1"); } else if (pattern == Pattern.PATTERN_B) { System.out.println("個別処理2-2"); } CommonUtil.commonProcess3(); if (pattern == Pattern.PATTERN_A) { System.out.println("個別処理4-1"); } else if (pattern == Pattern.PATTERN_B) { System.out.println("個別処理4-2"); } CommonUtil.commonProcess5(); } } class CommonUtil { public static void commonProcess1() { System.out.println("共通処理1"); } public static void commonProcess3() { System.out.println("共通処理3"); } public static void commonProcess5() { System.out.println("共通処理5"); } } enum Pattern { PATTERN_A, PATTERN_B }
CommonUtilクラスを作成することによって、処理をうまく共通化できたようにも思えます。もし、共通処理の中身が増えたとしても、executePattern()メソッドの中身は増えません。
しかし、この書き方にも問題点があります。
- オブジェクト指向の考え方に、「共通ユーティリティ」クラスを作るということはなじまない
- このままですと、
CommonUtilクラスはUseUtilクラス以外にも呼び出せてしまうので、スコープが大きくなり、もしほかのクラスからCommonUtilクラスのメソッドを呼び出すようなことがあれば、途端にメンテナンス性が低くなります。(簡単に修正できなくなる)UseUtilクラスに対するCommonUtilクラスの依存性が高くなっている
- 「CommonUtil」と書いておきながら、
UseUtilクラス専用の処理をしているので、一つ一つのメソッドを見たときに汎用性があまりない。- (確かに、
CommonUtilクラスをパッケージプライベートにしたり、それぞれのメソッドをUseUtilクラスに移してprivate化すればある程度スコープが小さくなりいい実装には近づきます。)
さらに、次のような問題も発生します。例えば、さらに似たような
パターンCという処理が加わったとします。よし、同様に追加すればよいのだなということで、下記のような実装にしました。
パターンCを追加しようとしましたpublic class AddPatternC { public void executePattern(Pattern pattern) { System.out.println("共通処理1"); if (pattern == Pattern.PATTERN_A) { System.out.println("個別処理2-1"); } else if (pattern == Pattern.PATTERN_B) { System.out.println("個別処理2-2"); } else if (pattern == Pattern.PATTERN_C) { System.out.println("個別処理2-3"); } System.out.println("共通処理3"); if (pattern == Pattern.PATTERN_A) { System.out.println("個別処理4-1"); } else if (pattern == Pattern.PATTERN_B) { System.out.println("個別処理4-2"); } // あれ、Pattern.PATTERN_Cは?? System.out.println("共通処理5"); } } enum Pattern { PATTERN_A, PATTERN_B, PATTERN_C }ためしに、
new AddPatternC().executePattern(Pattern.PATTERN_C);を実行してみると、共通処理1
個別処理2-3
共通処理3
共通処理5となります。
、、、
何か気づきませんか?そうです、バグが発生しています。
図からすれば、「共通処理3」と「共通処理5」の間に「個別処理4-3」表示されてほしいところですが、いざ実行してみると表示されていませんでした。このようなことになってしまったのはなぜなのでしょうか?「ああ、一つif文を書き忘れたのね」
そのように片付けるのも簡単です。しかし、このような分岐処理が複数ありしかも数百行・数千行のコードである場合、すべてに気づいて対処しきれるでしょうか?コードを書くのも人間ですし、テストをするのも人間です。しかも開発する際には大人数で行うことがほとんどなので、必ずこのような実装ミスは発生します。
実装のミスに気付かず、本番環境にリリースしてしまった場合、お客様に指摘されてはじめて気づく、データがおかしくなり復旧に時間がかかってしまう、など余計な工数もかかることになりプロジェクトにも大きな影響をもたらす可能性があります。実装のミスを防ぐための手法としての「継承」
このような実装のミスを防ぐためにはどうしたらよいでしょうか?その一つの答えが「継承を使う」なのです。
「でも、継承って親クラスのメソッド・フィールドが使えたり、メソッドをオーバライドして処理を変更したりすることができることだと思います。どうして実装ミスを防ぐことができるのでしょうか?」
そうですね、そう疑問に思われるのもわからなくありません。通常オブジェクト指向の継承を習う際には、継承によって得られる「機能」を教えられるのだと感じます。そのため、継承の考え方をいったん変えましょう!
それでは、お待たせしました。実装方法について紹介します。
方法1:処理の流れを親クラスに書く
Javaには、
abstractというキーワードがあります。classをabstractにすると、そのクラスだけではインスタン生成することができず、必ずextendsする必要が出てきます。これを利用して書くと次のようになります。継承を使った方法abstract class UseInheritance { public void executePattern() { System.out.println("共通処理1"); this.individualProcess2(); System.out.println("共通処理3"); this.individualProcess4(); System.out.println("共通処理5"); } abstract void individualProcess2(); abstract void individualProcess4(); } class PatternA extends UseInheritance { @Override void individualProcess2() { System.out.println("個別処理2-1"); } @Override void individualProcess4() { System.out.println("個別処理4-1"); } } class PatternB extends UseInheritance { @Override void individualProcess2() { System.out.println("個別処理2-2"); } @Override void individualProcess4() { System.out.println("個別処理4-2"); } }実行の仕方は、
new PatternA().executePattern();
new PatternB().executePattern();
となります。(実行結果は同じなので割愛)ちなみに、クラス図を描くと以下のようになります。
継承を使わない場合と見比べてみてください。どのような変化があるでしょうか?
- 個別処理が親クラスに一切かかれていない
- パターンごとにクラスになっている
- enumがなくなっている
- オーバライドを利用して個別処理を実現している
などなど、いろいろな発見があるかと思います。
実は、このような実装方法は、GoFのデザインパターンの「Template Methodパターン」と呼ばれるものです。気になる方は調べてみてください。
このような実装にするメリットは何でしょうか?それは、パターンを増やしたときに気づくことでしょう。同様に
PatternCを作ってみます。パターンを増やしましたclass PatternC extends UseInheritance { @Override void individualProcess2() { System.out.println("個別処理2-3"); } }ただし、コンパイルしようとした時、エラーが発生してしまいました。
エラー: PatternCはabstractでなく、UseInheritance内のabstractメソッドindividualProcess4()をオーバーライドしません class PatternC extends UseInheritance { ^エラーの内容を見ると、
individualProcess4()が抽象メソッドなのに、オーバライドしてないのでしなさいと言っています。
お分かりの通り、この時点で実装ミスに気づくことができるのです。
abstractキーワードを使うことで、個別に実装する範囲を明示していることで、パターンを書き足す人が(abstractクラスを実装した人とは違うかもしれません)何をしなくてはならないかが明確になります。さらに、次のような特徴に気づきませんでしょうか。
- if文が取り除かれているので、バグが発生しにくくなっている
- 親クラスを見ることによって、大まかな処理の流れが把握しやすい
- 子クラスを見ることによって、パターンごとの処理・違いが把握しやすい
このような特徴というのは、開発者目線からするととてもメリットが高いと思います。
このようにして、バグの発生を減らし、効率的に開発を進めていきしょう。方法2:インターフェイスを使用して、違いを吸収する
上記で示した、「Template Methodパターン」以外にも、実装方法があります。それは、
interfaceというキーワードです。インターフェイスを使用した方法class UseInterface { public void executePattern(Pattern pattern) { System.out.println("共通処理1"); pattern.individualProcess2(); System.out.println("共通処理3"); pattern.individualProcess4(); System.out.println("共通処理5"); } } interface Pattern { void individualProcess2(); void individualProcess4(); } class PatternA implements Pattern { @Override public void individualProcess2() { System.out.println("個別処理2-1"); } @Override public void individualProcess4() { System.out.println("個別処理4-1"); } } class PatternB implements Pattern { @Override public void individualProcess2() { System.out.println("個別処理2-2"); } @Override public void individualProcess4() { System.out.println("個別処理4-2"); } }実行の仕方は、
new UseInterface().executePattern(new PatternA());
new UseInterface().executePattern(new PatternB());
となります。(実行結果は同じなので割愛)クラス図を描くと以下のようになります。
これは、GoFのデザインパターンの「Stateパターン」と呼ばれるものです。
先ほどの「Template Methodパターン」との違いとしては、
- 個別処理を実装するメソッドの定義が、親クラスではなくインターフェイスになっている
- 処理の流れが書いてある
executePattern()メソッドを実行するためには、個別処理の書かれた処理を引数に入れる格好となっているとなっています。この2つの実装方法には、優劣をつけるものではないとは思っていますが、こちらの実装方法は、
UseInterfaceクラスとPatternA・PatternBクラスとの間に依存性がなくなっているという特徴があります。
依存性が低いことは、それぞれのクラスが変更しやすくなるということになるので、よりいい実装になっているということも言えるのではないでしょうか?さいごに
いかがだったでしょうか?「継承」を使うメリットを感じられましたでしょうか?ほう、「まだ実際に業務内で書いたことがないからわからないなぁ」。そうですよね、数をこなさないと上達していきません。これは長い道のりになりそうだ、、、
自身でコードを書くときに、「これは、継承を使った方が見通しが良いな」と気づくようになるためには、とにかくわからないなりにも使ってみるのも一つの手だと思います。
だって、バグが出るのは自分にとっても周りにとっても嫌ですもの、どんどん取り入れていきましょう。
- 投稿日:2021-04-03T19:36:28+09:00
Java Interceptorの有効化
JavaEEのインターセプターを有効にする方法について
インターセプターが有効になっていないなーと思ったら、Priorityアノテーションを忘れていました。
調べるとbeans.xmlで設定する方法もあるようで、違いはなんだろうと思ったのですけど、特にないようですね。
beans.xmlで定義するとソースコードを修正せずに有効にできたり無効にしたりできる程度ですね。方法は2つあります
- interceptorに@Priorityアノテーションをつける
- beans.xmlにinterceptorを定義する
1. Interceptorに@Priorityアノテーションをつけるサンプル
TraceLogInterceptor.javapackage sample; @InterceptorBinding @Target({Element.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface TraceLogInterceptor { }TraceLogImpl.javapackage sample; @Priority(Interceptor.Priority.APPLICATION) @TraceLogInterceptor @Interceptor public class TraceLogImpl { }2. beans.xmlにInterceptorを定義するサンプル
beans.xml<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/beans_2_0.xsd" bean-discovery-mode="annotated"> <interceptors> <class>sample.TraceLogInterceptor</class> </interceptors> </beans>
- 投稿日:2021-04-03T12:00:47+09:00
【メモ】eclipseからxamppのmariadbに接続する手順
1.Why? java with MariaDB
前回、javaでmvcアプリを作成した。
この段階から、crudアプリを作成できるようになりたかった。
なぜjavaからmariadbに接続するかは下記の理由があった。
(1)PHPで簡単なcrudアプリで作るのに、xamppをインストールしていた。
(2)とりあえず今あるxamppのDBを使って、javaでcrudアプリを作れるか試してみたいさらにxamppのコンパネにMySQLと書いてあるのでmysql-connector-xxx.jarを追加していたが実行すると接続エラーが出て動かない。
⇒
「mariadb」のコネクタが必要だったことが後にわかった。2.環境
xamppのバージョン:7.2.3
eclipseのバージョン:Version: 2019-12 (4.14.0)
mariadbのバージョン:10.1.31
java:11
OS:windows10 home 64bit3.eclipseでの設定
1).DB接続の必要なプロジェクトを右クリックしてビルド・パスを選択
2).javaのビルド・パスで「ライブラリ」タブを選択し、クラスパスを
選択した状態で「外部JARの追加(x)」ボタンを押下。3).下記サイトでダウンロードしていたmariadb-java-client-xxx.jarを選択して
「開く」ボタンを押す4).ライブラリのクラスパスにjarファイルが反映されていることが確認して、
適用して閉じるボタンを押下する4.ソースでの実装例
public Boolean updateData() { //pokemondbはデータベース名 String URL = "jdbc:mariadb://localhost:3306/pokemondb?useUnicode=true&characterEncoding=utf8mb4"; String USER = "root"; String PASS = ""; try(Connection conn = DriverManager.getConnection(URL, USER, PASS)){ conn.setAutoCommit(false); //DB処理 } catch (Exception e) { e.printStackTrace(); return false; }finally { System.out.println("処理が完了しました"); } }
- 投稿日:2021-04-03T00:26:27+09:00
[学習内容のまとめ]Javaのデータ型について
Javaのデータ型の種類
- 基本データ型(プリミティブ型)
- 参照型(リファレンス型) 以上の2つに分けられる。
基本データ型(プリミティブ型)について
基本データ型には数値、文字(文字と文字列は違うので注意)、真偽値を代入できる。
char, int, long, float, double, booleanが該当する。参照型(リファレンス型)について
参照型は、インスタンスへの参照。
種類としては、
- 配列型
- クラス型
- インターフェイス型
が挙げられる。文字列を扱うStringはクラス型(Stringクラス)である。Stringクラス以外にも、Dateクラスなどがある。[補足]
char型の変数には整数が代入できる。文字には番号が振られて管理されているため(文字コード)。
public static void main(String[] args){ char c1 = 97; System.out.println(c1); // 実行結果: a char c2 = '\u0061'; //unicodeに従ったときのa System.out.println(c2); // 実行結果: a // bの文字コードを確認する char c3 = 'b'; int c4 = (int)c3; //数字型に変換する System.out.println(c4); // 実行結果: 98 }



