独り言

プログラミングの講師をしています。新人研修で扱う技術の解説と個人の技術メモ、技術書の紹介など

【プログラミング全般】オブジェクト指向入門

ここでは様々なプログラミング言語でサポートされているオブジェクト指向について解説します。
オブジェクト指向は一言でいえば、モノ(オブジェクト)に着目したプログラミングの概念で、開発を効率よく行うために必要な考え方です。
オブジェクト指向をサポートしている言語としては、Java, PHP, Ruby, Python, C#, VB .NETなどがあります。
これだけではなく、他のも多くの言語でサポートされています。
90年代以降の作成されたプログラミング言語オブジェクト指向をサポートされているものが多く、最近の言語を学ぶ場合には避けて通れない技術です。

プログラミングの歴史

オブジェクト指向の説明に入る前にざっくりとプログラミングの歴史を振り返ります。

  • 機械語の時代
    0と1の2パターンでプログラミングをしていた時代。
    コンピュータの専門家の人しかできない。

  • アセンブリ言語の時代
    記号、アルファベットが使えるようになって機械語よりはましになった時代。
    けど、まだ簡単とは言えず、一部の専門家の人がプログラミングしていた(らしい)。

  • 高級言語の時代
    COBOLとかFORTRANの時代。
    英語の文法に近くなってかなり分かりやすく書けるようになった時代。
    アセンブリに比べて敷居は下がったが、GOTO文が乱用されており、プログラムの規模が大きくなるとメンテナンスが難しい。

  • 構造化言語の時代
    C言語など。
    順次・分岐・繰り返しというアルゴリズムの3要素で処理の流れを表現する。
    GOTO文がなくなり、メンテナンス性向上。

  • オブジェクト指向言語の時代
    構造化言語では、データを保持するのにグローバル変数という仕組みが使われていた。
    しかし、グローバル変数が多くなると、どこでどの変数が使用されているのかが分かりにくく、メンテナンスが難しくなった。
    その問題を解決するために生まれたのがオブジェクト指向
    データと処理をオブジェクトという概念で1つにまとめることで、プログラミングをしやすく、規模が大きくなってもメンテナンスがしやすくなった。

  • フレームワーク・ライブラリの時代
    オブジェクト指向の開発をさらに効率よく行うために、プログラムを再利用する動きが広まった。
    それがライブラリとフレームワーク
    ライブラリは、他の人が作成したプログラムを使うことで、面倒な処理や複雑な処理を自分で1から作成する必要がなくなる。
    フレームワークは、プログラムの半完成品みたいなもので、決められたルールに基づいてコーディングをすることでソースコードを書く量を減らし、効率を上げる仕組みです。
    フレームワークやライブラリが登場する中で、オブジェクト指向でよく使われる設計のパターンが出てきました。
    そのパターンを体系的にまとめたのがデザインパターンです。
    デザインパターンオブジェクト指向の考えを活かすために設計思想で、ライブラリやフレームワークの中に多く登場します。

  • ローコード・ノーコードの時代
    プログラミング言語ソースコードをほとんど書かずにプログラミングができる技術が流行している。
    ローコードは、少ないコード量で開発ができる技術。
    ノーコードは、全くコードを書かずして開発ができる技術。
    GUI操作でシステムを構築することができるため、非エンジニアでも開発ができることで注目を浴びています。

オブジェクト指向を学ぶメリット

この記事を書いている2020年現在、ローコード・ノーコードが大きな注目を浴びていて、今後も拡大していくことが予想されます。
ノーコードでは、プログラミング言語を学ぶ必要すらないため、今後の動向によってはオブジェクト指向を学ぶ意味は薄れてくるかもしれません。
そうでなくても、プログラミング言語を使って開発する場合、フレームワークを活用した開発がほとんど。
オブジェクト指向の仕組みを理解していなくても開発ができてしまう時代でもあります。
そんな時代にオブジェクト指向を理解する必要は果たしてあるのでしょうか。

今後もしかするとオブジェクト指向を学ぶメリットはなくなってくる可能性はありますが、フレームワークによる開発が主流であるうちは、まだオブジェクト指向を学ぶ意味はあると考えます。
なぜなら、フレームワークの中ではオブジェクト指向の仕組みが多く使われているからです。
オブジェクト指向について深く理解することができれいれば、フレームワークやライブラリの中身を理解することもできます。
そうなれば、フレームワークやライブラリを使用している中でエラーが発生したとしても、自分の力で解決できる可能性が高くなります。
また、フレームワークを自分で開発したり、既存のフレームワークを拡張することも可能になります。
一方、オブジェクト指向について理解が浅いと、フレームワークでエラーが発生しても中の仕組みが分からず、自分の力だけで解決することが難しくなります。

フレームワークやライブラリを使いこなし、トラブル解決も早いプログラマーになるにはオブジェクト指向の理解は不可欠になります。

オブジェクト指向の用語

オブジェクト指向でよく使用される用語について解説します。

オブジェクト

オブジェクトは日本語だとモノという意味です。
パソコンやスマホ、ペットボトルや机、イスなど、人間が認識することができるものは全てオブジェクトと言えます。
目に見えるもの、見えないものでも構いません。
コンピュータだと、ログインしているユーザー、商品、投稿、キャラクター、武器、など、人間が識別できるものがオブジェクトです。
用は何でもありですが、プログラミングをするときに人間にとって扱いやすい単位をオブジェクトとして扱うのがオブジェクト指向プログラミングです。

クラス

オブジェクト指向を学ぶ上で必ず出てくるのがクラスという概念です。
クラスはオブジェクトの基となるものです。
オブジェクトの設計書と呼ばれることもあります。
また、オブジェクトを抽象化したもの、という考え方もできます。
例えば、普段使用しているスマホやPCは、物理的に実体のあるモノですが、「パソコン」や「スマホ」という言葉自体は、実体のあるものを指しているわけではなく、パソコン全体、スマホ全体を表すための概念です。
「人間」という言葉も、特定の誰かを示す言葉ではなく、人間全体を表す概念で、実体のある人間の1人として私やこれを読んでいるあなたという存在がいます。
このように、オブジェクトという具体的なものを抽象化したものがクラスです。
クラスは日本語だと分類という意味を持ちます。
(学校のクラスも分類という意味ですね。)
そのため具体的なオブジェクトを分類するものがクラスという風に考えるのも自然です。
例えば自分自身の存在を分類すると、人間、社会人、男性、などに分類することができます。
このような分類がクラスになります。

インスタンス

クラスと同じ頻度でよく登場する用語がインスタンスです。
インスタンスはクラスを元に作られるもので、オブジェクトとほぼ同義です。
厳密には微妙にニュアンスが異なるようで、オブジェクトの方が幅広いです。
インスタンスは、クラスを元に作られたものに限られます。
多くのオブジェクト指向プログラミング言語ではnewというキーワードを使用してクラスを元にしてインスタンスを作成します。

メソッドとプロパティ

プログラミングにオブジェクト指向の考え方が取り入れられることで何が便利になったのか。
オブジェクト指向以前の言語では、データの保存と処理の定義はそれぞれ切り離されていました。
処理は関数で定義され、データはグローバル変数に保持していました。
それをオブジェクトという単位でデータと処理をまとめることにより、わかりやすく、そして拡張しやすいプログラムを作ることができます。

オブジェクトの中で定義されている処理のことをメソッド、オブジェクトの中で定義されているデータのことをプロパティと呼びます。
プロパティは言語によって呼び方が異なることがあり、例えばJavaではフィールドと呼ばれます。

コンストラク

多くのプログラミング言語では、インスタンスを作成する時にnewというキーワードを使用します。
そしてインスタンスが作成された時に呼び出される特殊な処理を定義することができます。
このインスタンス生成時に呼び出される処理のことをコンストラクタと呼びます。
コンストラクタでは、主にプロパティの初期化の処理などを定義することが多いです。

オブジェクト指向 三大要素

オブジェクト指向にはオブジェクト指向の三大要素と呼ばれるものがあります。
それは

の3つです。
本によっては四大要素として、「抽象化」がプラスされていることもあります。
それぞれを簡単に解説します。

継承

継承は、特徴を受け継いでいくことをいいます。
例えば、「生き物」「哺乳類」「人間」「社会人」という4つのクラスがあったとします。
哺乳類は生き物に含まれます。
つまり、生き物の特徴を受け継いでいます。
生き物の特徴を受け継ぎつつも、哺乳類特有の機能を持っています。
人間は哺乳類に含まれます。
つまり、哺乳類の特徴を受け継ぎつつも、人間独自の機能や特徴を持っています。
社会人は、人間の中でも学生生活を終えて働き始めた人たちのことを挿します。
つまり、人間の特徴を持ちつつ、社会人にしかないステータスや行動をいいます。
この場合、哺乳類は生き物を継承し、人間は哺乳類を継承し、社会人は人間を継承していると言えます。

もう一つ例を出しましょう。
「乗り物」「車」「電気自動車」というクラスがあったとします。
車は乗り物に含まれます。
乗り物の特徴がありつつも、車独自の機能があります。
つまり、車は乗り物を継承しています。
電気自動車は車の特徴がありつつ、電気自動車独自の機能があります。
つまり、電気自動車は車を継承しています。

イメージとしては、継承される継承元の方が概念としては広く、継承先はより具体的になっていくようなイメージです。
継承のイメージができたでしょうか。

プログラミングに継承という概念が導入されると何が良いのでしょうか。
プログラミングでは、継承を使用することでソースコードの量を減らすことができます。

先に例に出した「人間」クラスと「社会人」クラスを考えます。
人間は年齢、名前、性別などがあります。
これらの情報を「人間」クラスに定義していたとします。
そして、人間クラスを継承した社会人クラスを作成したとします。
社会人も人間なので、年齢、名前、性別といった情報はありますが、それらの情報は人間クラスに定義されているので、社会人クラスでは改めて定義する必要がなくなります。
プログラムを書くときに、社会人だけに必要となる情報だけを追加していけばよくなります。
結果として、全体のコード量を減らすことができます。

オーバーライド

オーバーライドはオブジェクト指向の三大要素ではありませんが、継承と非常に深い関係がある概念のため、ここで説明しておきます。
オーバーライドは簡単にいえばメソッドの上書きです。
継承を使うと、親クラスに定義されているメソッドを無条件に使うことができますが、親クラスから受けついだメソッドを上書きすることが可能です。
このような仕組みのことをオーバーライドといいます。
この説明だけだと何のメリットがあるのかわからないと思いますが、後に説明するポリモーフィズムと非常に深い関わりを持ちます。

カプセル化

カプセル化は、オブジェクトが持つデータを外部から直接アクセスできないように隠蔽して、メソッドによってデータのアクセスを提供することです。
多くのプログラミング言語では、アクセス修飾子という仕組みがあります。
オブジェクトが持つプロパティやメソッドに対して、外部からどこまでアクセスを許可するかを設定するものですが、そのような仕組みを駆使してカプセル化を実現します。
説明だけでは理解しにくいかもしれません。
例えば人間は名前、年齢、などのデータを持ちますが、このようデータは他人が勝手に変えることはできません。
つまり、外部から勝手に変えることができないデータです。
現実世界では人の名前を他人が勝手に変更するなんてことはあり得ませんが、プログラムでオブジェクトを作成したとき、作り方によっては外部から簡単にデータを変更することができてしまいます。
そのような不都合が起きないようにする仕組みがカプセル化です。

ポリモーフィズム

ポリモーフィズムは日本語では多態性、多様性などと呼ばれます。
これは継承や抽象化を前提とした機能で、異なるオブジェクトを同一視して扱う機能です。
先に紹介したオーバーライドの機能があることで成り立つ非常に便利な機能です。

例えば、「プログラマークラス」と「営業クラス」があったとします。
プログラマーにプログラミングをして欲しい時には、プログラマークラスのオブジェクトを作成し、プログラミングをするメソッドを呼び出します。
営業に営業の仕事をして欲しかったら、営業クラスのオブジェクトを作成し、営業活動をするメソッドを呼び出します。
ただ、このプログラマーと営業の上司が同じ人だった場合、人によっていちいち指示する内容を変えるのは面倒ですね。
「働け」という同じ命令をすることで、プログラマーならプログラマーとしての働きを、営業なら営業としての働きをしてくれると便利です。
この仕組みを実現するのがポリモーフィズムです。
実際には「社員クラス」のようなクラスを作成し、「働く」メソッドを定義しておきます。
プログラマークラスと営業クラスは、社員クラスを継承し、働くメソッドをオーバーライドし、それぞれの働く内容を上書きします。
そうすると、社員に対して「働く」メソッドを実行してもらうと、そのオブジェクトがプログラマーならプログラマーとしての働きと、営業なら営業としての働きをしてくれるわけです。

メソッドや関数という概念は、呼び出される処理を共通化する仕組みですが、ポリモーフィズムは処理の呼び出しを共通化する仕組みだといえます。
文章の説明だけだと分かりにくいかと思いますが、ソースコードで確認する場合はJavaオブジェクト指向編の記事に書いているのでそちらを参照ください。

UML

UMLはUnified Modeling Languageの略で、オブジェクト指向の分析と設計をするときに使用するモデリング手法です。
いくつかの図(ダイアグラム)が用意されています。
よく使用するのはクラス図、シーケンス図、ユースケース図あたりでしょうか。
ここでは詳細は解説しません。
機会があれば別でUMLについての記事もまとめます。
クラス図はオブジェクト指向の理解を助けるのに重要な役割をはたしますので、クラス図については調べて理解しておくと良いでしょう。

まとめ

ソースコードがないと理解が難しい用語もあるかと思います。
Javaオブジェクト指向編の記事と合わせて読んでいただければと思います。

オブジェクト指向の理解を深めるおすすめ書籍

【プログラミング全般】デバッグの基本

ここではデバッグについて解説します。

デバッグとは

プログラム(ソフトウェア・アプリケーション)の不具合のことをバグと呼びます。
そしてバグを取り除く(プログラムの不具合を修正する)ことをデバッグと呼びます。
バグは日本語では虫の意味があります。
なぜプログラムの不具合のことをバグと呼ぶかというと、昔、コンピュータの隙間に虫(蛾)が入り込み、コンピュータが正常に動かなくなることがあったそうです。
そこで、リアルに虫を取り除いてコンピュータの不具合を取り除いていたそうです。
その名残で、ソフトウェアの不具合もバグと呼ぶようになり、不具合の修正をデバッグを呼ぶようになったそうです。

プログラムの不具合と言っても色々な種類があります。
プログラムの文法が間違っている場合のエラー、実行したときに発生するエラーもあります。
また、エラーが発生しなくても、プログラムが意図した通りに動かない場合は不具合になります。
(例えば計算結果が想定した通りにならない、など)
そういった不具合の原因を調べて解決していく作業のことをデバッグと呼びます。

デバッグ力の重要性

デバッグの能力はエンジニア(プログラマー)として働いていく上では非常に重要なスキルで、デバッグ力=プログラミング力と言っても過言ではありません。
どんなにプログラミングの知識があって経験のあるエンジニアだとしても、作るプログラムの規模が大きくなると、エラーやバグのないプログラムを作ることは不可能に近いです。
人間はミスをする生き物ですし、どんなに頭の良い人でも1人の頭で考えたことは漏れがある可能性も高く、全体を把握するのも難しいです。
プログラムは人が書く以上バグが混在すると思って作る必要があります。
つまりプログラミングをする中でバグを解決する力というのは、プログラミングの基本的な知識と同じレベルで求められる能力になります。

デバッグ関連用語

デバッガ

大抵のプログラミング言語では、開発環境の中にデバッグをサポートするための機能が組み込まれています。
このような機能のことをデバッガと呼びます。
デバッガを使うと、実行中の処理を途中で止めることができます。
そして、その時点で使用することができる変数の値を確認したり、処理を1行ずつ進めながらどこでエラーが発生するかを確認したりすることができます。

ブレークポイント

デバッガを使うと、プログラム実行中に処理を止めることができますが、ソースコード上のどこで処理を止めるかを事前に設定する必要があります。
その処理を止める場所のことをブレークポイントと呼びます。
ブレークポイントを設定した後、デバッグモードでプログラムを実行すると、処理がブレークポイントに到達した時点で一度処理が止まります。
処理が止まると、その時点で使用することができる変数の中身を確認することができます。
また、そこから処理を再開したり、処理を1行ずつ進める(ステップ実行)ことができます。

ステップ実行

プログラムの処理をソースコードの1ステップずつ実行することをステップ実行と呼びます。
デバッグを行うときによく使用します。
ステップ実行にはステップオーバーとステップインの2つがあります。

ステップオーバー

ステップオーバーはステップ実行の1つです。
ステップ実行をしている中でメソッドや関数を呼び出していることもありますが、ステップオーバーの場合はそのメソッドや関数の中には入りこまず、あくまでも現在実行中のメソッド・関数の中を1行ずつ実行します。

ステップイン

ステップインでは、メソッドや関数を呼び出している場合、呼び出し先の処理の中にも入りこんでステップ実行します。

ステップリターン

ステップリターンでは、現在処理中のメソッドや関数の終了まで処理を実行し、呼び出し元まで戻ります。

デバッグの方法はプログラミング言語・開発環境によって異なりますが、ブレークポイント、ステップオーバー、ステップインといった用語は共通していることがほとんどです。
どの言語、環境であっても、デバッグに慣れることで、他の言語で応用が効くようになります。

標準出力とデバッグ

ほとんどのプログラミング言語では、標準出力(コンソールへの出力)を行う命令が用意されています。
Javaの場合は「System.out.print」メソッド。
JavaScriptの場合は「console.log」メソッド。
PHPの場合はvardump関数など。(これはブラウザへの出力ですが)
これらの出力の命令を駆使して、変数の値を確認することでデバッグをすることもあります。
むしろプログラミングを学習したばかりの頃はこの手法で確認をする方が一般的かもしれません。
デバッガを使ったデバッグは、慣れるまでは面倒に感じます。
しかし、プログラムの規模が大きくなったりすると、デバッガを使ってデバッグする方が圧倒的に効率的になりますので、なれて使いこなせるようにしておきましょう。

言語・環境ごとのデバッグ方法

いくつかの言語・環境のデバッグ方法を紹介します。

JavaScript

JavaScriptWebブラウザデバッグを行うことができます。
Google Chromeの場合、F12、またはCtrl + Shift + I でディベロッパーツールが起動します。
ディベロッパーツールのSourceタブを開くと、現在表示されているWebページ上のJavaScriptソースコードを確認できます。
ソースコードの行数の個所をクリックすると、ブレークポイントを設定することができます。
Webページの読み込みや、画面上のイベントなど、該当のソースコードを実行される処理が走るとブレークポイントで処理を止めることができます。
そこからステップ実行したり、変数の値を確認することができます。

【JavaScript】スロットゲーム

JavaScriptで作成したスロットゲームです。
ドットインストールを参考にしようとしつつ、結局全く違うコードになってしまいましたが、とりあえずは動きます。
JavaScriptの学習やゲーム作成の参考にどうぞ。

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>スロットゲーム</title>
    <style>
        body {
            text-align: center;
        }
        main section p {
            font-size: 36px;
            height: 100px;
            line-height: 100px;
            margin: 0;
            background-color: #f5f5f5;
        }
        main {
            background-color: #e6e6fa;
            width: 360px;
            margin: 50px auto;
            display: flex;
            justify-content: space-between;
        }
        section {
            width: 100px;
            margin: 10px;
        }
        section button {
            width: 100px;
        }
        #start {
            width: 150px;
            height: 40px;
            font-size: 24px;
        }

    </style>
</head>
<body>
    <h1>スロットゲーム</h1>
    <main>
        <section>
            <p id="slot1"></p>
            <button id="stop1">STOP</button>
        </section>
        <section>
            <p id="slot2"></p>
            <button id="stop2">STOP</button>
        </section>
        <section>
            <p id="slot3"></p>
            <button id="stop3">STOP</button>
        </section>
    </main>
    <button id="start">START</button>

    <script>
        'use strict';

        const nums = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
        const slot1 = document.getElementById('slot1');
        const slot2 = document.getElementById('slot2');
        const slot3 = document.getElementById('slot3');
        const stop1 = document.getElementById('stop1');
        const stop2 = document.getElementById('stop2');
        const stop3 = document.getElementById('stop3');
        const start = document.getElementById('start');

        let setTimeoutId1 = undefined;
        let setTimeoutId2 = undefined;
        let setTimeoutId3 = undefined;

        start.disabled = false;
        stop1.disabled = true;
        stop2.disabled = true;
        stop3.disabled = true;
        slot1.textContent = nums[Math.floor(Math.random() * nums.length)];
        slot2.textContent = nums[Math.floor(Math.random() * nums.length)];
        slot3.textContent = nums[Math.floor(Math.random() * nums.length)];


        function setNum1() {
            slot1.textContent = nums[Math.floor(Math.random() * nums.length)];
            setTimeoutId1 = setTimeout(() => {
                setNum1();
            }, 100);
        }

        function setNum2() {
            slot2.textContent = nums[Math.floor(Math.random() * nums.length)];
            setTimeoutId2 = setTimeout(() => {
                setNum2();
            }, 100);
        }

        function setNum3() {
            slot3.textContent = nums[Math.floor(Math.random() * nums.length)];
            setTimeoutId3 = setTimeout(() => {
                setNum3();
            }, 100);
        }

        function statusCheck() {
            if(start.disabled === true && stop1.disabled === true && stop2.disabled === true && stop3.disabled === true) {
                start.disabled = false;
                stop1.disabled = true;
                stop2.disabled = true;
                stop3.disabled = true;
            }
        }

        start.addEventListener('click', () => {
            start.disabled = true;
            stop1.disabled = false;
            stop2.disabled = false;
            stop3.disabled = false;
            setNum1();
            setNum2();
            setNum3();
        });

        stop1.addEventListener('click', () => {
            stop1.disabled = true;
            clearTimeout(setTimeoutId1)
            statusCheck();
        });

        stop2.addEventListener('click', () => {
            stop2.disabled = true;
            clearTimeout(setTimeoutId2)
            statusCheck();
        });

        stop3.addEventListener('click', () => {
            stop3.disabled = true;
            clearTimeout(setTimeoutId3)
            statusCheck();
        });      


    </script>
</body>
</html>

【JavaScript】タイピンゲーム2

以前JavaScriptでのタイピングゲーム1のソースコードを載せましたが、少し改良したのでそちらも載せます。
改善点としては、

  • リアルタイムで時間が表示される
  • ストップボタンを追加
  • 出題される単語のテーマを選べるように

といった感じです。
こちらもJavaScriptの学習やゲーム作成の参考にどうぞ。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>タイピングゲーム</title>
    <style>
        body {
            text-align: center;
        }
        #target {
            font-size: 48px;
        }
        .btn, #time, #count {
            font-size: 18px;
        }
    </style>
</head>
<body>
    <h1>タイピングゲーム</h1>
    <p>単語テーマ
        <select id="word-theme" name="word-theme">
            <option value="color">カラー</option>
            <option value="fruits">フルーツ</option>
        </select>
    </p>
    <p>
        <button class="btn" type="button" id="start-btn" name="start-btn">スタート<br>(Enter)</button>
        <button class="btn" type="button" id="stop-btn" name="stop-btn">ストップ<br>(ESC)</button>
    </p>
    <p id="target"></p>
    <p id="time"></p>
    <p id="count"></p>
    <script>
        'use strict';

        const target = document.getElementById('target');
        const time = document.getElementById('time');
        const count = document.getElementById('count');
        const startBtn = document.getElementById('start-btn');
        const stopBtn = document.getElementById('stop-btn');
        startBtn.disabled = false;
        stopBtn.disabled = true;

        let words = [];
        let wordCount = 0; // 完了した文字数
        let word = ''  // 現在入力中の文字
        let index = 0; // 文字の現在地
        let missCount = 0;
        let startTime = 0;
        let setTimeoutId = 0;

        function dispTime() {
            time.textContent = ((new Date - startTime) / 1000).toFixed(2) + '秒';
            setTimeoutId = setTimeout(() => {
                dispTime();
            }, 50);
        }

        // ゲームスタート時の処理
        function gameStart() {
            startBtn.disabled = true;
            stopBtn.disabled = false;
            const wordTheme = document.getElementById('word-theme').value;
            switch(wordTheme) {
                case 'color':
                    words = ['red', 'blue', 'green', 'yellow', 'pink', 'black', 'white', 'purple', 'gray', 'orange'];
                    break;
                case 'fruits':
                    words = ['apple', 'banana', 'grape', 'peach', 'orange', 'pear', 'melon', 'lemon', 'strawberry', 'mango'];
                    break;
                default :
                    words = ['abcde'];
                    break;
            }

            wordCount = words.length;
            missCount = 0;
            count.textContent = `残り ${wordCount} つ`;
            startTime = new Date();
            word = words.splice(Math.floor(Math.random() * words.length), 1).join();
            target.textContent = word;
            dispTime();
        }

        // ゲームストップ
        function gameStop() {
            clearTimeout(setTimeoutId);
            // target.textContent = '';
            // time.textContent = '';
            // count.textContent = '';
            startBtn.disabled = false;
            stopBtn.disabled = true;
        }

        // ボタン押下時
        document.getElementById('start-btn').addEventListener('click', gameStart, false);
        document.getElementById('stop-btn').addEventListener('click', gameStop, false);

        // キー押下時
        document.addEventListener('keydown', e => {
            // Enter押下時はスタート
            if(e.key === 'Enter' && startBtn.disabled === false) {
                gameStart();
                return;
            }
            // ESCキー押下でストップ
            if(e.key === 'Escape' && stopBtn.disabled === false) {
                gameStop();
                return;
            }

            if(stopBtn.disabled === true) {
                return;
            }

            if(word.charAt(index) === e.key)  {
                index++;
                target.textContent = '_'.repeat(index) + word.substring(index);

                if(index === word.length) {
                    // 1単語終了時
                    index = 0;
                    wordCount--;
                    count.textContent = `残り ${wordCount} `;

                    if(wordCount === 0) {
                        // ゲーム終了時
                        clearTimeout(setTimeoutId);
                        count.textContent = `タイプミス ${missCount}個`;
                        startBtn.disabled = false;
                        stopBtn.disabled = true;
                    } else {
                        word = words.splice(Math.floor(Math.random() * words.length), 1).join();
                        target.textContent = word;
                    }
                }
            } else {
                missCount++;
            }  

        });

    </script>
</body>
</html>

【JavaScript】タイピングゲーム1

JavaScriptで作成した簡単なタイピングゲームです。
ドットインストールで紹介されていたJavaScriptでのタイピングゲームの動画を参考にアレンジを加えたものです。
JvaScriptの学習、ゲームの作成の参考に。
もう少し改良したいのでバージョンアップしたら別記事でアップします。

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>タイピングゲーム</title>
    <style>
        body {
            text-align: center;
        }
        #target {
            font-size: 48px;
        }
        #start-btn {
            font-size: 24px;
        }
    </style>
</head>
<body>
    <h1>タイピングゲーム</h1>
    <p><button type="button" id="start-btn" name="start-btn">スタート<br>(Enter)</button></p>
    <p id="target"></p>
    <p id="count"></p>
    <script>
        'use strict';

        let words = [];
        let count = 0; // 完了した文字数
        let word = ''  // 現在入力中の文字
        let index = 0; // 文字の現在地
        let missCount = 0;
        const target = document.getElementById('target');
        let startTime = 0;

        // ゲームスタート時の処理
        var gameStart = () => {
            words = ['red', 'blue', 'green', 'yellow', 'pink', 'black', 'white', 'purple', 'gray', 'orange'];
            count = words.length;
            missCount = 0;
            document.getElementById('count').textContent = `残り ${count} つ`;
            startTime = new Date();
            word = words.splice(Math.floor(Math.random() * words.length), 1).join();
            target.textContent = word;
        };

        // ボタン押下時
        document.getElementById('start-btn').addEventListener('click', gameStart, false);

        // キー押下時
        document.addEventListener('keydown', e => {
            // Enter押下時はスタート
            if(e.key === 'Enter') {
                gameStart();
                return;
            }

            if(word.charAt(index) === e.key)  {
                index++;
                target.textContent = '_'.repeat(index) + word.substring(index);

                if(index === word.length) {
                    // 1単語終了時
                    index = 0;
                    count--;
                    document.getElementById('count').textContent = `残り ${count} `;

                    if(count === 0) {
                        // ゲーム終了時
                        const clearTime = new Date() - startTime;
                        target.textContent = (clearTime / 1000).toFixed(2) + '秒';
                        document.getElementById('count').textContent = `タイプミス ${missCount}個`;                       
                    } else {
                        word = words.splice(Math.floor(Math.random() * words.length), 1).join();
                        target.textContent = word;
                    }
                }
            } else {
                missCount++;
            }  

        });

    </script>
</body>
</html>

【JavaScript】じゃんけんゲーム

JavaScriptで作成した簡単なじゃんけんゲームです。
画像ファイルは適当なネットのフリー素材を探してご使用ください。
JavaScriptの学習、ゲーム作成の参考に。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>じゃんけんゲーム</title>
    <style>
        body {
            text-align: center;
        }
        #your-choice, #opponent-choice {
            font-size: 24px;
        }
        img.hidden {
            display: none;
        }
        main {
            display: flex;
            justify-content: space-between;
        }
        section {
            margin: 0 auto;
        }
    </style>
</head>
<body>
    <h1>じゃんけんゲーム</h1>
    <button type="button" name="btn" value="0">グー</button>
    <button type="button" name="btn" value="1">チョキ</button>
    <button type="button" name="btn" value="2">パー</button>
    <main>
        <section>
            <p>あなた</p>
            <p id="your-choice"></p>
            <img id="your-choice-img" class="hidden" src="#">
        </section>
        
        <section>
            <p>相手</p>
            <p id="opponent-choice"></p>
            <img id="opponent-choice-img" class="hidden" src="#">
        </section>

    </main>

    <hr>

    <p>結果</p>
    <p id="result"></p>
    <p>戦歴 <span id="totalNum">0</span><span id="winNum">0</span></p>

    <script>
        'use strict';

        const jankenImg = ['img/janken_gu.png', 'img/janken_choki.png', 'img/janken_pa.png'];
        const yourChoice = document.getElementById('your-choice');
        const opponentChoice = document.getElementById('opponent-choice');
        const yourImg = document.getElementById('your-choice-img');
        const opponentImg = document.getElementById('opponent-choice-img');


        const JankenDisp = function(choice, p, img) {
            img.src = jankenImg[choice];
            img.classList.remove('hidden');
            if(choice === 0) {
                p.textContent = 'グー';
                img.alt = 'グー';
            } else if (choice === 1) {
                p.textContent = 'チョキ';   
                img.alt = 'チョキ';
            } else if (choice === 2) {
                p.textContent = 'パー';   
                img.alt = 'パー';
            }
        } 

        const judge = function(yourChoice, opponentChooice) {
            let totalNum = Number(document.getElementById('totalNum').textContent);
            totalNum++;
            let winNum = Number(document.getElementById('winNum').textContent);
            let result;
            // グーの場合
            if(yourChoice === 0) {
                if (opponentChooice === 0) {
                    result = 'あいこです';
                } else if (opponentChooice === 1) {
                    result = 'あなたの勝ちです';
                    winNum++;
                } else if (opponentChooice === 2) {
                    result = 'あなたの負けです';
                }
            }
            // チョキの場合
            if(yourChoice === 1) {
                if (opponentChooice === 0) {
                    result = 'あなたの負けです';
                } else if (opponentChooice === 1) {
                    result = 'あいこです';
                } else if (opponentChooice === 2) {
                    result = 'あなたの勝ちです';
                    winNum++;
                }
            }
            // パーの場合
            if(yourChoice === 2) {
                if (opponentChooice === 0) {
                    result = 'あなたの勝ちです';
                    winNum++;
                } else if (opponentChooice === 1) {
                    result = 'あなたの負けです';
                } else if (opponentChooice === 2) {
                    result = 'あいこです';
                }
            }
            document.getElementById('totalNum').textContent = totalNum;
            document.getElementById('winNum').textContent = winNum;
            document.getElementById('result').textContent = result;

        }

        const btns = document.getElementsByName('btn');
        btns.forEach((el, key) => {
            el.addEventListener('click', function() {
                // 押したボタンの内容が表示される処理
                JankenDisp(Number(this.value), yourChoice, yourImg);

                // 相手の選択をランダムで処理
                let choiceNum = Math.floor(Math.random() * Math.floor(3));
                JankenDisp(choiceNum, opponentChoice, opponentImg);

                // 判定
                judge(Number(this.value), choiceNum)

            });
        })

        // for..ofバージョン
        // for(let btn of btns) {
        //     // ボタンを押下したときの処理
        //     btn.addEventListener('click', function() {
        //         // 押したボタンの内容が表示される処理
        //         JankenDisp(Number(this.value), yourChoice);

        //         // 相手の選択をランダムで処理
        //         let opponentChoice = Math.floor(Math.random() * Math.floor(3));
        //         JankenDisp(opponentChoice, opponentChoice, yourImg);

        //         // 判定
        //         judge(Number(this.value), opponentChoice, opponentImg)

        //     });
        // }

    </script>
</body>
</html>

【Java】【AWS】Amazon EC2でJavaのWebアプリケーション動作環境を構築してデプロイするまで

クラウド上にJavaによるWebアプリケーションの動作環境を構築する手順です。
AWSAmazon EC2を使用し、マシンイメージはAmazon Linux 2 を利用します。
DBはPostgreSQLを使用する前提とします。

解説する内容は以下の項目。
インスタンスの作成までに関する手順とSSH接続する手順は省略します。

  • ロケールの変更
  • PostgreSQLのインストール
  • Javaのインストール
  • ApacheTomcatのインストール
  • GitとMavenのインストール
  • Webアプリケーションのデプロイ
  • Gitを活用したデプロイ方法

ロケールの変更

参考サイト
Amazon LinuxのデフォルトのロケールはENになっている。
Spring Bootをでメッセージリソースを使う場合、デフォルトでen用のファイルを読み込んでしまう。
ファイルがない場合はエラーになるので、日本に設定しておく。

# ロケールの確認
$ localectl status
# ロケール変更
localectl set-locale LANG=ja_JP.eucjp
# 変更されたか確認
$ localectl status

PostgreSQLのインストール

ここではEC2インスタンスPostgreSQLをインストールして外部から接続できるようにする手順を解説します。
該当のEC2インスタンスSSH接続されていることを前提とします。

参考サイト

1. リポジトリの更新

まずはリポジトリを更新する。 デフォルトの状態でPosgreSQLをインストールするとバージョンが9.2なので(2020/09時点)最新バージョンがインストールできるようにリポジトリ更新します。

$ wget https://download.postgresql.org/pub/repos/yum/reporpms/EL-7-x86_64/pgdg-redhat-repo-latest.noarch.rpm
$ sudo rpm -Uvh --nodeps pgdg-redhat-repo-latest.noarch.rpm
$ sudo sed --in-place -e "s/\$releasever/7/g" /etc/yum.repos.d/pgdg-redhat-all.repo

2.PostgreSQLのインストールと環境設定

まずはインストールしてサービスの起動

# postgreSQLのインストール
$ sudo yum -y install postgresql12-server
# データベース初期化
$ sudo /usr/pgsql-12/bin/postgresql-12-setup initdb
# サービス自動起動設定
$ sudo systemctl enable postgresql-12
# サービスの起動
$ sudo systemctl start postgresql-12.service

デフォルトではOSユーザーでログインする設定なので、postgresユーザーにパスワードを与えてログインする。
(PostgreSQLをインストールすると自動でpostgresユーザーが作成される)

# postgresのパスワード変更(ここではpasswordとする)
$ sudo passwd postgres
⇒password

# ユーザーを切り替え
$ su - postgres
password ⇒ password

# postgreSQLへログイン
$ psql

PostgreSQL内のpostgresユーザーのパスワードを変更しておく。
デフォルトだとパスワードは設定されていない。

# postgreSQL内のpostgreユーザーのパスワード変更(ここではpasswordとする)
alter user postgres with password 'password';

# postgresqlログアウト
\q

postgresqlからログアウトしてec2-userに戻ったら、外部からOSのユーザー以外でもログインできるように設定ファイルを変更する。

# ec2-userに戻る(postgreユーザーになっているため)
$ exit

# 設定ファイルの変更
# 外部からのアクセスできるようにする
$ sudo vi /var/lib/pgsql/12/data/postgresql.conf

# listen_address = 'localhost'
⇓
listen_address = '*'

# 設定ファイルの変更
# 外部からのpostgreSQLユーザーのパスワードでアクセスできるようにする
$ sudo vi /var/lib/pgsql/12/data/pg_hba.conf

# IPv4 local conecctions:
host  all  all  127.0.0.1/32  ident
⇓
host  all  all  0.0.0.0/0  md5

# サービス再起動
$ sudo systemctl restart postgresql-12.service

外部からの接続を確認する。
コマンドプロンプトpowershell等から

psql -h <パブリックDNS> -p 5432 -U postgres -d postgres

その後パスワード(ここではpassword)を入力して接続できることを確認する。


Javaのインストール

Java環境のインストール方法です。
AmazonのOpenJDK(amazon-corretto)をインストールします。

参考サイト

# JDKインストール
$ sudo yum -y install java-11-amazon-corretto
# javac バージョン確認
$ javac --version
# java バージョン確認
$ java --version

ApacheTomcatのインストール

ApacheTomcatのインストールに関しては以下のサイトを参考に。
参考サイト

ちなみに、Spring Bootアプリケーションをデプロイする場合はビルドされたファイルにTomcatが含まれるため、全グループがSpring Bootアプリケーションを前提に開発を進める場合はこの手順は省略しても問題ありません。

Apacheインストール

# インストール
$ sudo yum install -y httpd
# 確認
$ httpd -v
# 自動起動設定
$ sudo systemctl enable httpd.service
# 起動
$ sudo systemctl start httpd.service
# 停止
$ sudo systemctl stop httpd.service

Tomcatインストール

ダウンロードするバージョンは
公式サイト
から確認

# ユーザー作成
$ sudo useradd -s /sbin/nologin tomcat

# tomcatダウンロード
$ cd ~
$ wget https://downloads.apache.org/tomcat/tomcat-9/v9.0.38/bin/apache-tomcat-9.0.38.tar.gz

# 解凍して配置
$ tar -xzvf ~/apache-tomcat-9.0.38.tar.gz
$ sudo mv ~/apache-tomcat-9.0.38 /opt
$ sudo chown -R tomcat:tomcat /opt/apache-tomcat-9.0.38

# シンボリックリンクの作成
$ sudo ln -s /opt/apache-tomcat-9.0.38 /opt/apache-tomcat
$ sudo chown -h tomcat:tomcat /opt/apache-tomcat

# ログのシンボリックリンク作成
$ sudo ln -s /opt/apache-tomcat/logs /var/log/tomcat
$ sudo chown -h tomcat:tomcat /var/log/tomcat

# TomcatをOSにサービスとして登録する為、ルート権限でUnitを作成
$ sudo vi /usr/lib/systemd/system/tomcat.service

tomcat.service

[Unit]
Description=Apache Tomcat Web Application Container
After=syslog.target network.target

[Service]
Type=oneshot
PIDFile=/opt/apache-tomcat/tomcat.pid
RemainAfterExit=yes
#EnvironmentFile=/etc/tomcat/tomcat.conf
#Environment="NAME="
#EnvironmentFile=-/etc/sysconfig/tomcat
ExecStart=/opt/apache-tomcat/bin/startup.sh
ExecStop=/opt/apache-tomcat/bin/shutdown.sh
ExecReStart=/opt/apache-tomcat/bin/shutdown.sh;/opt/apache-tomcat/bin/startup.sh
SuccessExitStatus=143
User=tomcat
Group=tomcat

[Install]
WantedBy=multi-user.target
# 自動起動設定
$ sudo systemctl enable tomcat.service
# システム起動
# Apacheを先に起動
$ sudo systemctl start httpd.service
$ sudo systemctl start tomcat.service

# システム停止
$ sudo systemctl stop tomcat.service

ブラウザから http://<パブリックDNS>:8080 でTomcatのトップページにアクセスできることを確認する。


http://ec2-18-182-18-43.ap-northeast-1.compute.amazonaws.com:8080/

※Spring Bootアプリケーションを起動したい場合は、Tomcatが起動しているとポート番号が重複してエラーになるので注意。


GitとMavenのインストール

GitとMavenを使ってデプロイすることも可能なので、それ用にインストールしておいても良いかも

Gitのインストール

# git インストール
$ sudo yum -y install git 
# バージョン確認
$ git version

Mavenのインストール

参考サイト

mavenの最新バージョンのリンクはこちらから

# ダウンロード
$ cd ~
$ sudo wget https://ftp.jaist.ac.jp/pub/apache/maven/maven-3/3.6.3/binaries/apache-maven-3.6.3-bin.tar.gz

# 展開&配置
$ sudo tar -xzvf apache-maven-3.6.3-bin.tar.gz
$ sudo mv apache-maven-3.6.3 /opt/
# シンボリックリンク作成
$ sudo ln -s /opt/apache-maven-3.6.3 /opt/apache-maven
# PATHの追加
$ vi .bash_profile
PATH=$PATH:$HOME/.local/bin:$HOME/bin
⇓
MVN_HOME=/opt/apache-maven
PATH=$MVN_HOME/bin:$PATH:$HOME/.local/bin:$HOME/bin
# PATHを通す
$ source .bash_profile
# Mavenバージョン確認
$ mvn --version

Webアプリケーションのデプロイ方法

デプロイとは開発環境のプログラムをアプリケーションを動作させる本番環境に反映させることをいいます。

Spring Bootを使用した場合と使用しない場合で方法が異なります。

Spring Bootアプリケーションの場合(JSPを使用していない場合)

参考サイト

プロジェクトをビルドしてjarファイルを作成する
Eclipseで対象のSpring Bootプロジェクトを右クリック ⇒ 実行 ⇒ Maven install を実行。
ビルドが成功したらtarget配下にjarファイルができる。

※環境によってはエラーが出ることがある。

java.lang.IllegalStateException: Unable to find a @SpringBootConfiguration, you need to use @ContextConfiguration or @SpringBootTest(classes=...) with your test

その時はsrc/test/java配下にあるテストクラスにある@SpringBootTestアノテーションに引数を追加する。

// クラス名は@SpringBootApplicationがついているクラス
@SpringBootTest(classes = {TaskFrameworkApplication.class})

これでエラーが解消する場合がある。

jarファイルを転送する
scpコマンドなどを使用してjarファイルをEC2インスタンスに転送する。

コマンドプロンプトなどから

scp -i pemファイル "jarファイルのパス" ユーザー名@パブリックDNS:転送先のパス

を実行する


デスクトップのjarファイルをec2-userのホームディレクトリに転送する

scp -i nihonbasib_20200915.pem "C:\Users\admin\Desktop\demo-0.0.1-SNAPSHOT.jar" ec2-user@ec2-18-182-18-43.ap-northeast-1.compute.amazonaws.com:~/

Webアプリケーションを実行する EC2インスタンスSSH接続し

java -jar 転送されたjarファイル

を実行するとSpringBootアプリケーションが起動する。

ブラウザからアクセスして動作を確かめる http://パブリックDNS:8080/マッピングしているパス

プログラムを停止したい場合
Ctrl + CでTomcatを停止することができます。
ただしサーバーを停止してもプロセスが残っている場合があります。
その場合はpsコマンドでプロセスIDを確認し、killコマンドで終了させる。

# プロセスの確認
$ ps a
# 出力結果からjavaアプリケーションのPIDを確認
# killコマンドでプロセスを削除
$ kill PID

Spring Bootアプリケーションの場合(JSPを使用している場合)

参考サイト JSPを使用する場合(アプリケーションにWEB-INFフォルダが存在している場合)は、jarファイルによるデプロイができない。
jarファイルとして作成した場合、WEB-INFフォルダが作成されない。
warファイルとして作成し、Tomcat上でデプロイする必要がある。
ビルドの方法は変わらない。

pom.xmlに以下を追加

<packaging>war</packaging>

書く場所はproject要素の直下。
propertiesやdependenciesなどと同じ階層に書く。
Eclipseの場合はプロジェクト作成時にビルドがjarかwarかを選択できるので、そこでwarを選択しておけば勝手に追記されるはず。

また、warファイルでデプロイする場合は外部のTomcatを使ってデプロイするため、warファイルにTomcatを含める必要がなくなる。
デフォルトだとTomcatを含めてしまうので、pom.xmlで省くように設定する。

<dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-tomcat</artifactId>
        <scope>provided</scope>
</dependency>

scopeをprovidedにすれば良い。
warファイルのデプロイ方法は次節の動的Webアプリケーションの場合で解説します。

動的Webアプリケーションの場合

Spring Bootを使用せずにJSP/Servletのみで作成した場合
参考サイト

warファイルを作成する Eclipseからプロジェクトを右クリック ⇒ エクスポート ⇒ warファイルを選択。
出力先を指定してwarファイルを出力します。

warファイルを転送する scpコマンドなどでwarファイルをEC2インスタンスに転送します。

scp -i pemファイル "warファイルのパス" ec2-user@パブリックDNS:~/ 

scp -i nihonbasib_20200915.pem "C:\Users\admin\Desktop\demo.war" ec2-user@ec2-18-182-18-43.ap-northeast-1.compute.amazonaws.com:~/

※ユーザー名(ec2-user)を書き忘れるとPermissionのエラーが出る。
パブリックDNSだけコピーして実行しようとするとエラーになるので注意。

Tomcatの起動とwarファイルの展開 EC2インスタンスssh接続し、転送されたwarファイルを
tomcatがインストールされたディレクトリ/webapps
に配置します。
tomcatは /opt/apache-tomcat
にインストールされている想定です。
インストールされていない場合は講師に確認してください。

$ sudo cp warファイル /opt/apache-tomcat/webapps
# Apacheを起動
$ sudo systemctl start httpd.service
# Tomcatを起動
$ sudo systemctl start tomcat.service

Tomcatの起動に成功すれば、webapps配下でwarファイルが展開されます。
展開されているかどうかはlsコマンドなどで確認してください。

$ sudo ls /opt/apache-tomcat/webapps/

ブラウザから http://パブリックDNS:8080/コンテキストルート
でアクセスして実行結果を確認する。


Gitを活用したデプロイ方法

Eclipseからjarファイルやwarファイルを作成してEC2上に転送するのではなく、直接EC2インスタンス上でjarファイルやwarファイルを作成することも可能です。

前提条件
* EC2インスタンスにGitがインストールされていること
* EC2インスタンスMavenがインストールされていること(Spring Bootを使用する場合)
* GitHub, GitLabなどのホスティングサービスにリモートリポジトリが作成されていること ※.gitignoreでクラスファイルなどが除外されていることを確認して下さい。

# Gitがインストールされていることの確認
$ git version

# 初期設定をしておく
# ユーザー名の設定
git config --global user.name "ユーザー名"
# メールアドレスの設定
git config --global user.email "メールアドレス"

# リモートリポジトリをクローンする
$ git clone リモートリポジトリのURL

# 作業ディレクトリ移動
$ cd クローンされたプロジェクト

Spring Bootの場合

プロジェクトのディレクトリ配下にpom.xmlがあることを確認する。
ビルドする際にpom.xmlがないとうまくいかない。

プロジェクトのビルド

# Mavenがインストールされていることの確認
$ mvn --version
# ビルド
$ mvn package spring-boot:repackage

ビルドが成功すればtargetディレクトリが作成され、中にjarファイルが出来上がります。

Webアプリケーションを実行する jarファイルを使ってアプリケーションを起動します。

java -jar 転送されたjarファイル

ブラウザからアクセスして動作を確かめる http://パブリックDNS:8080/マッピングしているパス

プログラムを停止したい場合
Ctrl + CでTomcatを停止することができます。
ただしサーバーを停止してもプロセスが残っている場合があります。
その場合はpsコマンドでプロセスIDを確認し、killコマンドで終了させる。

# プロセスの確認
$ ps a
# 出力結果からjavaアプリケーションのPIDを確認
# killコマンドでプロセスを削除
$ kill PID

動的Webアプリケーションの場合

参考サイト 「src」と「WebContent」ディレクトリがあるディレクトリに移動しておく。

# ライブラリ用ディレクトリ作成
$ mkdir lib
# servlet-api.jarを移動しておく
$ sudo cp /opt/apache-tomcat/lib/servlet-api.jar lib
# ソースファイルをコンパイルする
$ javac -sourcepath src -classpath lib/* -d WebContent/WEB-INF/classes src/example/*

# warファイルを作成
$ jar cvf webtest.war -C WebContent .

Tomcatの起動とwarファイルの展開 warファイルを
tomcatがインストールされたディレクトリ/webapps
に配置します。
tomcatは /opt/apache-tomcat
にインストールされている想定です。
インストールされていない場合は講師に確認してください。

$ sudo cp warファイル /opt/apache-tomcat/webapps
# Apacheを起動
$ sudo systemctl start httpd.service
# Tomcatを起動
$ sudo systemctl start tomcat.service

Tomcatの起動に成功すれば、webapps配下でwarファイルが展開されます。
展開されているかどうかはlsコマンドなどで確認してください。

$ sudo ls /opt/apache-tomcat/webapps/

ブラウザから http://パブリックDNS:8080/コンテキストルート
でアクセスして実行結果を確認する。