3

私は新しい機能の既存のクラスを拡張していますが、どの設計ソリューションを使用するかについて疑問があります。いくつかあり、それぞれに長所と短所があります。私の場合は次のとおりです。特別な形式のファイル ヘッダーがあり、それを読み取って保存します。FileHeader という名前のクラスがあり、ストリームとの間のシリアル化とその他の機能を既に実装しています。私のタスク リストの項目の 1 つは、特定のタイム スタンプ機能を追加することです。タイムスタンプは、1994 年 1 月 1 日 00:00:00 からの秒数として読み取り/保存する必要があります。ただし、FileHeader クラスは日付と時刻を 2 つの個別の変数に格納します。したがって、秒から日付と時刻への変換を記述する必要があります。問題は、この機能をどこに配置するかです。secondsPerDay (60*60*24) と dateOrigin (1/1/1994) を定数として使用しています。

次のオプションがあることがわかりました。

A) FileHeader クラスのプライベート メソッドとして変換を実装します。secondsPerDay と dateOrigin は、クラスの静的プライベート定数になります。

//fileheader.h
class FileHeader
{
private:
    static const unsigned secondsPerDate = 60 * 60 * 24;
    static const Date dateOrigin;
    const Date &m_date;
    const Time &m_time;
    unsigned convertToSeconds() const; // convert m_date and m_time to seconds
    void fromSeconds(unsigned secs); // convert and store to m_date and m_time
public:
    void saveToStream(Stream &s) const;
    void restoreFromStream(const Stream &s);
//... other methods
}

//fileheader.cpp
const Date FileHeader::dateOrigin = Date(1994, 1, 1);

これはかなり簡単です。しかし、私が気に入らないのは、すでに非常に重いクラスに責任が追加されることです。あなたはルールを知っています: 1 つのクラス = 1 つの責任。たとえば、メンテナンスは難しいでしょう。誰かが秒を分などに変更することを決定した場合、彼はメソッドを書き直しますが、十分に注意しないと、静的定数 secondsPerDay が残ってしまう可能性がありますが、それはもう必要ありません。さらに、実装の詳細にのみ影響するにもかかわらず、ヘッダー ファイルを更新したに違いないという事実も気に入りません。

B) .cpp ファイルの名前のない名前空間でのみ実装を行い、通常の関数と静的変数を使用します。

namespace
{
    const unsigned secondsPerDay = 60 * 60 * 24;
    const Date dateOrigin = Date(1994, 1, 1);
    unsigned dateTimeToSeconds(const Date &d, const Time &t) ...
    Date secondsToDate(unsigned secs) ...
    Time secondsToTtime(unsigned secs) ...
}

これらの関数を呼び出す FileHeader の save メソッドと restore メソッド。まあ、私はそれが好きです。私はヘッダーを台無しにしませんでした。クラスの FileHeader の責任は大きくなりませんでした。しかし、誰かが秒の代わりに分を使用するようにアルゴリズムを変更することを決定した場合、関数を変更できますが、注意を怠ると、不要になったにもかかわらず、不要な secondsPerDay 静的変数を残してしまいます。

C) FileHeader.cpp で名前のない名前空間を使用し、専用のクラスを使用します。

namespace 
{
    class TimeConverter
    {
    private:
        static const unsigned secondsPerDay = 60 * 60 * 24;
        static const Date dateOrigin;
    public:
        static unsigned secondsFromDateTime(const Date &date, const Time &time) //implementation here...
        static Date dateFromSeconds(unsigned seconds) //implementation here...
        static Time timeFromSeconds(unsigned seconds) //implementation here...
    };
    const Date TimeConverter::dateOrigin = Date(1994, 1, 1);
}

FileHeader の保存と復元は、これらの静的メソッドを呼び出します。

m_date = TimeConverter::dateFromSeconds(secs);
m_time = TimeConverter::timeFromSeconds(secs);

個人的には、このソリューションを選択しました。ヘッダーを台無しにせず、静的変数のスコープを視覚的に制限するため、誰かが TimeConverter の実装を秒単位から分単位に変更した場合、不要な静的変数 secondsPerDay を残さない可能性が非常に高くなります...現在、 TimeConverter は他のクラス (FileHeader のみ) では使用されませんが、これを変更すると、独自のヘッダーとソース ファイルに簡単に移動できます。

コードを書いているとき、これが新しい実装の詳細の既存のクラスの機能を拡張する私の通常の方法であることに気付きました。私はかなりの頻度でそれを行っているので、他の人が何を使用しているのか、またその理由について興味があります。私の経験によると、95% の開発者がオプション A を使用してクラスを拡張しています。だからここに質問があります:

  • 他に良い便利なオプションはありますか?

  • これらのオプションを使用することの重要な側面や意味を見逃していませんか?

更新:以下の回答の1つからのアドバイスに従って、オプションDも提示します:

namespace TimeConverter
{
    const unsigned secondsPerDay = 60 * 60 * 24;
    const Date dateOrigin = Date(1994, 1, 1);
    unsigned secondsFromDateTime(const Date &date, const Time &time)
    {
        return (date - dateOrigin) * secondsPerDay + time.asSeconds();
    }

    Date dateFromSeconds(unsigned seconds)
    {
        return dateOrigin + seconds / secondsPerDay;
    }

    Time timeFromSeconds(unsigned seconds)
    {
        return Time(seconds % secondsPerDay);
    }
}

そして、それに続く質問 - D は C よりも優れている点と、その逆の点はどうなのか。長所と短所は何ですか?

4

3 に答える 3

1

個人的には、オプション B を選択します。その機能を再利用する必要があれば、C に適応させます。しかし、小さなことごとにクラスを作成すると、肥大化とボイラープレートが大きくなりすぎる可能性があると思います。YAGNIのバリエーションの前ではなく、必要なときに抽象化することを好みます。オプション B では、同じ場所で機能を定義して使用するため、読みやすくなっています。また、あなたが言ったように、ヘッダーファイルが乱雑になることはありません。

于 2014-07-31T07:56:28.437 に答える
1

A は絶対に使用しないでください。プライベート メンバーにすると、クラスのインターフェイスの一部であり、混乱するだけです。

私は C には行きません。すべての関数とメンバーが静的であるクラスは好きではありません。それは実際には種類の名前ではありません。関連するものをグループ化するだけです。それが名前空間の目的です。

私は D を使用します。単体テストの作成を便利にするために、それを独自の .h および .cpp ファイルに引き出し、.cpp ファイルにのみ #include します。これは、クラスの実装の詳細であるためです。インターフェイスの一部。

于 2014-08-04T15:31:44.983 に答える
1

私の理解が正しければ、適用したい変更は実装の詳細のみです。ユーザーはそれを変更することはできません。

この関数は、他の場所でも役立つほど一般的なように思われるので、別のヘッダーの名前付き名前空間に配置します。

//date time conversions function header
namespace foo
{
   unsigned secondsPerDay();
   unsigned secondsFromDateTime(
                     const Date &date, 
                     const Time &time, 
                     const Date& startOfTime);
   Date dateFromSeconds(unsigned seconds, const Date& startOfTime);
   Time timeFromSeconds(unsigned seconds, const Date& startOfTime);
}

スタイルと明確さのために、グローバル変数の代わりに関数 secondsPerDay を導入します。パフォーマンスの違いは無視できると思います (プロファイリングだけでわかります)。

本当の違いは、追加のパラメーターを取る関数を作成することです。関数を分離してテストし、FileHeader クラス以外のコンテキストでそれらを再利用できます。

最後に、FileHeader.cpp ファイル内にヘッダーを含め、開始日を定義します。

オプション C に関する最後のコメントです。C++ では静的メソッドのみを使用してクラスを作成する必要はありません (たとえば、フリー関数が許可されていない Java では必要です)。名前付き名前空間は、それを実装する C++ の方法です。

于 2014-07-31T10:22:32.147 に答える