nprogram’s blog

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

Observerパターンについて

Observerとは、英語で観察者を意味します。

Observer パターンとは、状態の変化を観察することを目的としたものですが、どちらかというと「観察」よりも「通知」に重点の置かれたものになっています。

あるインスタンスの状態が変化した際に、そのインスタンス自身が、「観察者」に状態の変化を「通知」する仕組みです。

<Observerパターンの定義 : クラス図>

f:id:nprogram:20170816230359p:plain

[ObserverSample.h]

#include <vector>
#include <algorithm>
#include <iostream>

class Observer {
    friend class Subject;
public:
    virtual ~Observer() {}
protected:
    virtual void update(Subject*) = 0;
};

class Subject {
private:
    std::vector<Observer*> obs_;

public:
    // Observerを登録
    void add_observer(Observer* o) {
        if (std::find(obs_.begin(), obs_.end(), o) == obs_.end()) {
            obs_.push_back(o);
        }
    }

    // Observerの登録抹消
    void delete_observer(Observer* o) {
        auto iter = std::find(obs_.begin(), obs_.end(), o);
        if (iter != obs_.end()) {
            obs_.erase(iter);
        }
    }
protected:
    // 登録されたObserverのハンドラを呼び出す
    // std::for_each ⇒ 範囲の全ての要素に、指定された関数を適用する
    // oの全ての要素にラムダ式を適用する
    // 【thisのキャプチャ】
    // メンバ関数内では、thisをキャプチャすることでラムダ式内でメンバ変数、メンバ関数(update)を使用することができます。
    // 下記のラムダ式では、thisをキャプチャして、vector[obs_]の各要素のObserverオブジェクトのupdateメソッドを使用しています。
    void notify() {
        std::for_each(obs_.begin(), obs_.end(),
            [this](Observer* o) { o->update(this); });
    }
};

// 具象サブジェクト
// 室温
class RoomTemp : public Subject {
private:
    int value_; //
public:
    RoomTemp(int initial) : value_(initial) {}
    void increment() // 室温を1度上げる
    {
        ++value_;
        notify();
    }
    void decrement() // 室温を1度下げる
    {
        --value_;
        notify();
    }
    int get_temp() const // 現在の室温
    {
        return value_;
    }
};

// 具象オブサーバクラス
// 温度表示パネル : 室温の変化に応じて室温を表示する
class TempDisp : public Observer {
public:
    ~TempDisp() { std::cout << "お疲れ様でしたー" << std::endl; }
protected:
    virtual void update(Subject* from) {
        int temp = static_cast<RoomTemp*>(from)->get_temp();
        std::cout << "現在の温度: " << temp << "℃" << std::endl;
    }
};

[main.cpp]

#include "ObserverSample.h"


int main() {
    RoomTemp room1(20);
    RoomTemp room2(30);
    TempDisp disp1;
    TempDisp disp2;
    room1.add_observer(&disp1);
    room2.add_observer(&disp2);
    room1.increment();
    room1.increment();
    room1.increment();
    room2.decrement();
    room2.decrement();
    room2.decrement();
}

実行結果

f:id:nprogram:20170816230627p:plain

以下のサイトを参考にさせていただきました。

codezine.jp

Strategy Patternについて

[Duch.h]

#include <iostream>
using namespace std;

//QuackBehavior インターフェース
class QuackBehavior
{
public:
    virtual ~QuackBehavior() {}
    virtual void quack() const = 0; //純粋仮想関数(派生クラスに)
};

//QuackBehaviorインターフェースを実装したQuack, Squeak, MuteQuack クラス
class Quack : public QuackBehavior
{
public:
    void quack() const
    {
        cout << "gua-gua" << endl;
    }
};
class Squeak : public QuackBehavior
{
public:
    void quack() const
    {
        cout << "kyu-kyu" << endl;
    }
};

//なにもしない(鳴かない)クラス(Nullオブジェクト)
class MuteQuack : public QuackBehavior
{
public:
    void quack() const
    {
        cout << "<<mute>>" << endl;//何もしていないつもりだが、確認のため、出力する
    }
};

// Duck(基底クラス)と種々の派生クラス
class Duck
{
protected:
    QuackBehavior* q;
public:
    virtual ~Duck() { delete q; };
    //virtual を入れなかったのは、Duckクラス共通の振る舞いだから
    //デフォルトの実装と派生クラス独自の実装を分けたい場合は、virtualを付ける
    void quack() const
    {
        q->quack();
    }
};

class RedHeadDuck : public Duck
{
private:
public:
    RedHeadDuck()
    {
        q = new Quack();
    }
};

class RubberDuck : public Duck
{
private:
public:
    RubberDuck()
    {
        q = new Squeak();
    }
};

class DecoyDuck : public Duck
{
private:
public:
    DecoyDuck()
    {
        q = new MuteQuack();
    }
};

[main.cpp]

#include "Duck.h"

int main(void)
{
    Duck* d1 = new RedHeadDuck();
    Duck* d2 = new DecoyDuck();
    Duck* d3 = new RubberDuck();
    //RedHeadDuck()
    //  //基底クラスにアップキャスト
    //  QuackBehavior* q = new Quack();

    // [オブジェクト] [-> : アロー演算子] [メソッド(引数1..)]
    d1->quack();
    d2->quack();
    d3->quack();
    //  q->quack(); // qはQuackBehavior*型でquackメンバ関数は仮想関数なので、派生クラス(Quack)のquackメンバ関数が動的に実行される。

    delete d1;
    delete d2;
    delete d3;

    return 0;
}

実行結果

Visual Studio CommunityからCtrl + F5を押すと、派生クラスのメソッドに従った振る舞いをします。 f:id:nprogram:20170816112833p:plain

デザインパターン入門 (勉強するぞー)

HeadFirstデザインパターンの本のコードのサンプルは、GitHubにあります。

http://www.wickedlysmart.com/headfirstdesignpatterns/code.htmlgithub.com

以下の本を使ってこれから、学習を進めていきたいと思います。

どうぞよろしくお願いいたします。

Head Firstデザインパターン ―頭とからだで覚えるデザインパターンの基本

また、とても、いいページを見つけたので、載せておきます。

http://qiita.com/knknkn1162/items/9a65ca65b558f31121f0

Qtでcsvファイルに書き込む方法

QTextStreamを用いることで、csvファイルに保存することが可能です。

csvファイル保存の形式に合わせるため、データごとにカンマで区切ります。今回は、10個のデータごとに改行します。

今回使用するデータは、日本語文字列データを使用しました。

以下がコードです。

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QString>
#include <QDebug>
#include <QFile>
#include <QTextStream>
#include <QFileDialog>
#include <QMessageBox>
#define Jstr(str) QString::fromLocal8Bit(str)

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);
}

MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::on_pushButton_clicked()
{

    // csvファイルに書き込むQStringListを作成
    QStringList writeList;
    QString str = Jstr("日本語");

    for (int i = 1; i <= 100; i++) {

        if (i % 10){
            writeList.append(str + " : " + QString::number(i) + ",");
        } else {
            // 改行コードはLFです
            writeList.append(str + " : " + QString::number(i) + "\n");
        }
    }

    // fileNameとfile pathを求める
    QString fileName = QFileDialog::getSaveFileName(this,
        tr("Save Paint Info"), "",
        tr("Paint info (*.csv);;All Files (*)"));

    if (fileName.isEmpty()) return;

    QFile file(fileName);

    if (!file.open(QIODevice::WriteOnly)) {
            QMessageBox::information(this, tr("Unable to open file"),
                file.errorString());
            return;
    }

    // デバッグ表示
    foreach (QString item, writeList) {
        qDebug() << item;
    }

    // csvファイルに書き込む
    QTextStream out(&file);
    foreach (QString item, writeList) {
        out << item;
    }
}

実行結果

不明なコンパイル時の出力はありますが、無事、csvファイルにデータを書き込むことができました。

f:id:nprogram:20170810082054p:plain

f:id:nprogram:20170810082128p:plain

Qt5で日本語文字列を扱おうとすると文字化けするので、その対処方法

文字列リテラルを QString へ変換する

Qt5では文字列リテラルを QString へ変換するには主に以下のメソッドを使用可能。

  • QString::fromLatin1()

    • Latin1(ISO-8859-1) として QString を作成
  • QString::fromLocal8Bit()

    • 現在のロケールに合わせたコーデックを使用して QString を生成します
  • QString::fromUtf8()

    • 文字列が UTF-8 として QString を作ります

以下にマクロを使用しないパターンと使用するパターンを記載します。

[マクロ未使用パターン]

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QString>
#include <QDebug>

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);
}

MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::on_pushButton_clicked()
{
    QStringList qStrList;

    // QString::fromLocal8Bit() ⇒ 現在のロケールに合わせたコーデックを使用して QString を生成します
    QString temp = QString::fromLocal8Bit("日本語");


    for (int i = 0; i < 100; i++) {
        QString qStrTest = temp;
        qStrList.append(qStrTest);
    }

    foreach (QString item, qStrList) {
        qDebug() << item;
    }
}

[マクロ使用パターン]

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QString>
#include <QDebug>

#define Jstr(str) QString::fromLocal8Bit(str)

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);
}

MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::on_pushButton_clicked()
{
    QStringList qStrList;

    QString temp = Jstr("日本語");

    for (int i = 0; i < 100; i++) {
        QString qStrTest = temp;
        qStrList.append(qStrTest);
    }

    foreach (QString item, qStrList) {
        qDebug() << item;
    }
}

実行結果

実行結果は、以下のとおりです。日本語文字列を使用しても、文字化けが発生しません。

f:id:nprogram:20170810074356p:plain

以下の記事のサイトを参考にさせていただきました。ありがとうございます。

qt-labs.jp

  • Qtのマクロについて

loops.at.webry.info

ユーザー定義クラス(独自クラス)のリストをQDataStreamにセーブ・ロードする方法

作成環境について

Qt Creator 4.3.1で、以下の設定で、プロジェクトを作成しております。以下の設定でクラス名、cppファイル名、hファイル名はすべてデフォルトを使用しています。

  • Qtウィジェットアプリケーション
  • 基底クラスは、QMainWindows
  • フォームを生成する

プログラムについて

メインウィンドウ終了時に、ユーザー定義クラスのリストをQDataStreamにセーブします。

再度、アプリケーションを実行したときに、前回終了時に保存したユーザー定義クラスのリストの情報を取り出すプログラムです。

変更ファイルは以下のとおり。

  • mainwindow.cpp
  • mainwindow.h

新規追加ファイルは以下のとおり。Paintクラスはユーザー定義クラス(独自クラス)となります。 * paint.cpp * paint.h

[mainwindow.cpp]

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QMessageBox>
#include <QSettings>
#include <QSize>
#include <QDataStream>
#include <QFile>
#include <QDebug>
#include <iostream>

#include "paint.h"

const QString FILE_NAME = "out.txt";

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    QFile file(FILE_NAME);
    file.open(QIODevice::ReadOnly);
    QDataStream in (&file);
    in.setVersion(QDataStream::Qt_5_9);

    // QDataStreamから、paintListの要素数を読み出す
    int paintListSize = 0;
    in >> paintListSize;

    // QDataStreamから、paintListの要素数分、ユーザー定義クラス(Paintクラス)の情報を読み出す
    for (int i = 0; i < paintListSize; i++) {
        Paint paint;
        in >> paint;
        std::cout << "title : " << paint._title.toStdString() << std::endl;
        std::cout << "age : " << paint._year << std::endl;
    }
}

MainWindow::~MainWindow()
{
    delete ui;
}

// Qtでは、closeEventをオーバーライドする事で実現可能です。
void MainWindow::closeEvent( QCloseEvent *event ){

    // ユーザー定義クラス(Paintクラス)をQLinkedListに追加する
    for (int i = 0; i < 5; i++) {
        Paint paint("Paint", i + 1900);
        QVariant vPaint = QVariant::fromValue(paint);
        paintList.append(vPaint);
    }

    QFile file(FILE_NAME);
    file.open(QIODevice::WriteOnly);
    QDataStream out (&file);
    out.setVersion(QDataStream::Qt_5_9);

    // QDataStreamに、paintListの要素数を格納する
    out << paintList.size();

    // QDataStreamに、ユーザー定義クラス(Paintクラス)を格納する
    foreach (QVariant item, paintList) {
        Paint paint =  item.value<Paint>();
        out << paint;
    }
}

[mainwindow.h]

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QLinkedList>

namespace Ui {
class MainWindow;
}

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    explicit MainWindow(QWidget *parent = 0);
    ~MainWindow();

private:
    Ui::MainWindow *ui;

protected:
    virtual void closeEvent( QCloseEvent *event );
    QLinkedList<QVariant> paintList;

};

#endif // MAINWINDOW_H

[paint.cpp]

#include "paint.h"

QDataStream &operator << (QDataStream &out, const Paint &paint)
{
    out << paint._title << qint32(paint._year);
    return out;
}

QDataStream &operator >> (QDataStream &in, Paint &paint)
{
    QString title;
    qint32 year;

    in >> title >> year;

    paint = Paint(title, static_cast<int>(year));

    return in;
}

[paint.h]

#ifndef PAINT_H
#define PAINT_H

#include <QString>
#include <QDataStream>

class Paint
{
public:
    QString _title;
    int _year;

public:
    Paint(){}
    Paint(QString title, int year) {
        _title = title;
        _year = year;
    }
};

// Q_DECLARE_METATYPEマクロにより、QMetaTypeに独自の型を登録
Q_DECLARE_METATYPE(Paint)

QDataStream &operator << (QDataStream &out, const Paint &paint);
QDataStream &operator >> (QDataStream &in, Paint &paint);

#endif // PAINT_H

実行結果

以下のように、前回終了時に保存したユーザー定義クラスのリストの情報を取り出すができました。

f:id:nprogram:20170808063925p:plain

QSettingsを用いて、画面のサイズをセーブ・ロードしよう

作成環境について

Qt Creator 4.3.1で、以下の設定で、プロジェクトを作成しております。以下の設定でクラス名、cppファイル名、hファイル名はすべてデフォルトを使用しています。

  • Qtウィジェットアプリケーション
  • 基底クラスは、QMainWindows
  • フォームを生成する

プログラムについて

画面終了時の画面の幅と高さをQSettingsに格納します。

再度、アプリケーションを実行したときに、前回起動時の画面の幅と高さを再現するプログラムです。

修正コードは以下の2か所

  • mainwindow.cpp
  • mainwindow.h

[mainwindow.cpp]

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QMessageBox>
#include <QSettings>
#include <QSize>

const QString ORG_NAME = "My Company";
const QString APP_NAME = "My Tool";

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    QSettings settings("My Company", "My Tool");
    int width = settings.value("window/width").toInt();
    int height = settings.value("window/height").toInt();

    this->resize(QSize(width, height));
}

MainWindow::~MainWindow()
{
    delete ui;
}

// Qtでは、closeEventをオーバーライドする事で実現可能です。
void MainWindow::closeEvent( QCloseEvent *event ){

    int width = this->geometry().width();
    int height = this->geometry().height();

    QMessageBox::information(this, "Finish Timing", tr("window width : %1 \nwindow height : %2").arg(width).arg(height));

    QSettings settings("My Company", "My Tool");

    settings.setValue("window/width", width);
    settings.setValue("window/height", height);
}

[mainwindow.h]

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>

namespace Ui {
class MainWindow;
}

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    explicit MainWindow(QWidget *parent = 0);
    ~MainWindow();

private:
    Ui::MainWindow *ui;

protected:
    virtual void closeEvent( QCloseEvent *event );

};

#endif // MAINWINDOW_H

実行結果

前回の画面サイズが、起動時に再現することができました。

<アプリケーション終了時>

f:id:nprogram:20170807221432p:plain

<アプリケーション起動時>

f:id:nprogram:20170807221510p:plain