سؤال

لقد كنت برمجة في C # لفترة من الوقت والآن أريد تنظيف مهاراتي C ++.

وجود الفصل:

class Foo
{
    const std::string& name_;
    ...
};

ما سيكون أفضل نهج (أريد فقط السماح بالوصول إلى القراءة إلى حقل الاسم):

  • استخدم طريقة Getter: inline const std::string& name() const { return name_; }
  • اجعل المجال العام لأنه ثابت

شكرا.

هل كانت مفيدة؟

المحلول

يميل إلى أن تكون فكرة سيئة لجعل حقول غير مؤتمرات غير صعبة لأنها يصبح من الصعب إجبار الأخطاء على التحقق من القيود و / أو إضافة آثار جانبية إلى تغييرات القيمة في المستقبل.

في حالتك، لديك حقل ثابت، وبالتالي فإن القضايا المذكورة أعلاه ليست مشكلة. الجانب السلبي الرئيسي لجعله مجالا عاما هو أنك تقوم بإغلاق التنفيذ الأساسي. على سبيل المثال، إذا كنت ترغب في المستقبل في المستقبل تغيير التمثيل الداخلي سلسلة C أو سلسلة Unicode، أو أي شيء آخر، فستفسر كل رمز العميل. مع Getter، يمكنك التحويل إلى التمثيل القديم للعملاء الحاليين مع توفير الوظيفة الأحدث للمستخدمين الجدد عبر Getter جديد.

ما زلت أقترح وجود طريقة Getter مثل الشخص الذي وضعته أعلاه. هذا سيعزز مرونتك المستقبلية.

نصائح أخرى

إن استخدام طريقة Getter هو خيار أفضل تصميم لفصل طويل العمر حيث يسمح لك باستبدال طريقة Getter بشيء أكثر تعقيدا في المستقبل. على الرغم من أن هذا يبدو أنه أقل احتمالا أن تكون هناك حاجة إلى قيمة ثابتة، فإن التكلفة منخفضة والفوائد المحتملة كبيرة.

جانبا، في C ++، إنها فكرة جيدة خاصة لإعطاء كل من Getter و STERTER لعضو نفس الاسم, ، نظرا لأنه في المستقبل، يمكنك بالفعل تغيير زوج الأساليب:

class Foo {
public:
    std::string const& name() const;          // Getter
    void name(std::string const& newName);    // Setter
    ...
};

في متغير عضو عام واحد يحدد operator()() لكل:

// This class encapsulates a fancier type of name
class fancy_name {
public:
    // Getter
    std::string const& operator()() const {
        return _compute_fancy_name();    // Does some internal work
    }

    // Setter
    void operator()(std::string const& newName) {
        _set_fancy_name(newName);        // Does some internal work
    }
    ...
};

class Foo {
public:
    fancy_name name;
    ...
};

ستحتاج رمز العميل إلى إعادة ترجمة بالطبع، لكن لا توجد تغييرات بناء الجملة مطلوبة! من الواضح أن هذا التحول يعمل أيضا أيضا بالنسبة للقيم التي تنتقل، والتي هناك حاجة إليها فقط.

جانبا، في C ++، من الغريب إلى حد ما أن يكون لديك عضو مرجع COND. يجب عليك تعيينه في قائمة المنشئ. الذي يملك الذاكرة في الواقع لهذا الكائن وما هو عمره؟

أما بالنسبة للأناقة، فأنا أتفق مع الآخرين أنك لا تريد أن تعرض خصمينك. :-) أنا أحب هذا النمط للمشاركين / getters

class Foo
{
public:
  const string& FirstName() const;
  Foo& FirstName(const string& newFirstName);

  const string& LastName() const;
  Foo& LastName(const string& newLastName);

  const string& Title() const;
  Foo& Title(const string& newTitle);
};

بهذه الطريقة يمكنك القيام بشيء ما:

Foo f;
f.FirstName("Jim").LastName("Bob").Title("Programmer");

أعتقد أن نهج C ++ 11 سيكون أكثر مثل هذا الآن.

#include <string>
#include <iostream>
#include <functional>

template<typename T>
class LambdaSetter {
public:
    LambdaSetter() :
        getter([&]() -> T { return m_value; }),
        setter([&](T value) { m_value = value; }),
        m_value()
    {}

    T operator()() { return getter(); }
    void operator()(T value) { setter(value); }

    LambdaSetter operator=(T rhs)
    {
        setter(rhs);
        return *this;
    }

    T operator=(LambdaSetter rhs)
    {
        return rhs.getter();
    }

    operator T()
    { 
        return getter();
    }


    void SetGetter(std::function<T()> func) { getter = func; }
    void SetSetter(std::function<void(T)> func) { setter = func; }

    T& GetRawData() { return m_value; }

private:
    T m_value;
    std::function<const T()> getter;
    std::function<void(T)> setter;

    template <typename TT>
    friend std::ostream & operator<<(std::ostream &os, const LambdaSetter<TT>& p);

    template <typename TT>
    friend std::istream & operator>>(std::istream &is, const LambdaSetter<TT>& p);
};

template <typename T>
std::ostream & operator<<(std::ostream &os, const LambdaSetter<T>& p)
{
    os << p.getter();
    return os;
}

template <typename TT>
std::istream & operator>>(std::istream &is, const LambdaSetter<TT>& p)
{
    TT value;
    is >> value;
    p.setter(value);
    return is;
}


class foo {
public:
    foo()
    {
        myString.SetGetter([&]() -> std::string { 
            myString.GetRawData() = "Hello";
            return myString.GetRawData();
        });
        myString2.SetSetter([&](std::string value) -> void { 
            myString2.GetRawData() = (value + "!"); 
        });
    }


    LambdaSetter<std::string> myString;
    LambdaSetter<std::string> myString2;
};

int _tmain(int argc, _TCHAR* argv[])
{
    foo f;
    std::string hi = f.myString;

    f.myString2 = "world";

    std::cout << hi << " " << f.myString2 << std::endl;

    std::cin >> f.myString2;

    std::cout << hi << " " << f.myString2 << std::endl;

    return 0;
}

اختبرت هذا في Visual Studio 2013. لسوء الحظ من أجل استخدام التخزين الأساسي داخل LambDasetter، كنت بحاجة إلى تقديم ملحق علوي "Getrawdata" الذي يمكن أن يؤدي إلى كسر التغليف، ولكن يمكنك إما تركها وتوفر حاوية التخزين الخاصة بك T أو ما عليك سوى التأكد من أن المرة الوحيدة التي تستخدمها "Getrawdata" هي عند كتابة طريقة Getter / Setter مخصصة.

على الرغم من أن الاسم غير قابل للتغيير، فقد لا تزال ترغب في الحصول على خيار حسابه بدلا من تخزينه في حقل. (أدرك أن هذا غير مرجح أن "الاسم"، لكن دعنا نهدف إلى القضية العامة.) لهذا السبب، حتى الحقول المستمرة ملفوفة داخل الأجيوب:

class Foo {
    public:
        const std::string& getName() const {return name_;}
    private:
        const std::string& name_;
};

لاحظ أنه إذا كنت قد تغير getName() لإرجاع قيمة محسوبة، لا يمكن إرجاع const المرجع. هذا جيد، لأنه لن يتطلب أي تغييرات على المتصلين (إعادة إخراج مودولو)

تجنب المتغيرات العامة، باستثناء الفصول الدراسية الأساسية هيكل C-Style. انها ليست مجرد ممارسة جيدة للدخول.

بمجرد تحديد واجهة الفصل، قد تكون قادرا أبدا لتغييره (بخلاف إضافة إلى ذلك)، لأن الناس سيبونون على ذلك ويعتمدون عليه. إن إجراء عام متغير يعني أنك بحاجة إلى هذا المتغير، وتحتاج إلى التأكد من أنه يحتاج المستخدم.

الآن، إذا كنت تستخدم Getter، فأنت تعزز بتقديم بعض المعلومات، والتي يتم الاحتفاظ بها حاليا في هذا المتغير. إذا تغير الوضع، وكنت تفضل عدم الحفاظ على هذا المتغير طوال الوقت، فيمكنك تغيير الوصول. إذا تغير المتطلبات (وقد رأيت بعض التغييرات المتطلبات الفردية الجميلة)، وكنت بحاجة في الغالب إلى الاسم الذي في هذا المتغير ولكن في بعض الأحيان يكون واحد في هذا المتغير، يمكنك فقط تغيير Getter. إذا قمت بإنشاء الجمهور المتغير، فستكون عالقا فيه.

لن يحدث هذا دائما، لكنني أجد أنه أسهل كثيرا فقط لكتابة كتاب سريع بدلا من تحليل الموقف لمعرفة ما إذا كنت أسدم لإنجاح الجمهور المتغير (والمخاطرة في وقت لاحق).

جعل متغيرات الأعضاء خاصة هي عادة جيدة للدخول. من المحتمل أن يحظر أي متجر لديه معايير التعليمات البرمجية مما يجعل من المحتمل أن ينتقدك أي متجر بمتغير العضو في بعض الأحيان، ومن المرجح أن ينتقدك أي متجر مع مراجعات التعليمات البرمجية.

كلما لم يهم حقا سهولة الكتابة، فانتقل إلى العادة الأكثر أمانا.

تم جمع الأفكار التي تم جمعها من مصادر C ++ متعددة ووضعها في مثال لطيف للغاية، لا يزال بسيطا جدا للحصول على Getters / Setters في C ++:

class Canvas { public:
    void resize() {
        cout << "resize to " << width << " " << height << endl;
    }

    Canvas(int w, int h) : width(*this), height(*this) {
        cout << "new canvas " << w << " " << h << endl;
        width.value = w;
        height.value = h;
    }

    class Width { public:
        Canvas& canvas;
        int value;
        Width(Canvas& canvas): canvas(canvas) {}
        int & operator = (const int &i) {
            value = i;
            canvas.resize();
            return value;
        }
        operator int () const {
            return value;
        }
    } width;

    class Height { public:
        Canvas& canvas;
        int value;
        Height(Canvas& canvas): canvas(canvas) {}
        int & operator = (const int &i) {
            value = i;
            canvas.resize();
            return value;
        }
        operator int () const {
            return value;
        }
    } height;
};

int main() {
    Canvas canvas(256, 256);
    canvas.width = 128;
    canvas.height = 64;
}

انتاج:

new canvas 256 256
resize to 128 256
resize to 128 64

يمكنك اختباره عبر الإنترنت هنا: http://codepad.org/zosxqjtx.

ملاحظة: fo yvette <3

من نظرية أنماط التصميم؛ "تغليف ما يختلف". من خلال تحديد "Getter"، هناك انتزام جيد للمبدأ أعلاه. لذلك، إذا تمثل تمثيل التغييرات الأعضاء في المستقبل، فيمكن "التدليك" قبل العودة من "Getter"؛ يعني عدم إعادة صياغة رمز على جانب العميل حيث يتم إجراء مكالمة "Getter".

يعتبر،

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top