『リビルド--既存コードのデザインを改善する』読書ノート---Extract Class

8606 ワード

オブジェクト向けでは、クラスという概念に対して、各クラスには変化点が1つしかないべきであり、各クラスの変化は単一の要素しか受けられないべきである.すなわち、各クラスには明確な責任が1つしかないべきである.もちろん、やりやすいことを言うのは難しいですが、多くの人は私と同じように、最初はクラスを構築するときに自信満々で、SRPの原則をしっかり覚えているかもしれませんが、開発の進度が進むにつれて、もともと設計したクラスに新しいフィールドを追加したり、新しい関数を追加したりする可能性があります.少量の増加に対しては、面倒なので、単独で新しいクラスを作って分解しないことを考えているかもしれません.長い間、あなたのこの類はますます肥大化し、管理する責任もますます多くなります.このようなクラスには多くのデータと関数があり、理解しにくいことが多い.このとき、単独のクラスとして分離できるものを考えるべきです.いくつかのデータといくつかの関数が常に一緒に現れていることを発見したら、いくつかのデータは常に同時に変化し、互いに依存していることを示しています.これは、それらを分離すべきであることを示しています.もちろん、著者は、いくつかの関数とフィールドを単独で移動すると、何が起こるか、他の関数とフィールドが意味を持たなくなるかどうかを自分で自分に聞くという良いテクニックにも言及しています.
もう一つのケースは,開発後期に出現するクラスのサブクラス化の問題であることが多い.サブクラス化がクラスの一部の特性にのみ影響することを発見した場合、またはいくつかの特性がシード化方式を必要としていることを発見した場合、いくつかの特性が別のシード化方式を必要としている場合は、元のクラスを分解する必要があることを意味します.
方法:
  • は、クラスの責任をどのように分解するかを決定します.
  • は、古いクラスから分離された責任を表現するための新しいクラスを構築します.古いクラスの残りの責任が古いクラス名と一致しない場合は、古いクラスに名前を変更します.
  • 古いクラスから新しいクラスにアクセスする接続関係を確立します.新しいクラスから古いクラスにアクセスする必要がある場合は、本当に必要でない限り、新しいクラスから古いクラスへの接続を確立しないでください.
  • 移動したいフィールドごとに、Move Fieldを使用して移動します.
  • 毎回移動した後、コンパイル、テストを行います.
  • Move Methodを使用して、必要な関数を古いクラスから新しいクラスに移動します.ここでは、下位レベルの関数(他の関数によって呼び出された回数が他の関数を余分に呼び出された関数)を先に移動し、上位レベルの関数を移動するテクニックがあります.
  • 毎回移動した後、コンパイル、テストを行います.
  • 各クラスのインタフェースを確認します.双方向接続を確立したら、一方向接続に変更できるかどうかを考えてみましょう.
  • 新しいクラスを公開するかどうかを決定し、公開しなければ、古いクラスを完全に新しいクラスの依頼クラスにすることができます.もしあなたが本当に彼を公開するならば、あなたが他のユーザーに公開したとき、参照するか普通の値オブジェクトかという別名の問題も考えなければなりません.

  • 例:
    class Person
    
    {
    
    public:
    
        QString name()
    
        {
    
            return m_name;
    
        }
    
        
    
        QString telephoneNumber()
    
        {
    
            return m_officeAreaCode + m_officeNumber;
    
        }
    
        
    
        QString officeAreaCode()
    
        {
    
            return m_officeAreaCode;
    
        }
    
        
    
        void setOfficeAreaCode(const QString &value)
    
        {
    
            m_officeAreaCode = value;
    
        }
    
        
    
        QString officeNumber()
    
        {
    
            return m_officeNumber;
    
        }
    
        
    
        void setOfficeNumber(const QString &value)
    
        {
    
            m_officeNumber = value;
    
        }
    
    private:
    
        QString m_name;
    
        QString m_officeAreaCode;
    
        QString m_officeNumber;
    
    };

    この例を見ると、電話番号に関連する特性を個別の「電話」クラスに完全に置くことができることがわかります.まず、電話番号という概念を表す電話クラスを定義します.
    class TelephoneNumber
    
    {   
    
    };

    次に、PersonからTelephoneNumberへの接続を確立します.つまり、Personにフィールドを追加してTelephoneNumberオブジェクトを保存します.
    class Person
    
    {
    
    private:
    
        TelephoneNumber m_telephoneNumber;
    
    };

    次に、Move Fieldを使用して電話に関連するフィールドを移動できます.ここではm_officeAreaCodeとm_officeNumber、まずm_を移動しますofficeAreaCodeは
    class TelephoneNumber
    
    {
    
        QString officeAreaCode()
    
        {
    
            return m_officeAreaCode;
    
        }
    
        
    
        void setOfficeAreaCode(const QString &value)
    
        {
    
            m_officeAreaCode = value;
    
        }
    
    private:
    
        QString m_officeAreaCode;
    
    };

    ターゲットクラスがMove Fieldを通過した後の様子です
    class Person
    
    {
    
    public:    
    
        QString telephoneNumber()
    
        {
    
            return officeAreaCode() + m_officeNumber;
    
        }
    
        
    
        QString officeAreaCode()
    
        {
    
            return m_telephoneNumber.officeAreaCode();
    
        }
    
        
    
        void setOfficeAreaCode(const QString &value)
    
        {
    
            m_telephoneNumber.setOfficeAreaCode(value);
    
        }
    
        
    
    private:
    
        TelephoneNumber m_telephoneNumber;
    
    };

    これは、古いクラスがMove Fieldを通過した後にofficeAreaCodeについて参照される変更です.他のフィールドを移動し、Move Methodを使用して関連関数をTelephoneNumberクラスに移動できます.
    class Person
    
    {
    
    public:
    
        QString name()
    
        {
    
            return m_name;
    
        }
    
        QString telephoneNumber()
    
        {
    
            return m_telephoneNumber.telephoneNumber();
    
        }
    
        TelephoneNumber telephoneClass()
    
        {
    
            return m_telephoneNumber;
    
        }
    
    private:
    
        TelephoneNumber m_telephoneNumber;
    
        QString m_name;
    
    };
    
    
    
    
    
    class TelephoneNumber
    
    {
    
        QString telephoneNumber()
    
        {
    
            return m_officeAreaCode + m_officeNumber;
    
        }
    
        
    
        QString officeAreaCode()
    
        {
    
            return m_officeAreaCode;
    
        }
    
        
    
        void setOfficeAreaCode(const QString &value)
    
        {
    
            m_officeAreaCode = value;
    
        }
    
        
    
        QString officeNumber()
    
        {
    
            return m_officeNumber;
    
        }
    
        
    
        void setOfficeNumber(const QString &value)
    
        {
    
            m_officeNumber = value;
    
        }
    
    private:
    
        QString m_officeAreaCode;
    
        QString m_officeNumber;
    
    };

    これらを完了した後、ユーザーにこの新しいクラスを公開する必要があるかどうかを考慮する必要があります.私は古いクラスに新しいクラスの依頼クラスを作って新しいクラスを完全に隠すことができます.また、直接ユーザーに公開することもできます.公開を考慮すると、別名の問題を考慮し、戻り値オブジェクトと参照オブジェクトが自分に与える結果を考慮する必要があります.
    このような問題に直面して、以下のいくつかの選択肢があります.
  • では、TelephoneNumberオブジェクトの任意の部分を変更できます.つまり、このクラスを参照オブジェクトに変更し、Change Value to Referenceを使用して値オブジェクトを参照オブジェクトに変更できます.この場合PersonはTelephoneNumberのアクセスポイントです.
  • では、Personオブジェクトを介してTelephoneNumberオブジェクトを変更することはできません.つまり、TelephoneNumberは変更できません.ここでは、C++のプロパティconstを使用して修飾することができます.
  • もう一つの方法は、TelephoneNumberと全く同じ新しいオブジェクトをコピーして戻ることです.これは他のユーザーに困惑する可能性があります.彼は修正したと思っているかもしれませんが、なぜ元のオブジェクトが変わっていないのかと思っています.また、同じTelephoneNumberオブジェクトが複数のユーザーに渡された場合、ユーザー間の別名の問題も発生します.

  • Extract Classはコンカレントプログラムを改善する一般的な技術ですが、どのように理解しますか?簡単に言えば、彼は分解後の2つのクラスを別々にロックすることができます.もしあなたがこの方面の需要があれば、あなたはこのようにすることができます.もちろん危険性もあります.2つのオブジェクトが同時にロックされていることを確認するには、トランザクションの問題にも関連しています.他のタイプの共有ロックを使用する必要があります.これは複雑な分野で、一般的な状況よりも重いメカニズムが必要です.トランザクションは実用的ですが、ここでトランザクションマネージャを手動で作成すると、多くのプログラマーの職責範囲を超えます.