nprogram’s blog

気ままに、プログラミングのトピックについて書いていきます

「Webプログラミングが面白いほどわかる本」を見て仮想環境を作成しました

はじめに

「Webプログラミングが面白いほどわかる本」を見て、仮想環境(Linux OS)の作成したので、その手順を記載します。

本は、こちらです。

https://www.kadokawa.co.jp/product/321712000860

本の通りに、Linux環境構築しようとする

Linux 環境構築時に、長いコマンドを入力する必要があるのですが、コマンド集を用意してくれています。 * GitHub - progedu/commands: 入門コース内で入力が大変なコマンド/URL集

環境構築時には、まずはじめにフォルダーの作成と移動を忘れてはいけません。自分は忘れました。(笑)

  • mkdir %USERPROFILE%\vagrant\ubuntu64_16

  • cd %USERPROFILE%\vagrant\ubuntu64_16

vagrant up時に問題発生 (VBoxManage.exe: error: Details: code E_FAIL (0x80004005))

以下に解決策が記載されていました。 https://www.nnn.ed.nico/questions/7722

(1) Windows10のアップデートをしっかりと行う。
(2) 一度VagrantとVirtualBoxをコントロールパネルのアプリの追加と削除から削除の後、再度教材で指定されたバージョンをインストールし、一度マシン自体を再起動してubuntu64_16フォルダを削除してからやってみてください。
(3) それでもダメであればまたアンインストールして最新のVirtualBoxとVagrantを再インストールの後、再起動してやってみてください。
(4) それでもダメな場合には、Windowsで別な管理者ユーザーを作ってやってみることをおすすめします。Windowsはレジストリ状況がほかのアプリケーションと干渉し正しくアプリケーションが動作しないことが非常に多いため
(5) またそれでもダメな場合にはubuntuの公式サイトからUbuntu16のisoイメージをダウンロードしてVirtualBoxで構築

C:\Users\uv2ut\vagrant\ubuntu64_16>vagrant up
Bringing machine 'default' up with 'virtualbox' provider...
==> default: Importing base box 'ubuntu/xenial64'...
==> default: Matching MAC address for NAT networking...
==> default: Setting the name of the VM: ubuntu64_16_default_1543518701958_70650
Vagrant is currently configured to create VirtualBox synced folders with
the `SharedFoldersEnableSymlinksCreate` option enabled. If the Vagrant
guest is not trusted, you may want to disable this option. For more
information on this option, please refer to the VirtualBox manual:

  https://www.virtualbox.org/manual/ch04.html#sharedfolders

This option can be disabled globally with an environment variable:

  VAGRANT_DISABLE_VBOXSYMLINKCREATE=1

or on a per folder basis within the Vagrantfile:

  config.vm.synced_folder '/host/path', '/guest/path', SharedFoldersEnableSymlinksCreate: false
==> default: Clearing any previously set network interfaces...
==> default: Preparing network interfaces based on configuration...
    default: Adapter 1: nat
==> default: Forwarding ports...
    default: 22 (guest) => 2222 (host) (adapter 1)
==> default: Running 'pre-boot' VM customizations...
==> default: Booting VM...
There was an error while executing `VBoxManage`, a CLI used by Vagrant
for controlling VirtualBox. The command and stderr is shown below.

Command: ["startvm", "7468a4a8-3159-473c-9b37-ea90ff673d8d", "--type", "headless"]

Stderr: VBoxManage.exe: error: The virtual machine 'ubuntu64_16_default_1543518701958_70650' has terminated unexpectedly during startup with exit code 1 (0x1).  More details may be available in 'C:\Users\uv2ut\VirtualBox VMs\ubuntu64_16_default_1543518701958_70650\Logs\VBoxHardening.log'
VBoxManage.exe: error: Details: code E_FAIL (0x80004005), component MachineWrap, interface IMachine

簡単に仮想環境を作れると思っていたのだけど・・・。(´;ω;`)

最新のVirtual Box, Vagrant, Linux OSイメージを使用したいため、すべて用意しなおすことにした

以下の手順で実施しました。

[手順1]モジュールを用意する

最新のVirtual BoxとVagrantとBoxファイルを導入します

  • vagrantのバージョン
    • Vagrant 2.2.1
  • virtual boxのバージョン
    • 5.2.22 r126460 (Qt5.6.2)
  • Linux OS

    • 'ubuntu/trusty64' (v20181103.0.0)
  • パス(URL)を指定してBoxファイルを追加する (ここではubuntu/trusty64を追加する)

    • vagrant box add "ubuntu/trusty64
  • boxファイルの確認
  • vagrant box list
  • ubuntu/trusty64 (virtualbox, 20181103.0.0)

[手順2]Vagrant Fileを編集する

Vagrantfileは、vagrant initを実施したディレクトリ内に作成されます。
デフォルトでは修正前のようになっているので、以下のように修正します。

[修正前]

Vagrant.configure("2") do |config|
  config.vm.box = "base"
end

[修正後]

Vagrant.configure("2") do |config|
  config.vm.box = "ubuntu/trusty64"
end

[手順3] トラブルシューティングを行う・・・

以下のトラブルシューティングを行いました。

[問題1] コンソールのvagrant upコマンドでOS起動失敗

仕方がないので、VirtualBoxのUIからOS起動を試みる。
「仮想化支援機構(VT-x/AMD-V)を有効化できません。」と怒られる。

f:id:nprogram:20181202151844p:plain

[対処方法1]
* VT-x/AMD-Vを有効化 - これは、BIOSで仮想環境のための設定がされていないことが原因。64bitOSをゲストOSにする場合はCPUがVT-xに対応している必要がある。
- https://futurismo.biz/archives/1647/

[問題2] VirtualBoxのUIからOS起動を再び試みる。また怒られる。
f:id:nprogram:20181202153821p:plain

[対処方法2] Windows で Virtualbox が起動しないので、原因を調べたところ、Virtualbox は Hyper-V が有効になっていると、起動できないと言うことがわかりました。
* Vagrant 事始め 番外編 01 - Virtualbox が起動しない - Qiita

[問題3] ようやく直ったと思ったので、次は、コンソールのvagrant upコマンドを実施。以下のコマンドで詰まる・・・。

C:\Users\uv2ut\vagrant\ubuntu64>vagrant up
Bringing machine 'default' up with 'virtualbox' provider...
==> default: Checking if box 'ubuntu/trusty64' is up to date...
==> default: Clearing any previously set forwarded ports...
==> default: Vagrant has detected a configuration issue which exposes a
==> default: vulnerability with the installed version of VirtualBox. The
==> default: current guest is configured to use an E1000 NIC type for a
==> default: network adapter which is vulnerable in this version of VirtualBox.
==> default: Ensure the guest is trusted to use this configuration or update
==> default: the NIC type using one of the methods below:
==> default:
==> default:   https://www.vagrantup.com/docs/virtualbox/configuration.html#default-nic-type
==> default:   https://www.vagrantup.com/docs/virtualbox/networking.html#virtualbox-nic-type
==> default: Clearing any previously set network interfaces...
==> default: Preparing network interfaces based on configuration...
    default: Adapter 1: nat
==> default: Forwarding ports...
    default: 22 (guest) => 2222 (host) (adapter 1)
==> default: Booting VM...
==> default: Waiting for machine to boot. This may take a few minutes...
    default: SSH address: 127.0.0.1:2222
    default: SSH username: vagrant
    default: SSH auth method: private key
Timed out while waiting for the machine to boot. This means that
Vagrant was unable to communicate with the guest machine within
the configured ("config.vm.boot_timeout" value) time period.

If you look above, you should be able to see the error(s) that
Vagrant had when attempting to connect to the machine. These errors
are usually good hints as to what may be wrong.

If you're using a custom box, make sure that networking is properly
working and you're able to connect to the machine. It is a common
problem that networking isn't setup properly in these boxes.
Verify that authentication configurations are also setup properly,
as well.

If the box appears to be booting properly, you may want to increase
the timeout ("config.vm.boot_timeout") value.

[対処方法3] vagrant initコマンドで、vagrant fileを作成しなおして、再度vagrant upコマンドを入力することでうまく行きました。
* private_keyとVMのイメージがずれていることが原因とのこと。そういえば、Virtual BoxとVagrantを削除後、インストールし直したか・・・。 - https://www.nnn.ed.nico/questions/6392

C:\Users\uv2ut\vagrant\ubuntu64>vagrant up
Bringing machine 'default' up with 'virtualbox' provider...
==> default: Importing base box 'ubuntu/trusty64'...
==> default: Matching MAC address for NAT networking...
==> default: Checking if box 'ubuntu/trusty64' is up to date...
==> default: Setting the name of the VM: ubuntu64_default_1543734011399_42011
==> default: Clearing any previously set forwarded ports...
==> default: Fixed port collision for 22 => 2222. Now on port 2200.
==> default: Vagrant has detected a configuration issue which exposes a
==> default: vulnerability with the installed version of VirtualBox. The
==> default: current guest is configured to use an E1000 NIC type for a
==> default: network adapter which is vulnerable in this version of VirtualBox.
==> default: Ensure the guest is trusted to use this configuration or update
==> default: the NIC type using one of the methods below:
==> default:
==> default:   https://www.vagrantup.com/docs/virtualbox/configuration.html#default-nic-type
==> default:   https://www.vagrantup.com/docs/virtualbox/networking.html#virtualbox-nic-type
==> default: Clearing any previously set network interfaces...
==> default: Preparing network interfaces based on configuration...
    default: Adapter 1: nat
==> default: Forwarding ports...
    default: 22 (guest) => 2200 (host) (adapter 1)
==> default: Booting VM...
==> default: Waiting for machine to boot. This may take a few minutes...
    default: SSH address: 127.0.0.1:2200
    default: SSH username: vagrant
    default: SSH auth method: private key
    default:
    default: Vagrant insecure key detected. Vagrant will automatically replace
    default: this with a newly generated keypair for better security.
    default:
    default: Inserting generated public key within guest...
    default: Removing insecure key from the guest if it's present...
    default: Key inserted! Disconnecting and reconnecting using new SSH key...
==> default: Machine booted and ready!
==> default: Checking for guest additions in VM...
    default: The guest additions on this VM do not match the installed version of
    default: VirtualBox! In most cases this is fine, but in rare cases it can
    default: prevent things such as shared folders from working properly. If you see
    default: shared folder errors, please make sure the guest additions within the
    default: virtual machine match the version of VirtualBox you have installed on
    default: your host and reload your VM.
    default:
    default: Guest Additions Version: 4.3.36
    default: VirtualBox Version: 5.2
==> default: Mounting shared folders...
    default: /vagrant => C:/Users/uv2ut/vagrant/ubuntu64

これでようやく仮想環境が作成できました。

起動したUbuntuにアクセスする

Windowsでは、SSHクライアントと呼ばれるソフトウェアを用いることで、利用できます。

Windowsの場合は、追加で、SSHクライアントを用意する必要があります。

私はRLoginというソフトウェアを使用しました。 なお、上記ソフトウェアで接続するために必要な情報は以下のコマンドvagrant ssh-configで取得可能です。

C:\Users\uv2ut\vagrant\ubuntu64>vagrant ssh-config
Host default
  HostName 127.0.0.1
  User vagrant
  Port 2200
  UserKnownHostsFile /dev/null
  StrictHostKeyChecking no
  PasswordAuthentication no
  IdentityFile C:/Users/uv2ut/vagrant/ubuntu64/.vagrant/machines/default/virtualbox/private_key
  IdentitiesOnly yes
  LogLevel FATAL

詳しい設定方法は、本に記載してあります。この本はわかりやすいと思います。

「Webプログラミングが面白いほどわかる本」 https://www.kadokawa.co.jp/product/321712000860

以下はイメージです。
f:id:nprogram:20181202164006p:plain

(補足) VagrantとVirtualBoxが作成したファイル、どこに行った?

C:\Users\ユーザー名.vagrant.d\boxes\以下
vagrant box addを実行すると、VMDK形式のファイルがコピーされる。
vagrant box removeを実行すると、VMDK形式のファイルが削除される。

C:\Users\ユーザー名\VirtualBox VMs\以下
vagrant upを実行すると、VMDK形式のファイルがコピーされる。

あとがき

Windows上で、仮想環境を作ろうとすると、相当大変であることがわかりました・・・。

C++11, C++14, C++17を勉強したい

はじめに

C++11, C++14, C++17を勉強するときに役立つサイト、書籍を紹介します。

サイト

書籍

書籍格安購入サイト (電子書籍版)

オブジェクト指向プログラミングの学習

はじめに

オブジェクト指向型プログラミングを再学習します。

S.O.L.I.Dとは?

オブジェクト指向プログラミングの5の原則のこと

(1) S - 単一責任の原則 (Single Responsibility Principle)

(2) O - 開放・閉鎖原則 (Open/closed principle)

(3) L - リスコフ置換原則 (Liskov substitution principle)

(4) I - インタフェース分離の原則 (Interface segregation principle)

(5) D - 依存性逆転の原則 (Dependency inversion principle)

1. SRP (単一責任の原則:Single Responsibility Principle)

A class should have one and only one reason to change, meaning that a class should have only one job.

「クラスはただ一つの理由で変更すべきだ。つまり、クラスは一つの機能だけを持っているようにすること。」

SRP原則を適用すれば責任領域が明確になりますので、一つの責任を変更することが次々と他の責任の変更になり続ける連鎖作用を防止できます。

更に、責任を適切に分配することにより、コードの可読性の向上,メインテナンスに容易という利点があります。

2. OCP: 開放・閉鎖原則 (Open/closed principle)

オープンクローズドの原則とは、ソフトウェアの構成要素は拡張に対しては開いていなければならず、修正に対しては閉じていなければなりません。

この原則を達成すると、そのソフトウェアの特定の構成要素は、他の構成要素の影響を全く受けなくなります。

また、機能追加をする際も既存の構成要素が受ける影響は限りなく少なくなります。

3. リスコフ置換原則 (Liskov substitution principle)

リスコフの置換原則で望まれるのは、下の例で登場するような置換可能な性質だそうです。 クラスAはクラスBを使っていて、そのBをCに置き換えた際に振る舞いが変わらない場合、CはBの派生形である。 と言えます。

4. インターフェイス分離の原則 (Interface segregation principle)

インターフェイス分離の原則では、必要としないものとの依存を切り離し、予期せぬトラブルの発生を抑えれます。

5. 依存性逆転の原則 (Dependency inversion principle)

依存関係逆転の原則は柔軟なシステムの作り方を教えてくれます。 最も柔軟なシステムは、ソースコードの依存関係がインターフェイスだけを参照している場合です。

参考サイト

postd.cc

Stateパターン [C#]

はじめに

状態を多く持ち、状態ごとの操作の動作が異なるプログラムで、恐ろしく長い条件分岐処理(if文, switch文)を見かけたことはありませんか?

私はよくあります。(^_^;)

たとえば、簡単な電気ポットのプログラムがあるとします。このプログラムでは、以下の状態を持ちます。

  • 電源OFF状態
  • 待機状態
  • 加熱状態
  • 保温状態

また、以下のボタンを持ちます。

  • 電源ボタン
  • 加熱ボタン
  • 停止ボタン

各状態ごとに特定のボタンを押したときの電気ポットの動作は異なります。
(電源OFF状態で加熱ボタンを押しても何も起こらないが、待機状態で加熱ボタンを押すと加熱が始まるなど)

デザインパターンを使わないで、プログラムを記載すると、以下のようになると思います。

if (加熱ボタンが押されたとき)
{
    if (電源OFF状態であるか)
    {
       // 何もしない
    }
    else if (待機状態であるか)
    {
       加熱処理実行();
    }
    else if (加熱状態であるか)
    {
       // 何もしない
    }
    else  if (保温状態であるか)
   {
       加熱処理実行();
   }
}

if (電源ボタンが押されたとき)
{
    // 加熱ボタンが押されたときと同じような条件分岐処理
}

if (停止ボタンが押されたとき)
{
    // 加熱ボタンが押されたときと同じような条件分岐処理
}

ここで、次の新商品では、以下の機能が新しく追加されました。

  • ロックボタンを押すことにより、誤操作を防止する機能

    • ロックボタンとロック状態が追加されました
  • さらに、電気ポットがインターネットに接続できるようになり、自動的に電気ポットのプログラムを更新する機能

    • プログラム更新状態が追加されました
  • ECOモード(省電力モード)ボタンを押すことで、省電力モードに移行する機能

    • ECOモードボタンと省電力状態が追加されました

死ぬ気で機能追加しましたが、次の商品では、以下の機能がさらに追加されます。

  • せっかく、わが社の電気ポットがインターネットにつながったので、Amazon EchoとGoogle Homeとも連携させましょう。(by上司)
  • 競合他社の製品にもある便利機能を10個追加したいです。そこまで工数もかからないでしょう。(by上司)

上記のように機能が増え状態が増えるたびに、状態を特定する条件分岐処理は延々と長くなり、コード修正・コードテストしにくくなります…。

if文、switch文地獄の始まりです…。

上記のように、多くの状態を持ち、状態ごとの操作のふるまいが異なる場合こそ、Stateパターンの出番です。

Stateパターンを用いることで、条件分岐処理をなくすことが可能です。

電気ポットのクラス図とステートマシン図は以下のとおりです。

クラス図

f:id:nprogram:20181123130526p:plain

重要クラス説明

クラス名 クラス説明
IState インターフェースクラスであり、各状態で異なる振る舞いをする操作(関数)を定義する
PowerOffState 電源OFF状態を表すクラス。IStateクラスを継承する。
IdleState 待機状態を表すクラス。IStateクラスを継承する。
HeatState 加熱状態を表すクラス。IStateクラスを継承する。
WarmState 保温状態を表すクラス。IStateクラスを継承する。
PodContext 電気ポット制御情報クラス。本クラスが、IStateクラスのインスタンスを保有する

ステートマシン図 (状態遷移図)

f:id:nprogram:20181116232019p:plain

実行イメージ [C#]

コマンドを指定してください。
0 : 現在の状態を表示する
1 : 電源ボタンを押す
2 : 加熱ボタンを押す
3 : 停止ボタンを押す
4 : ヒーターモードを弱に変更する(固定(Min:30度, Max:60度))
5 : ヒーターモードを強に変更する(固定(Min:70度, Max:100度))

[IState : State_Pattern_CSharp.State.PowerOffState] [Current Temperature : 20] [Heater Mode : High]
[IState : State_Pattern_CSharp.State.IdleState] [Current Temperature : 20] [Heater Mode : Low]
[IState : State_Pattern_CSharp.State.HeatState] [Current Temperature : 25] [Heater Mode : Low]
[IState : State_Pattern_CSharp.State.HeatState] [Current Temperature : 50] [Heater Mode : Low]
[IState : State_Pattern_CSharp.State.HeatState] [Current Temperature : 60] [Heater Mode : Low]
[IState : State_Pattern_CSharp.State.WarmState] [Current Temperature : 57] [Heater Mode : Low]
[IState : State_Pattern_CSharp.State.WarmState] [Current Temperature : 54] [Heater Mode : Low]
[IState : State_Pattern_CSharp.State.WarmState] [Current Temperature : 30] [Heater Mode : Low]
[IState : State_Pattern_CSharp.State.HeatState] [Current Temperature : 35] [Heater Mode : Low]
[IState : State_Pattern_CSharp.State.HeatState] [Current Temperature : 40] [Heater Mode : Low]

コード説明

コードでは、タイマークラスを用いて、1秒間ごとに、外気温によるポットの水温低下(3度)、加熱による水温上昇(5度)を再現しています。

コード (インタフェースクラス)

以下がIStateインターフェースクラスです。各状態で、その操作実施時に異なる動作となる操作を定義します。

    /// <summary>
    /// 電気ポット状態を表すクラス
    /// </summary>
    public interface IState
    {
        IState PushPowerBtnEvent();
        IState PushStopBtnEvent();
        IState PushHeatBtnEvent();
        IState MeasureTemperatureEvent(HeaterContext someHeaterContext);
    }

状態クラス

状態クラスでは、状態ごとの各動作実施、状態遷移先の判断と遷移先の決定のみ行うようにします。他の機能を一切持たせません。

    /// <summary>
    /// 待機状態 (非加熱状態)
    /// </summary>
    public class IdleState : IState
    {
        public IdleState()
        {
        }

        public IState PushPowerBtnEvent()
        {
            return new PowerOffState();
        }

        public IState PushStopBtnEvent()
        {
            return this;
        }

        public IState PushHeatBtnEvent()
        {
            return new HeatState();
        }

        public IState MeasureTemperatureEvent(HeaterContext someHeaterContext)
        {
            return this;
        }
    }

コード (Contextクラス)

電気ポット制御情報クラスです。本クラスが、電気ポットが現在どの状態にあるかを表すIState変数をもちます。

    /// <summary>
    /// 電気ポット制御情報クラス
    /// </summary>
    public class PotContext
    {
        /// <summary>
        /// 電気ポットの稼働状態
        /// </summary>
        private IState state = null;

        /// <summary>
        /// ヒーター設定パラメーター
        /// </summary>
        public HeaterContext HeaterContext { get; set; } = null;

        public PotContext(HeaterContext someHeaterContext)
        {
            this.HeaterContext = someHeaterContext;

            if (state == null)
            {
                state = new PowerOffState();
            }
        }

        public IState GetState()
        {
            return this.state;
        }

        public void ShowCurrentState()
        {
            Console.WriteLine("[IState : {0}] [Current Temperature : {1}] [Heater Mode : {2}]\n\n\n", this.state.ToString(), Thermometer.CurrentTemp, this.HeaterContext.HeaterMode.ToString());
        }

        public void PushPowerBtn()
        {
            this.state = this.state.PushPowerBtnEvent();
        }

        public void PushStopBtn()
        {
            this.state = this.state.PushStopBtnEvent();
        }

        public void PushHeatBtn()
        {
            this.state = this.state.PushHeatBtnEvent();
        }

        public void MeasureTemperature()
        {
            this.state = this.state.MeasureTemperatureEvent(this.HeaterContext);
        }
    }

まとめ

Stateパターンを用いることで、現在何の状態であるかという条件分岐を削除することができました。

ただし、状態が増えることによって、クラスが必ず1つ追加されるというデメリットは存在します。

うまく使いこなすには、本当に腕が必要なデザインパターンだと思いました。

状態クラス内の設計について

本プログラムでは、各状態クラス内で、遷移先の状態のインスタンス化を実施していますが、IDEで確認したところ、メモリリークは発生しませんでした。

これは、状態クラスで、データを保有せず、必ずガーベッジコレクションによって、生成したインスタンス化が破棄されているからと思われます。

状態クラス内で、別のデータを持とうとすると、どこかのクラスからそのデータを参照されることによって、

ガーベッジコレクションが働かず、メモリリークが発生すると思います。その場合は、状態クラスでは、シングルトンパターンを採用したほうがよさそうです。

コード全体

  • コード全体は以下にあります。 github.com

参考リンク

Compositeパターン [C#]

Compositeパターン

Compositeパターンは、容器と中身を同一視して、再帰的な構造を作るパターンです。 利用者は容器と中身を意識せずに使用することができます。

Compositeパターンを使用しない場合は、容器と中身を意識する必要がある。 コード例では、DirecotryクラスのListに追加したインスタンスがフォルダーなのか、ファイルなのか、判定する処理が発生します。

クラス図

f:id:nprogram:20181112224053p:plain

クラス説明

クラス名 クラス説明
IEntry FolderとFileを同一視するインターフェース
Directory フォルダークラス
File ファイルクラス
Program ユーザークラス

実行イメージ

f:id:nprogram:20181107222825p:plain

コード例

using System;
using System.Collections.Generic;

namespace Composite
{
    class Program
    {
        static void Main(string[] args)
        {
            Directory root = new Directory("root");
            Directory dir1 = new Directory("dir1");
            Directory dir2 = new Directory("dir2");
            Directory dir3 = new Directory("dir3");

            File file1 = new File("file1");
            File file2 = new File("file2");
            File file3 = new File("file3");
            File file4 = new File("file4");

            // フォルダーを追加する
            root.AddEntry(dir1);
            root.AddEntry(dir2);
            dir2.AddEntry(dir3);

            // ファイルを追加する
            root.AddEntry(file1);
            root.AddEntry(file2);
            dir2.AddEntry(file3);
            dir3.AddEntry(file4);

            root.Output(0);

            Console.ReadLine();
        }
    }

    public interface IEntry
    {
        void Output(int someDepth);
    }


    public class Directory : IEntry
    {
        private string Name = null;
        private List<IEntry> Entries = new List<IEntry>();

        public Directory(string someName)
        {
            this.Name = someName;
        }

        public void AddEntry(IEntry someEntry)
        {
            Entries.Add(someEntry);
        }

        public void Output(int someDepth)
        {
            for (int i = 0; i < someDepth; i++)
            {
                Console.Write("    ");
            }

            Console.WriteLine("{0} : {1}", this.GetType().ToString(), this.Name);

            foreach (var item in Entries)
            {
                // 階層を1つ深くする
                item.Output(someDepth + 1);
            }
        }
    }

    public class File : IEntry
    {
        private string Name = null;

        public File(string someName)
        {
            this.Name = someName;
        }

        public void Output(int someDepth)
        {
            for (int i = 0; i < someDepth; i++)
            {
                Console.Write("    ");
            }

            Console.WriteLine("{0} : {1}", this.GetType().ToString(), this.Name);
        }
    }
}

Abstract Factory パターン [C#]

Abstract Factory パターン [C#]

Abstract Factory パターンを記載します。

Abstract Factoryパターンを使うことによって、クラスの使用者は、具象クラスを直接扱わずに、具職クラスのインスタンスを取得できる。
(Main関数で扱っているのは、抽象クラスのみである。)

クラス図

f:id:nprogram:20181104232706p:plain

実行イメージ

f:id:nprogram:20181104235003p:plain

using System;
using System.Collections.Generic;

namespace ConsoleApp2
{
    class Program
    {
        static void Main(string[] args)
        {
            CurryRiceAbstractFactory tomatoCurry = CreateFactory("Tomato Curry Rice");
            tomatoCurry.Order();


            CurryRiceAbstractFactory indianCurryRice = CreateFactory("Indian Curry Rice");
            indianCurryRice.Order();

            Console.ReadLine();
        }

        private static CurryRiceAbstractFactory CreateFactory(String str)
        {
            if (str == "Tomato Curry Rice")
            {
                return new TomatoCurryFactory(str);
            }
            else if (str == "Indian Curry Rice")
            {
                return new IndianCurryRice(str);
            }

            return null;
        }

        public abstract class CurryRiceAbstractFactory
        {
            private string Name;
            private Soup Soup;
            private List<Spice> SpiceList = new List<Spice>();
            private List<Ingredients> IngredientsList = new List<Ingredients>();
            private List<Vegetable> VegetableList = new List<Vegetable>();


            abstract public Soup GetSoup();
            abstract public List<Spice> GetSpiceList();
            abstract public List<Ingredients> GetIngredientsList();
            abstract public List<Vegetable> GetVegetableList();

            public void Order()
            {
                Console.WriteLine("---- Get a " + GetName() + " Recipe ----");
                GetRecipe();
                Console.WriteLine();

                Console.WriteLine("---- Cooking a " + GetName() + " ----");
                Cut();
                StirFry();
                Boil();
                Serve();
                Console.WriteLine();
            }

            public string GetName()
            {
                return this.Name;
            }

            public void SetName(string someName)
            {
                this.Name = someName;
            }

            public void Cut()
            {
                Console.WriteLine("Cut");
            }

            public void Boil()
            {
                Console.WriteLine("Boil");
            }

            public void StirFry()
            {
                Console.WriteLine("Stir fry");
            }

            public void Serve()
            {
                Console.WriteLine("Here you are. This is " + GetName());
            }

            public void GetRecipe()
            {
                Console.WriteLine("---- Soup ----");
                Console.WriteLine(GetSoup().Name);

                Console.WriteLine("---- Spice ----");
                GetSpiceList().ForEach(item =>
                {
                    Console.WriteLine(item.Name);

                });

                Console.WriteLine("---- Ingredients ----");
                GetIngredientsList().ForEach(item =>
                {
                    Console.WriteLine(item.Name);

                });

                Console.WriteLine("---- Vegetable ----");
                GetVegetableList().ForEach(item =>
                {
                    Console.WriteLine(item.Name);

                });
            }
        }

        public class TomatoCurryFactory : CurryRiceAbstractFactory
        {
            public TomatoCurryFactory(string str)
            {
                SetName(str);
            }

            public override Soup GetSoup()
            {
                return new TomatoSoup();
            }

            public override List<Spice> GetSpiceList()
            {
                List<Spice> list = new List<Spice>();

                list.Add(new GaramMasala());
                list.Add(new Turmeric());

                return list;
            }

            public override List<Ingredients> GetIngredientsList()
            {
                List<Ingredients> list = new List<Ingredients>();

                list.Add(new Pork());

                return list;
            }

            public override List<Vegetable> GetVegetableList()
            {
                List<Vegetable> list = new List<Vegetable>();

                list.Add(new Tomato());
                list.Add(new Carrot());

                return list;
            }

        }

        public class IndianCurryRice : CurryRiceAbstractFactory
        {
            public IndianCurryRice(string str)
            {
                SetName(str);
            }

            public override Soup GetSoup()
            {
                return new OnionSoup();
            }

            public override List<Spice> GetSpiceList()
            {
                List<Spice> list = new List<Spice>();

                list.Add(new GaramMasala());
                list.Add(new Turmeric());

                return list;
            }

            public override List<Ingredients> GetIngredientsList()
            {
                List<Ingredients> list = new List<Ingredients>();

                list.Add(new Chicken());

                return list;
            }

            public override List<Vegetable> GetVegetableList()
            {
                List<Vegetable> list = new List<Vegetable>();

                list.Add(new Tomato());
                list.Add(new Carrot());
                list.Add(new GreenPapper());

                return list;
            }
        }

        #region Spice
        public abstract class Spice
        {
            public string Name;
            public string Color;
        }

        public class GaramMasala : Spice
        {
            public GaramMasala()
            {
                this.Name = "Garam masala";
                this.Color = "Yellow";
            }

        }

        public class Turmeric : Spice
        {
            public Turmeric()
            {
                this.Name = "Turmeric";
                this.Color = "Yellow";
            }
        }
        #endregion Spice

        #region Soup
        public abstract class Soup
        {
            public string Name;
        }

        public class OnionSoup : Soup
        {
            public OnionSoup()
            {
                this.Name = "Onion soup";
            }
        }

        public class TomatoSoup : Soup
        {
            public TomatoSoup()
            {
                this.Name = "Tomato soup";
            }
        }
        #endregion Soup

        #region Ingredients
        public abstract class Ingredients
        {
            public string Name;
        }

        public class Pork : Ingredients
        {
            public Pork()
            {
                this.Name = "Pork";
            }
        }

        public class Beef : Ingredients
        {
            public Beef()
            {
                this.Name = "Beef";
            }
        }

        public class Chicken : Ingredients
        {
            public Chicken()
            {
                this.Name = "Chicken";
            }
        }
        #endregion Ingredients

        #region Vegetables
        public abstract class Vegetable
        {
            public string Name;
        }

        public class Carrot : Vegetable
        {
            public Carrot()
            {
                this.Name = "Carrot";
            }
        }

        public class GreenPapper : Vegetable
        {
            public GreenPapper()
            {
                this.Name = "GreenPapper";
            }
        }

        public class Tomato : Vegetable
        {
            public Tomato()
            {
                this.Name = "Tomato";
            }
        }
        #endregion Vegetables
    }
}

以下の記事に記載されています。

http://www.techscore.com/tech/DesignPattern/AbstractFactory.html/

Factory Methodパターン [C#]

Factory Methodパターン

インスタンス化したいオブジェクト(製品)を実行時の条件によって決めたい場合に利用します。
Factory Methodパターンはオブジェクト(製品)を生成する側と利用する側に分けて定義する必要があります。
分けておくことで、将来システムに起こりえる変更をあらかじめ分離でき保守性を保つことができます。

パターン構造

f:id:nprogram:20181116223913p:plain

構成要素

  • Product : 生成されるオブジェクト(製品)のAPIを定義する抽象クラス。製品の具象クラスが抽象クラスで使用できるようにする
  • ConcreteProduct : 生成される具象製品クラス
  • Creator : ファクトリメソッドを除く製品を操作するAPI抽象クラスを定義する抽象クラス
  • ConcreteCreator : ファクトリメソッドを実装し、実際に製品を作成するクラス

クラス図

f:id:nprogram:20181116224953p:plain

コード

using System;

namespace Test
{
    class Program
    {
        static void Main(string[] args)
        {

            PizzaStore NYPizzaFactory = new NYPizzaFactory();
            PizzaStore ChicagoPizzaFactory = new ChicagoPizzaFactory();
            
            Pizza pizzaNYCheese = NYPizzaFactory.OrderPizza("Cheese");
            Pizza pizzaNYTomato = NYPizzaFactory.OrderPizza("Tomato");
            Pizza pizzaChicagoCheese = ChicagoPizzaFactory.OrderPizza("Cheese");
            Pizza pizzaChicagoTomato = ChicagoPizzaFactory.OrderPizza("Tomato");
            
            Console.ReadLine();
        }
    }

    /// <summary>
    /// ピザを作成するファクトリークラス (抽象クラス)
    /// </summary>
    public abstract class PizzaStore
    {
        public abstract Pizza CreatePizza(string type);

        public Pizza OrderPizza(string type)
        {
            Pizza pizza;

            pizza = CreatePizza(type);

            if (pizza == null) return null;

            // すべてのピザに対して
            pizza.Prepare();
            pizza.Bake();
            pizza.Cut();
            pizza.Box();

            return pizza;
        }
    }

    /// <summary>
    /// ニューヨークのピザファクトリークラス (具象クラス)
    /// </summary>
    public class NYPizzaFactory : PizzaStore
    {
        public override Pizza CreatePizza(string type)
        {
            Pizza pizza = null;

            if (type == "Cheese")
            {
                pizza = new NYStyleCheesePizza(type);

            }
            else if (type == "Tomato")
            {
                pizza = new NYStyleCheesePizza(type);
            }

            return pizza;
        }
    }

    /// <summary>
    /// シカゴのピザファクトリークラス
    /// </summary>
    public class ChicagoPizzaFactory : PizzaStore
    {
        public override Pizza CreatePizza(string type)
        {
            Pizza pizza = null;

            if (type == "Cheese")
            {
                pizza = new ChicagoStyleCheesePizza(type);

            }
            else if (type == "Tomato")
            {
                pizza = new ChicagoStyleCheesePizza(type);
            }

            return pizza;
        }
    }

    /// <summary>
    /// 抽象Pizzaクラス。
    /// </summary>
    public abstract class Pizza
    {
        /// <summary>
        /// ピザの名前
        /// </summary>
        public string Name;

        /// <summary>
        /// ピザの生地の厚さ
        /// </summary>
        public string Thickness;

        /// <summary>
        /// ピザのチーズ量
        /// </summary>
        public string AmountOfCheese;

        /// <summary>
        /// スタイル
        /// </summary>
        /// <remarks>
        /// ニューヨーク風、シカゴ風など
        /// </remarks>
        public string Style;


        public void Prepare()
        {
            Console.WriteLine(this.Style + "ピザの作成を開始します。");
            Console.WriteLine(this.Name + "ピザの材料を準備します。");
            Console.WriteLine("ピザの生地は" + this.Thickness + "します。");
            Console.WriteLine("チーズ量は" + this.AmountOfCheese + "します。");
        }

        public void Bake()
        {
            Console.WriteLine(this.Name + "ピザを焼きます。");
        }

        public void Cut()
        {
            Console.WriteLine(this.Name + "ピザをカットします。");
        }

        public void Box()
        {
            Console.WriteLine(this.Name + "ピザを箱に収納します。");
            Console.WriteLine();
        }
    }

    /// <summary>
    /// 具象Pizzaクラス (チーズピザ : ニューヨーク風)
    /// </summary>
    public class NYStyleCheesePizza : Pizza
    {
        public NYStyleCheesePizza(string someName)
        {
            this.Name = someName;
            this.Style = "ニューヨーク風";
            this.Thickness = "薄く";
            this.AmountOfCheese = "少なく";
        }
    }

    /// <summary>
    /// 具象Pizzaクラス (トマトピザ : ニューヨーク風)
    /// </summary>
    public class NYStyleTomatoPizza : Pizza
    {

        public NYStyleTomatoPizza(string someName)
        {
            this.Name = someName;
            this.Style = "ニューヨーク風";
            this.Thickness = "薄く";
            this.AmountOfCheese = "少なく";
        }
    }
    /// <summary>
    /// 具象Pizzaクラス (チーズピザ : シカゴ風)
    /// </summary>
    public class ChicagoStyleCheesePizza : Pizza
    {

        public ChicagoStyleCheesePizza(string someName)
        {
            this.Name = someName;
            this.Style = "シカゴ風";
            this.Thickness = "厚く";
            this.AmountOfCheese = "多く";
        }
    }

    /// <summary>
    /// 具象Pizzaクラス (トマトピザ : シカゴ風)
    /// </summary>
    public class ChicagoStyleTomatoPizza : Pizza
    {

        public ChicagoStyleTomatoPizza(string someName)
        {
            this.Name = someName;
            this.Style = "シカゴ風";
            this.Thickness = "厚く";
            this.AmountOfCheese = "多く";
        }
    }
}
ニューヨーク風ピザの作成を開始します。
Cheeseピザの材料を準備します。
ピザの生地は薄くします。
チーズ量は少なくします。
Cheeseピザを焼きます。
Cheeseピザをカットします。
Cheeseピザを箱に収納します。

ニューヨーク風ピザの作成を開始します。
Tomatoピザの材料を準備します。
ピザの生地は薄くします。
チーズ量は少なくします。
Tomatoピザを焼きます。
Tomatoピザをカットします。
Tomatoピザを箱に収納します。

シカゴ風ピザの作成を開始します。
Cheeseピザの材料を準備します。
ピザの生地は厚くします。
チーズ量は多くします。
Cheeseピザを焼きます。
Cheeseピザをカットします。
Cheeseピザを箱に収納します。

シカゴ風ピザの作成を開始します。
Tomatoピザの材料を準備します。
ピザの生地は厚くします。
チーズ量は多くします。
Tomatoピザを焼きます。
Tomatoピザをカットします。
Tomatoピザを箱に収納します。

参考リンク

https://gist.github.com/fujimisakari

事例で学ぶデザインパターン 第 4 回 | オブジェクトの広場