2013-01-25 12 views
6

Rozważmy następujące klasy:Użycie przeciążenia wartości i przeciążenia operatora() w celu uproszczenia procesu pobierania/ustawiania: niebezpieczna praktyka?

class MyClass1 
{ 
    public: 
     double x() const {return _x;} // getter 
     double y() const {return _y;} // getter 
     double z() const {return _x*_y;} // getter 
     void x(const double var) {_x = var;} // setter 
     void y(const double var) {_y = var;} // setter 
     void z(const double var) {_x = var; _y = 1;} // setter 
    protected: 
     double _x; 
     double _y; 
}; 

Jak rzeczywistej zawartości MyClass1 jest szczegół realizacji, pobierające i ustawiające zapewnić jednolity sposób, aby pobrać i ustawić zawartość klasy, nawet jeśli są one interdependant (tutaj _z nie istnieje wewnętrznie, ale dla użytkownika z jest zmienną, taką jak x i y).

teraz, aby uniknąć musiał napisać getter/setter dla x i y można wykorzystywać owijkę tak:

template <typename Type> 
class Wrapper 
{ 
    public: 
     constexpr Wrapper(const Type& value) {_value = value;} 
     constexpr Type& operator()() {return _value;} 
     constexpr const Type& operator()() const {return _value;} 
     constexpr void operator()(const Type& value) {_value = value;} 
    protected: 
     _value; 
}; 

A teraz oryginalna klasa staje:

class MyClass2 
{ 
    public: 
     Wrapper<double> x; 
     Wrapper<double> y; 
     double z() const {return x*y;} // getter 
     void z(const double var) {x = var; y = 1;} // setter 
}; 

Czy go niebezpieczna praktyka lub dobre rozwiązanie, aby uniknąć pisania podpowiedzi/ustawiaczy? Uwaga: tutaj MyClass1 i MyClass2 są tylko przykładami. Moje pytanie jest bardzo "ogólne": jest to niebezpieczne, aby zastąpić pobierające/ustawiające klasy przez proponowaną Wrapper, gdy getter/setter właśnie wróci/ustawi wewnętrzną wartość.

+2

Przypuszczam, że musiałbyś zaimplementować jakiś wzór zachowania, aby faktycznie coś zrobić (sprawdzić, powiadomić, itp.) na akcesoriach. W przeciwnym razie mogliby pokonać cel ich posiadania. Nie sądzisz? imho – imreal

+1

dlaczego twoja klasa potrzebuje uzyskać/ustawić interfejs? jeśli te funkcje nie muszą utrzymywać niezmienników klasy, nie ma sensu ich włączać. – TemplateRex

Odpowiedz

1

Jak już wspomniano, głównym celem modułów pobierających i ustawiających jest zapewnienie ujednoliconego sposobu uzyskiwania dostępu i ustawiania prywatnych zmiennych instancji.

Z rozwiązania do pakowania, jeśli w jakimś czasie na ścieżce cyklu życia programu zdecydujesz się zmienić setera, możesz po prostu wyrzucić opakowanie i zastąpić je oryginalnym programem pobierającym/ustawiającym, jak w MyClass1 - bez konieczności zmiany wszystkiego inne składniki kodu, który je wywołuje.

Więc od tego momentu myślę, że twoje rozwiązanie jest dobrym sposobem na zaoszczędzenie ci pisania kilku dodatkowych linii kodu.

1

Nie widzę tam nic szczególnie niebezpiecznego, ale wydaje się, że nic nie zyskuje. C++ udostępnia semantykę odniesienia dla dowolnego obiektu, który jest niezgodny z funkcją ustawiającą. Czasami rzeczywista zawartość nie jest tylko szczegółem.

Można też pójść w drugą stronę (faktycznie, to jest to, co spodziewałem się zobaczyć po kliknięciu na to pytanie):

struct virt_z { 
    double x; 
    double y; 
    struct z_wrap { 
     virt_z &parent; 

     operator double() { return parent.x * parent.y; } 
     z_wrap &operator=(double in) { 
      parent.x = in; 
      parent.y = 1; 
      return *this; 
     } 
     z_wrap(virt_z &in) : parent(in) {} 
    } z; 

    virt_z() : z(*this) {} 
    virt_z(virt_z const &o) : x(o.x), y(o.y), z(*this) {} 
}; 

https://ideone.com/qqgj3D

+1

A co z twoim biednym klientem, który musi przeczytać ten interfejs i dowiedzieć się, że aby ustawić z muszą przypisać do z poprzez przeciążony operator = z wewnętrznej klasy opakowania. Prosty styl funkcyjny gettera i ustawiającego z OP MyClass1 jest lepszy niż MyClass2 i twojego rozwiązania, ponieważ jest jasne, czytając interfejs, jak z niego korzystać. Biorąc pod uwagę, że wszystkie trzy kompilują się do tego samego kodu, twierdzę, że MyClass1 powinien być faworyzowany. –

+0

@ user1131467 Różne rzeczy pasują do różnych sytuacji. Jeśli chodzi o MyClass1, żadna funkcja, która ma więcej niż "set' z' "nie powinna być nazywana" 'SetZ()' ", i to jest podstawowy problem z formalizmem getter/setter. Jeśli ustawienie czegoś ma efekty uboczne, to tak naprawdę nie tylko ustawiasz jakiś stan. Podobnie rzadko można * po prostu * zmienić interfejs, aby dodać efekty uboczne w obrębie tego samego obiektu. To tylko mit utrwalony w podręcznikach OO. – Potatoswatter

+0

Dlaczego użytkownik powinien dbać o szczegóły implementacji interfejsu? Powinny po prostu używać go w sposób naturalny. – balki

1

To nie jest niebezpieczne, jako takie, to jest po prostu więcej trudne w użyciu (dla użytkownika klasy). MyClass2 powoduje zaciemnianie i zaśmiecanie interfejsu.

Może to sprawić, że napisanie kodu będzie mniej uciążliwe, ale wystarczy napisać kod tylko jeden raz - interfejs będzie wielokrotnie używany. Dlatego czytelnik kodu powinien być faworyzowany.

MyClass1 jest lepszy od obu rozwiązań MyClass2 i Potatoswatter z tego powodu.

(Nawet jeśli istnieje sposób w C++, aby to zrobić:

class MyClass3 
{ 
    double x, y, z; 
} 

MyClass3::z::operator=(double) { ... } // property set 
double MyClass3::z::operator() { ... } // property get 

jak w C# to i tak będzie to zły pomysł, myślę, ponieważ ten rodzaj „ukrytego” kodu prowadzi do złych założeń, które mogą spowolnij debugowanie Styl funkcji przynajmniej pozwala ci sądzić, że może być coś bardziej skomplikowanego niż pierwotna kopia.)

Powiązane problemy