简体   繁体   中英

How to use strategy pattern with additional methods in derived classes

I'm writing a project in which I'm using strategy, however, my derived classes have other additional functionalities which the base class shouldn't have.

class Base {
        public:
            virtual void execute() = 0;
            virtual void print() const noexcept = 0;
            virtual ~Base() {}
        };


class DerivedA : public virtual Base {
        public:
            void execute() override;
            void print() const noexcept override;
            void doSomething();

        private:
            int x;
            double y;
};

class DerivedB final : public Base {
        public:
            void execute() override;
            void print() const noexcept override;
            std::string getZ() const noexcept;

        private:
            std::string z;
        };


In main(), I'm trying to use dynamic_cast to be able to use this additional functionality like this:

int main() {

    DerivedA da;
    Base* base = &da;
    DerivedA* derivedA = dynamic_cast<DerivedA*>(base);
    derivedA.doSomething();

    return 0;
}


But when I try to run the code I get the following error:

'DerivedA *' differs in levels of indirection from 'DerivedA'


My questions are, should I be even using strategy for this, or should I be using another design pattern? And if I should use strategy, how can I solve this error?


UPDATE

I'm thinking of switching to decorator, since the DerivedA can be a base class for DerivedB, however, I'm worried about casting, since each class has different class members.


UPDATE

Seems like I wrote the casting wrong. However, I wrote it right when posting it here! It works now, but, I agree that there's a better way to implement this.

If this is exactly the code you're trying to compile, the solution is easy. Instead of

derivedA.doSomething();

you should write

derivedA->doSomething();

Since derived is a pointer, not a reference or instance.

However, I do have some other serious concerns about considering dynamic_cast . My opinion is that if you use dynamic_cast there is probably something wrong in your design. You have an interface hiding the implementations, yet have code that relies on the implementations and not in the interface.

Some alternatives to consider:

  • Try to convert the implementation-specific methods to something that makes sense on the interface. Maybe you can find some common concept for all the implementations.
  • Extract the implementation-specific methods into 'capability' interfaces and provide functionality in the interface to get a 'capability' interface. Eg if your base class is IAnimal , and some of your animals can swim, make an interface ISwimmable and add a method to IAnimal that return a pointer to ISwimmable ( IAnimal::getSwimmable ). Animal implementations that can swim, can inherit from ISwimmable and implement getSwimmable and return a pointer to the ISwimmable interface (so actually, return an upcast of itself, which is simply implicitly itself). Animals that can't swim can return a nullptr (which could be the default implementation in IAnimal or some AnimalBase class inheriting from IAnimal if you want to keep the interface a pure interface).

This second approach is also used by Microsoft's COM system, where every COM interface implements the IUnknown interface, and where you can call IUnknown to get a specific, different interface for some 'capability'.

Code below iw working file for me, with g++ and compilation: g++ main.cc -o main -std=c++14

#include <string>

class Base {
        public:
            virtual void execute() = 0;
            virtual void print() const noexcept = 0;
            virtual ~Base() {}
        };


class DerivedA : public virtual Base {
        public:
            void execute() override {}
            void print() const noexcept override {}
            void doSomething(){}

        private:
            int x;
            double y;
};

class DerivedB final : public Base {
        public:
            void execute() override {}
            void print() const noexcept override {}
            std::string getZ() const noexcept {}

        private:
            std::string z;
        };
int main() {

    DerivedA da;
    Base* base = &da;
    DerivedA* derivedA = dynamic_cast<DerivedA*>(base);
    derivedA->doSomething();

    return 0;
}

What you're describing is a common problem in software, and one that crops up a lot in various design patterns. You want to use DerivedA and DerivedB polymporphically, so they have to share a common interface. That's fine. However DerivedA has a useful method doSomething you want to use as well. Your options are either:

  1. Don't use the doSomething method, which keeps your code nice and clean;
  2. Do a runtime typecheck and cast so you can access doSomething , but then your code unavoidably looks a bit hacky;
  3. Implement doSomething in Base to do nothing, then your code looks nice, but when you call doSomething on a Base reference, it will do nothing if the receiving object in actually of class DerivedB .

Decorator won't really help here. Decorator is good for augmenting existing methods. You can't use it (in a statically typed language) to add new methods, as I learned the hard way :-)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM