7

Here we have TV class and DVD class as example:

class TV
{
public:
    TV();
    virtual ~TV();

    void on() const;
    void off() const;
};

class DVDPlayer
{
public:
    DVDPlayer();
    ~DVDPlayer();

    void SetCD() const;
    void StartPlay() const;
    void Eject() const;
    void PowerOff() const;
};

We create Adapter which helps DVD to "pretends" TV:

class TVStyleAdapter :
    public TV
{
public:
    TVStyleAdapter(DVDPlayer* AdapteeDVD);
    ~TVStyleAdapter();

    void on() const;
    void off() const;

private:
    DVDPlayer* DVD;
};

// override base class funcs:
void TVStyleAdapter::on() const
{
    DVD->SetCD();
    DVD->StartPlay();
}

void TVStyleAdapter::off() const
{
    DVD->Eject();
    DVD->PowerOff();
}

After that I can add "virtual" in the TV(Base) class & override on() off() functions in Adapter class. And it will be works right.

BUT the questions is:

  1. Can we somehow create an Adapter without ANY changes (e.g. adding "virtual") in a base ('TV') class?
  2. Is it possible in this situation not violate the Open Closed principle?
  3. If I assume using an adapter in my program in the future, should I do the virtual methods in my classes in advance?
  • I’m a huge fan of marking things const as much as possible. But in your case it’s not possible: none of these member functions can logically be const. Even though you could theoretically make it work using mutable, it’d be hugely misleading. – Konrad Rudolph Jan 24 '18 at 13:32
11

1. Yes, but no:

You could compile any code with a TVStyleAdapter instead of a TV, and it would work without a change in TV. So in a certain sense, the answer could be yes.

But this compile-time reuse of the interface would also work if TVStyleAdapter would be unrelated to TV. Unfortunately, it's no dynamic: you have to know the real object type at compile time, and you couldn't hope for a dynamic, object dependent behavior at runtime.

If you want polymorphism, i.e. use a pointer or a reference to a TV base class and expect the correct behavior to happen depending on the real type of the object you must have the virtual in the base class.

2. Yes, but who broke the principle ?

The question is not so much whether have to change the base change the base class, but rather why the base class was not already virtual.

You think that you break the close principle because you have to add the virtual in the base class. But in reality the designer of the base class broke the open principle in the first line.

3. Design for polymorphism from the beginning

If you want a class to be polymorphic, that is behave differently at rutime depending on its real type, you have to design it accordingly from the start. SO yes, virtual in advance (and consequently a virtual destructor, even if it remains empty)

  • Well, doesn't designing for polymorphism from the beginning, without having even an idea of how it should be polymorphic, violate YAGNI? – Deduplicator Jan 24 '18 at 16:34
  • 1
    @Deduplicator I fully agree with you: it would not be wise to make classes polymorphic just in case of some distant eventuality. I trust the designer to have founded reasons, and to have thought carefully about the pros, the cons, and the hows, before he/she wants to make a class polymorphic ;-) – Christophe Jan 24 '18 at 20:07
9

The classic adapter pattern, utilizing inheritance, simply does not work if the base class is not prepared for it. And yes, you are correct, without TV having virtual methods, it does not conform to the OCP. The OCP requires classes to provide required "extension points" or parameters beforehand, see this former Q&A about the OCP and what it means to class design.

In C++, however, there is the possibility to implement the adapter pattern using templates instead, by making the kind of TV class it uses a template parameter:

    template <class T> void myFunction(T tv)
    {
         tv->on();
    }

This will allow you to write code which either takes the original TV class or the TVStyleAdapter as a parameter, without making the onand off methods virtual. Note this makes it a compile time decision which of the two classes will be used, whilst the inheritance solution will allow to switch at run time.

If one needs a run-time decision together with the template solution, the decision has to be made by the code which calls either myFunction<TV> or myFunction<TVStyleAdapter>. Or better use @JackAidley's solution, which is a good example of "composition over inheritance".

3

Yes, you can.

The solution is simple: wrap TV too, and inherit both your TV and DVD adaptors from a common abstract base class. You can then use this abstract base class in place of everywhere you were using TV and get run-time flexibility.

class TVBase {
    public:
    virtual ~TVBase() {}

    virtual void on() const = 0;
    virtual void off() const = 0;
};

class TVAdaptor : public TVBase {
    public:
    // Obviously you need a constructor!
    virtual void on() const { tv->on(); }
    virtual void off() const { tv->off(); }

    private:
    TV* tv;
};

class DVDAdaptor : public TVBase {
    // Insert contents of your TV style adaptor here...
};

I've omitted various implementation details in the above but, hopefully, you can see what I've done. You could even implement it as a template with automatic pass through for on and off and then specialise for classes like DVD with a different interface.

  • Yes, this is a way not to modify the TV. But then TV is no longer the base, which contradicts the OP requirements (" creating an adapter without ANY changes in a base") – Christophe Jan 24 '18 at 11:29
  • 3
    @Christophe: read this answer again, it does not require any changes in the original TV class. It only requires changes in the code using the TV class, which has to be refactored to use the new TVBase class instead. – Doc Brown Jan 24 '18 at 11:39
  • @DocBrown thar's exactly my point: the question is not about leaving the tv unchanged, but leaving the base class untouched when implementing an adapter. This answer creates a new base class with two adapter. So TV is no longer the base for the adapter. This is fine, but is an answer to a different question (e.g. how to enable polymorphism for an existing class without changing it). – Christophe Jan 24 '18 at 13:02
  • 2
    @Christophe: I do not interpret the question in that way - I agree with Doc Brown's interpretation - and I do not see how it makes sense to do so. The question does not make any specifications about the calling code. – Jack Aidley Jan 24 '18 at 13:05
  • 2
    @Christophe: adding a new class == leaving the old code untouched. OP clearly wrote about by "base class" he exclusively meant TV, nothing else. This is a sensible solution which I have already used in the past in production code and test code for exactly this purpose, so I am a little bit angry about myself that I forgot to mention it in my own answer. – Doc Brown Jan 24 '18 at 15:32

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.