2015-03-13 20 views
14

Tôi có một thiết kế đẳng cấp tương tự như sau:chức năng thành viên mà đôi khi được const

class MyClass { 
public: 
    bool IsValid() const; 
    void MakeValid(); 
private: 
    bool CheckValidity(bool fix); 
}; 

bool MyClass::IsValid() const { 
    // Check validity, but don't fix any problems found. Doesn't work. 
    return CheckValidity(false); 
} 

void MyClass::MakeValid() { 
    // Check validity and fix problems found. 
    CheckValidity(true); 
} 

IsValid nên const, bởi vì nó không thực hiện thay đổi. MakeValid không phải là không const, bởi vì nó thực hiện thay đổi. Họ chia sẻ cùng một triển khai, CheckValidity, nhưng vì CheckValidity có thể hoặc không thể thực hiện thay đổi, nó không thể được đánh dấu const.

Cách tốt nhất để xử lý việc này là gì? Phương pháp đơn giản nhất là chỉ cần sử dụng const_cast, nhưng đúc đi const cảm thấy một chút bẩn:

bool MyClass::IsValid() const { 
    // Check validity, but don't fix any problems found. 
    return const_cast<MyClass*>(this)->CheckValidity(false); 
} 

Đây có phải là một sử dụng hợp pháp của const_cast? Có cách tiếp cận tốt hơn không?

+14

Kiểm tra phân tách và "sửa lỗi" thành hai chức năng khác nhau? Kiểm tra là 'const' và sửa chữa không? – crashmstr

+6

Tôi đồng ý với @crashmstr, có một chức năng làm hai việc khác nhau là mùi thiết kế xấu. –

+5

Trên thực tế, đã có tên 'CheckValidity' gợi ý rằng hàm này chỉ thực hiện kiểm tra và có thể là const. Nếu bạn muốn 'MakeValid' nó là một cái gì đó khác nhau (và nó không nên const ...) – user463035818

Trả lời

16

Tôi giả định thực hiện của bạn trông giống như sau:

bool CheckValidity(bool fix) 
{ 
    // Actually check validity. 
    bool isValid = ...; 

    if (!isValid && fix) 
    { 
     // Attempt to fix validity (and update isValid). 
     isValid = ...; 
    } 

    return isValid; 
} 

Bạn thực sự có hai chức năng khác nhau xô đẩy thành một. Một trong những chỉ số chính của loại vướng víu này là đối số boolean đối với hàm ... mà có mùi bởi vì người gọi không thể nhận ra ngay lập tức có nên đặt đúng hay sai mà không tham khảo mã/tài liệu.

Phân ra phương pháp:

bool CheckValidity() const 
{ 
    // Actually check validity. 
    bool isValid = ...; 
    return isValid; 
} 

void FixValidity() 
{ 
    // Attempt to fix validity. 
    // ... 
} 

Và sau đó phương pháp nào bạn có thể làm cho các cuộc gọi một cách thích hợp hơn.

bool IsValid() const 
{ 
    // No problem: const method calling const method 
    return CheckValidity(); 
} 

void MakeValid() 
{ 
    if (!CheckValidity()) // No problem: non-const calling const 
    { 
     FixValidity(); // No problem: non-const calling non-const 
    } 
} 
+1

Trong trường hợp này, logic kiểm tra phức tạp (lặp qua các cấu trúc con lồng nhau và kiểm tra chúng để nhất quán với nhau), và sửa chữa các vấn đề đòi hỏi phải biết các phần tử phụ nào có vấn đề, vì vậy tôi đang cố gắng tìm một cách sạch sẽ tách 'CheckValidity' và' FixValidity' mà không sao chép hoặc làm phức tạp đáng kể logic. –

+5

@JoshKelley Nó có thể là bạn có thể tách ra khỏi traversal từ kiểm tra từ thao tác. Hoặc bạn có thể tìm kiếm Kiểm tra và Khắc phục sự cố khó phân tách vì mã/cấu trúc hiện có của bạn được tuân thủ. Một cách "dễ dàng" để chia thành Kiểm tra và sửa lỗi đơn giản là sao chép phương thức CheckValidity (bool) hiện tại của bạn. Với cái đầu tiên, giả sử bool là sai ở khắp mọi nơi trong phương thức đó, loại bỏ mã chết, sau đó loại bỏ tham số bool. Với thứ hai, đổi tên thành FixValidity, giả sử bool là true, loại bỏ mã chết, sau đó loại bỏ tham số bool. Sau đó xem những gì vẫn còn phổ biến giữa hai và refactor. –

+0

Suy nghĩ về nó hơn nữa, chia tách traversal dường như là con đường để đi. Cảm ơn bạn. –

6

Đây là phương pháp hữu ích trong một số trường hợp. Nó có thể là quá mức cần thiết cho tình huống cụ thể của bạn.

Chức năng CheckValidity của bạn có thể được chuyển qua đối tượng xử lý. Hàm CheckValidity sẽ tìm thấy những gì không hợp lệ và gọi một phương thức thích hợp của đối tượng xử lý. Bạn có thể có nhiều phương pháp khác nhau cho các loại vi phạm hợp lệ khác nhau và các phương pháp đó có thể được chuyển đủ thông tin để khắc phục sự cố nếu cần. Để thực hiện IsValid, bạn chỉ cần vượt qua một trình xử lý đặt cờ cho biết có sự cố. Để thực hiện MakeValid, bạn có thể vượt qua một trình xử lý thực sự khắc phục sự cố. Các vấn đề const được giải quyết bằng cách xử lý sửa chữa giữ một tham chiếu không const đối tượng.

Dưới đây là một ví dụ:

class MyClass { 
public: 
    bool IsValid() const 
    { 
     bool flag = false; 
     CheckValidity(FlagProblems{flag}); 
     return flag; 
    } 

    void MakeValid() 
    { 
     CheckValidity(FixProblems{*this}); 
    } 

private: 
    struct FlagProblems { 
     bool& flag; 

     void handleType1(arg1,arg2)  const { flag = true; } 
     void handleType2(arg1,arg2,arg3) const { flag = true; } 
     . 
     . 
     . 
    }; 

    struct FixProblems { 
     MyClass& object; 
     void handleType1(arg1,arg2)  const { ... } 
     void handleType2(arg1,arg2,arg3) const { ... } 
     . 
     . 
     . 
    }; 

    template <typename Handler> 
    bool CheckValidity(const Handler &handler) const 
    { 
     // for each possible problem: 
     // if it is a type-1 problem: 
     //  handler.handleType1(arg1,arg2); 
     // if it is a type-2 problem: 
     //  handler.handleType2(arg1,arg2,arg3); 
     // . 
     // . 
     // . 
    } 
}; 

Sử dụng mẫu cho phép hiệu quả tối đa. Ngoài ra, việc sử dụng một lớp cơ sở với các hàm ảo cho trình xử lý có thể cung cấp một kích thước thực thi nhỏ hơn.

Nếu cách thức đối tượng có thể không hợp lệ đơn giản hơn, thì việc CheckValidity trả về cấu trúc chứa thông tin liên quan có thể đơn giản hơn.

+0

Chắc chắn là một khả năng hợp lệ, mặc dù với kiểu tách đó, 'CheckValidity' có thể được đặt tên thích hợp hơn là' Traverse' hoặc một cái gì đó tương tự chỉ ra rằng nó đang di chuyển qua dữ liệu và áp dụng các trình xử lý thay vì tự kiểm tra. –

+1

@MatthewMoss: Tôi thực sự có nghĩa rằng chúng tôi đang đi mặc dù mỗi vấn đề có thể, chứ không phải là mỗi đối tượng có thể có trong cấu trúc dữ liệu. Tôi sẽ viết lại nó. –

+0

Ah, hiểu rồi. –

2

Bạn có thể sử dụng chuyên môn mẫu để tách các phần chỉ có mục đích trên đối tượng không phải là const.

Sau đây là triển khai cho lớp đồ chơi. Nó có một thành viên c-mảng đơn v với 10 ints, và, cho mục đích của chúng ta, nó chỉ hợp lệ khi mỗi một phần tử trong số chúng bằng 0.

class ten_zeroes { 
    int v[10]; 
    void fix(int pos) {v[pos] = 0;} 

    public: 
    ten_zeroes() { // construct as invalid object 
    for (int i=0;i<10;i++) { 
     v[i] = i; 
    } 
    } 
}; 

Xem mà tôi đã thực hiện một hàm thành viên, có thể chữa một vị trí không hợp lệ, và một constructor tốt đẹp mà khởi nó như là một đối tượng không hợp lệ (không làm điều đó: D)

Kể từ khi chúng ta sẽ sử dụng các mẫu, chúng ta cần phải di chuyển việc thực hiện chu kỳ kiểm tra/sửa lỗi bên ngoài lớp. Để các chức năng liên quan có thể truy cập v và phương pháp fix(), chúng tôi sẽ kết bạn với bạn bè. mã của chúng tôi bây giờ trông giống như:

class ten_zeroes { 
    int v[10]; 
    void fix(int pos) {v[pos] = 0;} 

    public: 
    ten_zeroes() { // construct as invalid object 
    for (int i=0;i<10;i++) { 
     v[i] = i; 
    } 
    } 

    template<typename T> 
    friend void fix(T& obj, int pos); 

    template<typename T> 
    friend bool check(T& obj); 
}; 

check() 's thực hiện rất đơn giản:

// Check and maybe fix object 
template<typename T> 
bool check(T& obj){ 
    bool result = true; 
    for(int i=0;i<10;i++) { 
    if (obj.v[i]) { 
     result = false; 
     fix(obj, i); 
    } 
    } 
    return result; 
} 

Bây giờ đây là phần khó khăn. Chúng tôi muốn chức năng fix() của chúng tôi để thay đổi hành vi dựa trên độ chói. Để làm được điều đó, chúng tôi sẽ cần chuyên về mẫu. Đối với một đối tượng không phải const, nó sẽ sửa chữa vị trí. Đối với một const, nó sẽ không làm gì cả:

// For a regular object, fix the position 
template<typename T> 
void fix(T& obj, int pos) { obj.fix(pos);} 

// For a const object, do nothing 
template<typename T> 
void fix(const T& obj, int pos) {} 

Cuối cùng, chúng ta viết của chúng tôi is_valid()make_valid() phương pháp, và ở đây chúng tôi có đầy đủ việc thực hiện:

#include <iostream> 

class ten_zeroes { 
    int v[10]; 
    void fix(int pos) {v[pos] = 0;} 

    public: 
    ten_zeroes() { // construct as invalid object 
    for (int i=0;i<10;i++) { 
     v[i] = i; 
    } 
    } 

    bool is_valid() const {return check(*this);} // since this is const, it will run check with a const ten_zeroes object 
    void make_valid() { check(*this);} // since this is non-const , it run check with a non-const ten_zeroes object 

    template<typename T> 
    friend void fix(T& obj, int pos); 

    template<typename T> 
    friend bool check(T& obj); 
}; 

// For a regular object, fix the position 
template<typename T> 
void fix(T& obj, int pos) { obj.fix(pos);} 

// For a const object, do nothing 
template<typename T> 
void fix(const T& obj, int pos) {} 

// Check and maybe fix object 
template<typename T> 
bool check(T& obj){ 
    bool result = true; 
    for(int i=0;i<10;i++) { 
    if (obj.v[i]) { 
     result = false; 
     fix(obj, i); 
    } 
    } 
    return result; 
} 

int main(){ 
    ten_zeroes a; 
    std::cout << a.is_valid() << a.is_valid(); // twice to make sure the first one didn't make any changes 
    a.make_valid(); // fix the object 
    std::cout << a.is_valid() << std::endl; // check again 
} 

Tôi hy vọng bạn không nhớ các main() hoạt động ở đó. Nó sẽ kiểm tra đồ chơi nhỏ của chúng tôi, và đầu ra 001, như mong đợi. Bây giờ bất kỳ bảo trì trên mã này sẽ không phải đối phó với trùng lặp mã, những gì bạn có thể có ý định để tránh. Tôi hy vọng nó sẽ có ích.

Tất nhiên, nếu bạn định ẩn các chi tiết triển khai này khỏi người dùng cuối cùng, bạn nên chuyển chúng đến một không gian tên chi tiết thích hợp. Tôi sẽ để lại điều đó cho bạn :)

Các vấn đề liên quan