Преобразование объекта в другой класс

#c #class

#c #класс

Вопрос:

Сэнди принадлежит к классу Person, который принимает ислам и принимает новое имя Фатима, и обладает всеми атрибутами Сэнди, с новыми мусульманскими атрибутами (например, религия == Ислам и т. Д.). На этом этапе Сэнди можно удалить, и Фатима, теперь из класса Муслим, впредь будет играть роль Сэнди. Проблема в том, что из-за ее нового адреса все люди, которые знали Сэнди, не знают Фатиму. Изменение адреса Сэнди вручную на адрес Фатимы для всех тех людей, которые знали Сэнди, явно неприемлемый метод. Есть предложения по улучшению дизайна? Вот мой упрощенный код, показывающий проблему:

 #include <iostream>
#include <string>
#include <typeinfo>

class Person {
        std::string name;
        Person* bestFriend;
    public:
        Person (const std::stringamp; newName) : name (newName) {}
        virtual ~Person() = default;
        std::string getName() const {return name;}
        void setName (const std::stringamp; newName) {name = newName;}
        Person* getBestFriend() const {return bestFriend;}
        void setBestFriend (Person* newBestFriend) {bestFriend = newBestFriend;}
};

class Muslim: public Person {
    public:
        Muslim (const Personamp; person) : Person (person) {
                          // religion = Islam; etc... 
                }
};

int main() {
    Person *mary = new Person ("Mary"), *sandy = new Person ("Sandy");
    mary->setBestFriend (sandy);
    std::cout << "sandy = " << sandy << ", " << typeid(*sandy).name() << std::endl;
    std::cout << mary->getName() << "'s best friend is " << mary->getBestFriend()->
    getName() << "." << std::endl;
    Muslim* fatima = new Muslim (static_cast<Muslim>(*sandy));  // the big change
    fatima->setName ("Fatima");  // should now delete sandy, because fatima takes on every attribue of sandy
    std::cout << "fatima = " << fatima << ", " << typeid(*fatima).name() << std::endl;
    std::cout << mary->getName() << "'s best friend is " << mary->getBestFriend()->
    getName() << "." << std::endl;  // still Sandy, of course
}
  

Вывод:

сэнди = 0x32658, 6 человек

Лучший друг Мэри — Сэнди.

фатима = 0x23fec0, 6Muslim

Лучший друг Мэри — Сэнди.

Конечно, мы хотим иметь: лучший друг Мэри — Фатима, с мэри-> getBestFriend()-> getReligion() == Ислам и т.д… Как перепроектировать все это так, чтобы это было автоматизировано (предположим, что есть тысячи людей, которые ее знают)?

Я хочу использовать наследование, потому что класс Muslim будет иметь много сложных переопределений методов Person (а также множество новых элементов данных и методов).

Комментарии:

1. Наследование здесь кажется излишним. Почему нет только religion члена Person ?

2. @Джозеф Мэнсфилд. Потому что класс Muslim будет иметь много сложных переопределений методов Person.

3. @prestokeys В таком случае, почему бы не использовать внедрение зависимостей для этих действий и не ввести другой набор, основанный на религии (предполагая, что так работают ваши корреляции)

4. Это хорошо известный недостаток C , которого нет в некоторых других OO-языках, таких как Smalltalk. Обходным путем, как всегда, является добавление уровня косвенности. У вас есть класс Person, который представляет неизменяемого (до прихода grim reaper) person, и атрибуты PersonAttributes, на которые ссылается Person, которые вы можете прозрачно обновить

5. «все люди, которые знали Сэнди, не знают Фатиму»…. вероятно, правильное предположение, если поведение было полностью заменено. Но, тем не менее, ваш вопрос о кодировании интересен.

Ответ №1:

Моим первым подходом было бы, как предложил @Joseph Mansfield в комментариях, создать religion атрибут Person . Следовательно, изменение будет означать только изменения в атрибутах, а не новый объект.

Тем не менее, если для вас обязательно иметь другой класс, вы можете использовать шаблон observer . То есть любой друг человека должен быть подписан (в адресной книге друзей) на список изменений человека. Этот подписчик должен реализовать метод из того же интерфейса (подписчика), который должен вызываться Person объектом при изменении его атрибутов.

Подводя итог:

  • Иметь список адресов для каждого друга в Person ( friends ) .
  • Каждый класс дружественных объектов должен реализовывать общий интерфейс, в котором объявляется метод уведомления об изменении ( friendChanged(...) )
  • При Person изменении путем создания нового объекта нового класса он должен пройти через свой friends список, вызывая friendChanged и передавая им новый адрес объекта person. После этого вы можете уничтожить и освободить старый объект.

Наконец, я не думаю, что религия должна быть представлена как подкласс. Религия звучит для меня как особенность человека, и, следовательно, она должна быть атрибутом. Существует принцип ООП, который гласит: предпочтение композиции перед наследованием. Кажется, что ваш дизайн искусственно противоречит этому принципу.

Комментарии:

1. Похоже, это хорошее начало. Но предположим, что класс BasketBallTeam имеет Сэнди в качестве члена. Итак, BasketBallTeam должна иметь тот же тип подписки на шаблон наблюдателя, чтобы позаботиться о ее изменении адреса, когда Сэнди превращается в новый класс? По сути, сам класс, который имеет элементы данных указателя на Person, должен делать то же самое? Я не против реализовать это (совсем не сложно), если это заставит все это работать.

2. @prestokeys Вы можете заставить BasketBallTeam реализовать интерфейс наблюдателя (абстрактный класс). Вы даже можете делегировать реализацию BasketBallTeam observer каждому методу члена команды friendChanged BasketBallTeam.

3. Это прекрасное решение, hovewer, нужно быть очень точным при его реализации в многопоточной программе, где могут происходить одновременные изменения общих друзей.

4. @ nm. Помимо этого, можете ли вы предсказать какие-либо другие исключения, которые могут возникнуть при использовании метода Пабло Идальго?

Ответ №2:

 class Person;

class ObserverPersonInterface {
    public:
        virtual void registerObserver (Person*) = 0;
        virtual void removeObserver (Person*) = 0;
        virtual void notifyObservers() const = 0;       
};

class AddressChangeData : public ObserverPersonInterface {
    public:
        void addressChange (const Person* oldPerson, Person* newPerson) {
            oldAddress = oldPerson;
            newAddress = newPerson;
            // observers.remove (oldAddress);  // but not relevant here
            notifyObservers();
        }
        virtual void registerObserver(Person* person) override {
            observers.emplace_back (person);
        }
        virtual void removeObserver(Person* person) override {
            observers.remove (person);
        }
    private:
        virtual inline void notifyObservers() const override;

        std::list<Person*> observers;  // should perhaps be a red-black tree for greater efficiency
        const Person *oldAddress;
        Person *newAddress;
} changeOfAddressData;

class Person {
    public:
        Person() {
            changeOfAddressData.registerObserver(this);
        }
        // every new Person constructed is registered to changeOfAddressData
        Person(const std::stringamp; newName) : name (newName) {
            changeOfAddressData.registerObserver (this);
        }
        virtual ~Person() = default;
        std::string getName() const {return name;}
        void setName (const std::stringamp; newName) {name = newName;}
        Person* getBestFriend() const {return bestFriend;}
        void setBestFriend (Person* newBestFriend) {bestFriend = newBestFriend;}
    protected:
        void notifyChangeOfAddress(const Person* oldAddress, Person* newAddress) const {
            changeOfAddressData.addressChange (oldAddress, newAddress);
            delete oldAddress;
        }
    private:
        std::string name;
        Person* bestFriend;

};

class Muslim : public Person {
    public:
        Muslim() = default;
        Muslim(const Personamp; person) : Person(person) {
            changeOfAddressData.registerObserver (this);
            notifyChangeOfAddress (amp;person, this);
        }
};

inline void AddressChangeData::notifyObservers() const {
    for (Person* x : observers) {
        if(x->getBestFriend() == oldAddress) {
            x->setBestFriend(newAddress);
        }
    }
}

int main() {
    Person *mary = new Person ("Mary"), *sandy = new Person ("Sandy");

    mary->setBestFriend (sandy);
    sandy->setBestFriend (mary);

    std::cout << "mary = " << mary << ", " << typeid(*mary).name() << std::endl;
    std::cout << "sandy = " << sandy << ", " << typeid(*sandy).name() << std::endl;
    std::cout << mary->getName() << "'s best friend is " << mary->getBestFriend()->getName() << "." << std::endl;
    std::cout << sandy->getName() << "'s best friend is " << sandy->getBestFriend()->getName() << "." << std::endl;

    Muslim* fatima = new Muslim(static_cast<Muslim>(*sandy));

    // all subscribers of changeOfAddressData notified of sandy's address change, sandy is deleted automatically
    fatima->setName("Fatima");

    std::cout << "fatima = " << fatima << ", " << typeid(*fatima).name() << std::endl;
    std::cout << mary->getName() << "'s best friend is " << mary->getBestFriend()->getName() << "." << std::endl;
    std::cout << fatima->getName() << "'s best friend is " << fatima->getBestFriend()->getName() << "." << std::endl;

    Muslim* shazia = new Muslim (static_cast<Muslim>(*mary));
    // all subscribers of changeOfAddressData notified of mary's address change, mary is deleted automatically
    shazia->setName ("Shazia");

    std::cout << "shazia = " << shazia << ", " << typeid(*shazia).name() << std::endl;
    std::cout << shazia->getName() << "'s best friend is " << shazia->getBestFriend()->getName() << "." << std::endl;
    std::cout << fatima->getName() << "'s best friend is " << fatima->getBestFriend()->getName() << "." << std::endl;

    std::cin.get();
}
  

Комментарии:

1. При написании кода, который вы собираетесь опубликовать здесь, пожалуйста, установите в настройках вкладок IDE значение 4 пробела и установите заменить табуляции пробелами. Это позволяет вам просто скопировать и вставить код как есть, выделить его, а затем нажать кнопку code block, и он будет выглядеть так, как вы его ввели. Замена табуляции пробелами также предотвращает появление неразборчивого кода, если другой человек, просматривающий ваш код, имеет другую настройку табуляции.

2. Это не ответ, это просто целый код. Пожалуйста, добавьте контекст.

3. @ Jeffrey. Если вы запустите код, он правильно выполнит исходную задачу. Объяснение уже было дано Пабло Идальго, который предложил это.

4. Увы! Я обнаружил, что приведенный выше код не будет работать, если класс является виртуальным производным классом, потому что старые адреса не будут совпадать со значениями. Каково решение в этом случае?