Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

adapter pattern : support underlying data that can be const or non-const, elegantly

How to make an adapter class to support both const and non-const underlying data appropriately?

Concrete Example

RigidBody is a class describing physic property of object.
Here is its very simplified version (1D):-

class RigidBody{
    float position=1;
    public: float getPosition()const{ return position;}
    public: void setPosition(float ppos){ position=ppos;}
};

Adapter encapsulates RigidBody.
It provides a-little-distorted functionality to get/set position:-

class Adapter{
    public: RigidBody* rigid; int offset=2;
    public: float getPosition(){
        return rigid->getPosition()+offset;     //distort
    }
    public: void setPosition(float ppos){
        return rigid->setPosition(ppos-offset); //distort
    }
};

I can set position of RigidBody indirectly by using Adapter :-

int main() {
    RigidBody rigid;
    Adapter adapter;  //Edit: In real life, this type is a parameter of many function
    adapter.rigid=&rigid;
    adapter.setPosition(5);
    std::cout<<adapter.getPosition();//print 5
    return 0;
}

Everything works (demo).

Objective

I want create a new function that receives constRigidBody* rigid.
I should be able to read from it (e.g. getPosition()) by using adapter.

However, I don't really know how to do it elegantly.

void test(const RigidBody* rigid){
    Adapter adapter2; 
    //adapter2.rigid=rigid; //not work, how to make it work?
    //adapter2.setPosition(5); //should not work
    //adapter2.getPosition();  //should work 
}

My poor solutions

Solution A1 (2 adapters + 1 widget)

Create a widget :-

class AdapterWidget{
    public: static Adapter createAdapter(RigidBody* a);
    public: static AdapterConst createAdapter(const RigidBody* a);
};

AdapterConst can only getPosition(), while AdapterConst can both get and set.

I can use it like :-

void test(const RigidBody* rigid){
    auto adapter=AdapterWidget::createAdapter(rigid);

It is easy to use.

Disadvantage: Code of AdapterConst and Adapter will be very duplicated.

Solution A2 (+inheritance)

It is an improvement of the previous solution.
Let Adapter (has setPosition()) derive from AdapterConst (has getPosition()).

Disadvantage: It is not concise. I use 2 classes for the single task!
This might seem to be trivial, but in a bigger code-base, it is not fun at all.

Specifically, location of getPosition() will be far-away from setPosition(), e.g in different files.
This causes maintainability problem.

Solution B (template)

Create a template class. There are many ways e.g. :-

  • Adapter<T =RigidBody OR const RigidBody >
  • Adapter<bool=true is const OR false is non-const >

Disadvantage: In every ways, it is inelegant. It is an overkill. (?)
I will suffer from disadvantage of template e.g. everything in header.

Solution C1 (const_cast)

I am trying to avoid it. It is evil.

class Adapter{
    public: RigidBody* rigid; 
    void setUnderlying(const RigidBody* r){
        rigid=const_cast< RigidBody*>(r);
    }
    ....
};

Solution C2 (+manual assert)

I can add some assertion manually.
It just emphasizes how much it is unprofessional :-

    bool isConst;
    void setUnderlying(const RigidBody* r){
        ...
        isConst=true;
    }
    void setUnderlying(RigidBody* r){
        ...
        isConst=false;
    }
    void setPosition(float a){
        if(isConst){ /*throw some exception*/ }
        ....
    }

Solution D (run away)

  • Lazy : change from test(constRigidBody* rigid) to test(RigidBody* rigid).
  • Crazy : change RigidBody::setPosition() to be const.

In either way, my program will not be const-correct any more,
but a single Adapter class would be enough.

Question

Do I really have to do one of these things wherever I encounter the const/non-const pattern?
Please provide a pretty solution. (no full code is required, but I don't mind)

Sorry for the long post.

Edit: In real life, Adapter is a parameter for many function.
It is passed around like a toy.

Most of such functions don't have knowledge about RigidBody, so it is not quite suitable to change from a bundle calling someFunction(adapter) to someFunction(offset,rigidbody).

like image 492
javaLover Avatar asked Apr 27 '17 10:04

javaLover


People also ask

What is Adapter pattern used for?

An Adapter pattern acts as a connector between two incompatible interfaces that otherwise cannot be connected directly. An Adapter wraps an existing class with a new interface so that it becomes compatible with the client's interface.

What are the advantages of the Adapter pattern?

Advantage of Adapter PatternIt allows two or more previously incompatible objects to interact. It allows reusability of existing functionality.

Which of the following design problems does the Adapter pattern?

First, be sure you have a problem that this pattern can solve. Define the client interface that will be used to indirectly interact with incompatible objects. Make the adapter class inherit the interface defined in the previous step. In the adapter class, create a field to store a reference to the adaptee object.

How does the class Adapter pattern differ from the object adapter?

Object and Class Adapters Difference: The only difference is that with class adapter we subclass the Target and the Adaptee, while the object adapter uses composition to pass requests to an adaptee. Object Adapters and Class Adapters use two different means of adapting the adaptee: composition versus inheritance.


1 Answers

You shouldn't keep with that idea. This is C++, not Java.

Your code is extremely Java oriented. I can see it by the way you write the code, use pointers and silently omit const when needed.

In fact, most of the bad C++ code that I personally saw is pretty much either written to be "C inside classes" or "Java without GC". Both of them are extremely bad ways of writing C++ code.

Your question has an idiomatic solution:

  1. Ditch most of the design patterns. they are useful for languages where object are reference types by default. C++ prefers most of the times to tread object as value types and prefer static polymorphism (templates) rather than run-time polymorphism (inherit + override).

  2. Write two classes , one is Adapter and one is ConstAdapter. This is what the standard library already does. every container has different iterator and const_iterator implementation exactly because of that reason. you can either store something by pointer, either by const pointer. It is error prone trying to mix the two. If there were a nice solution for that problem, we wouldn't have two iterator typedefs for each container.

like image 121
David Haim Avatar answered Nov 14 '22 05:11

David Haim