C ++ API: изменение внутренних объектов - PullRequest
2 голосов
/ 26 сентября 2019

У меня есть два связанных вопроса.В данный момент я разрабатываю / пишу API C ++, в котором мне нужно иметь возможность изменять объект, который содержится в другом объекте.

Это сопоставимо с этим примером:

class Bar
{
    public:
        Bar(int x) : num(x){}

        void setNum(int x)
        {
            num = x;
        }

        int getNum()
        {
            return num;
        }

    private:
        int num;
};

class Foo
{
    public:
        Foo() = default;

        void setBar(std::unique_ptr<Bar> newBar)
        {
            bar = std::move(newBar);
        }

        Bar* getBar()
        {
            return bar.get();
        }

    private:
        std::unique_ptr<Bar> bar;
};

Класс Foo становится владельцем Bar, однако, Bar должен быть в состоянии изменить.Здесь Foo - это основной класс, с которым пользователь будет взаимодействовать.В то время как Bar можно больше рассматривать как тип данных, который изменяет вывод Foo.

Является ли решение возврата необработанного указателя на Bar предпочтительным вариантом?У меня такое ощущение, что это тормозит инкапсуляцию, которая не нужна для разработки API.Мои усилия по поиску еще не дали мне конкретного ответа именно на эту проблему.Но я мог бы просто искать неправильные условия поиска.

Вторая часть этого вопроса - как изменится этот пример, если Bar будет храниться в контейнере в Foo.Вернул бы я указатель на весь контейнер, итератор для контейнера ...?

Ответы [ 2 ]

0 голосов
/ 26 сентября 2019

Если вы беспокоитесь о getBar нарушении инкапсуляции, то вы также должны увидеть void setBar(std::unique_ptr<Bar> newBar) как такой разрыв.

Поскольку разрешение установить bar извне делает знание о barвозможно, ни один не является эксклюзивным для Foo, а тот, который передает Bar в Foo, все еще может иметь возможность изменить bar, поэтому Foo не может делать никаких предположений о состоянии Bar, так как он может изменитьсяв любое время.

С другой стороны, если вы хотите иметь доступ только для чтения к Bar через Foo, тогда const Bar* getBar() или const Bar& getBar() не будут так сильно нарушать инкапсуляцию, потому что getBarне позволит изменить Bar.

0 голосов
/ 26 сентября 2019

Является ли решение возврата необработанного указателя на Bar предпочтительным вариантом?

Это решение и не обязательно плохое.Возвращение ссылки было бы предпочтительным в тех случаях, когда объект всегда существует (не в этом случае, поскольку конструктор по умолчанию Foo не создает Bar).

Некоторые программисты предпочитают использовать оболочку дляголые указатели (такие как observer_ptr, который был предложен в стандарте), чтобы отличать его от указателя, целью которого является итерация массива, или от обладания голыми указателями (последних следует избегать).

У меня такое чувство, что это тормозит инкапсуляцию

Вся ваша предпосылка - нарушить инкапсуляцию, поскольку вы хотите "изменить внутренние объекты" .Если вы хотите избежать разрушения инкапсуляции, вам, возможно, потребуется еще больше изменить дизайн, чтобы вам не нужно было изменять внутренние объекты (внешне).

Решение, которое не нарушает инкапсуляцию, заключается впредоставить конкретный интерфейс для Foo для модификации, такой как:

void Foo::transmogrify_bar(int gadgets) {
    bar->transmofgrify(gadgets);
}

Полезна ли эта инкапсуляция для вашего API, это другой вопрос.В некоторых случаях это важно, в других это не имеет большого значения.

, если Bar будет храниться в контейнере в Foo.Вернул бы я указатель на весь контейнер, итератор для контейнера ...?

Хотели бы вы, чтобы клиент мог изменять контейнер (добавлять, удалять элементы)?

Это еще больше нарушает инкапсуляцию.Вместо этого у вас могут быть итераторы begin и end, которые не позволяют изменять сам контейнер, что приводит к эквивалентной инкапсуляции возврата указателя в случае одного объекта.

Вы можетепредоставьте только константные итераторы и добавьте итератор в качестве аргумента в transmogrify, чтобы сохранить инкапсуляцию изменения Bar.

Наконец, для полной инкапсуляции вам потребуется использовать шаблон PIMPL, чтобы полностью скрыть Bar.

...