Method chaining + inheritance don't play well together?

后端 未结 15 765
北荒
北荒 2020-12-08 22:20

Consider:

// member data omitted for brevity

// assume that \"setAngle\" needs to be implemented separately
// in Label and Image, and that Button does need         


        
相关标签:
15条回答
  • 2020-12-08 22:42

    A simple, but annoying, way of solving your problem is to reimplement all your public methods in your subclasses. This doesn't solve the issue with polymorphism (if you cast from Label to Widget, for example), which may or may not be a major issue.

    struct Widget {
        Widget& move(Point newPos) { pos = newPos; return *this; }
    };
    
    struct Label : Widget {
        Label& setText(string const& newText) { text = newText; return *this; }
        Label& setAngle(double newAngle) { angle = newAngle; return *this; }
        Label& move(Point newPos) { Widget::move(newPos); return *this; }
    };
    
    struct Button : Label {
        Button& setText(string const& newText) { Label::setText(newText); return *this; }
        Button& setAngle(double newAngle) {
            backgroundImage.setAngle(newAngle);
            Label::setAngle(newAngle);
            return *this;
        }
        Button& move(Point newPos) { Label::move(newPos); return *this; }
    };
    

    Be sure to include in your documentation this must be done for chaining to work.

    But really, now, why bother with method chaining? Many times, these functions will be called less trivially, and will need longer lines. This would really hurt readability. One action per line -- this is a general rule when it comes to ++ and -- too.

    0 讨论(0)
  • 2020-12-08 22:43

    [rant]

    Yes. Quit this method chaining business and just call functions in a row.

    Seriously, you pay a price for allowing this syntax, and I don't get the benefits it offers.

    [/rant]

    0 讨论(0)
  • 2020-12-08 22:46

    I'd abandon the chaining stuff. For one, it can't actually be done without doing some relatively nasty hacks. But, the biggest problem is that it makes it harder to read and maintain code and you will most likely end up with people abusing it, creating one giant line of code to do multiple things (think back to high-school algebra and those gigantic lines of additions, subtractions and multiplications you always seem to end up with at some point, that is what people will do if you let them).

    Another problem is that because most of your functions in the system are going to be returning a reference to itself, its going to be logical that all of them should. When (not if) you finally do start implementing functions that should return values as well (not just accessors, but some mutators will as well, and other generic functions), you will be faced with a dilemma, either break your convention (which will snowball, making it unclear as to how things should be implemented for other future functions) or be forced to start returning values via parameters (which I'm sure you would loathe, as most other programmers I know do as well).

    0 讨论(0)
  • 2020-12-08 22:47

    Well, you know it's a Button so you should be able to cast the returned Widget& as a Button& and keep going. It does look a bit ugly though.

    Another rather annoying option is to create a wrapper in your Button class for the Widget::move function (and friends). Probably not worth the effort to wrap everything if you have more than a handful of functions though.

    0 讨论(0)
  • 2020-12-08 22:54

    Is a Button really a Label? You seem to be violating the Liskov substitution principle. Perhaps you should consider the Decorator pattern to add behaviors to Widgets.

    If you insist on the structure as is, you can solve your problem like so:

    struct Widget {
        Widget& move(Point newPos) { pos = newPos; return *this; }
        virtual ~Widget();  // defined out-of-line to guarantee vtable
    };
    
    struct Label : Widget {
        Label& setText(string const& newText) { text = newText; return *this; }
        virtual Label& setAngle(double newAngle) { angle = newAngle; return *this; }
    };
    
    struct Button : Label {
        virtual Label& setAngle(double newAngle) {
            backgroundImage.setAngle(newAngle);
            Label::setAngle(newAngle);
            return *this;
        }
    };
    
    int main() {
        Button btn;
    
        // Make calls in order from most-specific to least-specific classes
        btn.setText("Hey").move(Point(0,0));
    
        // If you want polymorphic behavior, use virtual functions.
        // Anything that is allowed to be overridden in subclasses should
        // be virtual.
        btn.setText("Boo").setAngle(.5); 
    }
    
    0 讨论(0)
  • 2020-12-08 22:56

    C++ does support return value covariance on virtual methods. So you could get something like what you want with a little work:

    #include <string>
    using std::string;
    
    // member data omitted for brevity
    
    // assume that "setAngle" needs to be implemented separately
    // in Label and Image, and that Button does need to inherit
    // Label, rather than, say, contain one (etc)
    
    
    struct Point
    {
        Point() : x(0), y(0) {};
        Point( int x1, int y1) : x( x1), y( y1) {};
    
        int x;
        int y;
    };
    
    struct Widget {
        virtual Widget& move(Point newPos) { pos = newPos; return *this; }
        virtual ~Widget() {};
    
        Point pos;
    };
    
    struct Label : Widget {
        virtual ~Label() {};
        virtual Label& move( Point newPos) { Widget::move( newPos); return *this; }
    
        // made settext() virtual, as it seems like something 
        // you might want to be able to override
        // even though you aren't just yet
        virtual Label& setText(string const& newText) { text = newText; return *this; }
        virtual Label& setAngle(double newAngle) { angle = newAngle; return *this; }
    
        string text;
        double angle;
    };
    
    struct Button : Label {
        virtual ~Button() {};
        virtual Button& move( Point newPos) { Label::move( newPos); return *this; }
        virtual Button& setAngle(double newAngle) {
            //backgroundImage.setAngle(newAngle);
            Label::setAngle(newAngle);
            return *this;
        }
    };
    
    int main()
    {
        Button btn;
    
        // this works now
        btn.move(Point(0,0)).setText("Hey");
    
        // this works now, too
        btn.setText("Boo").setAngle(.5); 
    
      return 0;
    }
    

    Note that you should use virtual methods for doing something like this. If they aren't virtual methods, then the 'reimplemented' methods will result in name-hiding and the method called will depend on the static type of the variable, pointer or reference so it might not be he correct method if a base pointer or reference is being used.

    0 讨论(0)
提交回复
热议问题