шаблоны проектирования — C ++ Decorator добавлен в std :: vector

У меня есть базовый класс для записи, и я хочу добавить дополнительные поля и функции сравнения, используя декораторы, и иметь возможность связывать декораторы (записи могут иметь адрес электронной почты, или дату рождения, или и то, и другое, или ни одного). У меня также будет много таких декораторов; один для каждого дополнительного поля, и его функция сравнения. Как только это будет сделано, я собираюсь добавить объекты в вектор, используя указатель базового класса.

Вот точный код:

class BaseRecord
{
public:
virtual bool Compare();     // defined elsewhere

protected:
std::string m_strName;
std::string m_strAddress:
};

class BaseDecorator : public BaseRecord
{
public:
BaseDecorator(BaseRecord *pBase) : m_pBase(pBase){}

bool Compare()
{
return m_pBase->Compare();
}

private:
BaseRecord *m_pBase;
};

class EmailDecorator : public BaseDecorator
{
public:
EmailDecorator(BaseRecord *pBase) : EmailDecorator(pBase){}

bool Compare()
{
if (!CompareEmail())        // defined elsewhere
{
return false;
}

BaseDecorator::Compare();
}

private:
std::string m_strEmail
};

class DOBDecorator : public BaseDecorator
{
public:
DOBDecorator(BaseRecord *pBase) : DOBDecorator(pBase){}

bool Compare()
{
if (!CompareDOB())      // defined elsewhere
{
return false;
}

BaseDecorator::Compare();
}

private:
std::string m_strDOB;
};

Это классы. Теперь я хотел бы добавить их в вектор:

vector<BaseRecord *> m_vecRecords;

BaseRecord pRecord = new BaseRecord();

// wrong - copies pointer only to vector
m_vecRecords.push_back(pRecord);

// OK - default copy constructor for BaseRecord used
m_vecRecords.push_back(new BaseRecord(*pRecord));

// now chain the decorators

// pRecord is a BaseRecord
BaseRecord pRecord = new EmailDecorator(pRecord);

//wrong - copies pointer only to vector
m_vecRecords.push_back(pRecord);

// ??? needs copy constructor
m_vecRecords.push_back(new EmailDecorator(*pRecord));

// pRecord is an EmailDecorator
BaseRecord pRecord = new DOBDecorator(pRecord);

// wrong - copies pointer only to vector
m_vecRecords.push_back(pRecord);

// ??? needs copy constructor
m_vecRecords.push_back(new DOBDecorator(*pRecord));

Теперь попробуйте написать конструкторы копирования:

// should p be an EmailDecorator *, or a BaseDecorator * ?
EmailDecorator::EmailDecorator(const EmailDecorator *p)
{
// this will leak - no delete in the destructor
// I have not supplied a destructor
m_pBase = new BaseDectorator(p);
m_strEmail = p->m_strEmail;
}

// should p be a DOBDecorator *, or  BaseDecorator * ?
// in the above example, when the copy constructor is needed, it is an EmailDecorator *

DOBDecorator::DOBDecorator(const DOBDecorator *p)
{
// this will leak - no delete in the destructor
// I have not supplied a destructor
m_pBase = new BaseDectorator(p);
m_strDOB = p->m_strDOB;
}

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

4

Решение

С точки зрения декоратора, вы не так уж и плохи; к сожалению, вы далеко позади с точки зрения C ++.

С точки зрения Decorator, главная проблема у вас в том, что ваш интерфейс не должен иметь никакого значения. В противном случае, как и здесь, BaseDecorator объект имеет по крайней мере два name поля:

  • один из своего базового класса
  • один из его членов

и вы забыли инициализировать один из базового класса.

С точки зрения C ++, к сожалению, это сложно, потому что вы не рассматривали владение. В языках с сборщиком мусора вы можете срезать углы, но в C ++ это не работает.

Итак, давайте исправим это, не так ли?


Прежде всего, нам нужен чистый интерфейс:

class IRecord {
public:
virtual bool lessThan(IRecord const& other) const = 0;
};

Я дам вам понять, как на самом деле сравнить две записи; это может быть нелегко, используя подход декоратора, потому что нет никакой гарантии, что только потому, что this является EmailDecoratorУ других также есть EmailDecorator где-то в своей цепочке.

Затем мы можем создать подход декоратора, и мы собираемся сделать это, используя требование сильной собственности:

class RecordDecorator: public IRecord {
protected:
RecordDecorator(std::unique_ptr<IRecord> r): _decorated(std::move(r)) {}

private:
std::unique_ptr<IRecord> _decorated;
};

Мы также можем построить наш первый камень:

class BaseRecord final: public IRecord {
public:
BaseRecord(std::string name, std::string address):
_name(std::move(name)), _address(std::move(address)) {}

virtual bool lessThan(IRecord const& record) const override;

private:
std::string _name;
std::string _address;
}; // class BaseRecord

И тогда мы можем наконец попытаться создать Значение класса (то есть класс, которым можно манипулировать по значению) из этого:

class Record {
public:
Record(std::string name, std::string address):
_data(std::make_unique<BaseRecord>(std::move(name), std::move(address)) {}

bool lessThan(Record const& other) const {
return _data->lessThan(other._data);
}

template <typename D, typename... Args>
void decorate(Args&&... args) {
_data = std::make_unique<D>(std::move(_data), std::forward<Args>(args)...);
}

private:
std::unique_ptr<IRecord> _data;
}; // class Record

Этот дизайн звук:

  • не пропускает память,
  • он не хранит несколько расходящихся копий данных-членов,
  • и это легко манипулировать (для клиента)

Однако, как оно есть, Record не будет иметь конструктора копирования (или оператора назначения копирования), потому что std::unique_ptr скучает по тем.

Если вы хотите добавить их, вам нужно добавить virtual std::unique_ptr<IRecord> clone() const = 0 (*) до IRecord который будет отвечать за углубленную копию. Вот где наш RecordDecorator светит:

class RecordDecorator: public IRecord {
protected:
RecordDecorator(std::unique_ptr<IRecord> r): _decorated(std::move(r)) {}

RecordDecorator(RecordDecorator const& other):
_decorated(other._decorated->clone()) {}

RecordDecorator& operator=(RecordDecorator const& other) {
if (this == &other) { return *this; }
_decorated = other._decorated.clone();
return *this;
}

// These two got disabled when we wrote our own copy constructor
// and copy assignment operator, so let's re-enable them.
RecordDecorator(RecordDecorator&&) = default;
RecordDecorator& operator=(RecordDecorator&&) = default;

private:
std::unique_ptr<IRecord> _decorated;
};

Теперь любой класс, наследующий от RecordDecorator автоматически получает конструктор копирования и оператор копирования-назначения, если он не содержит собственных не копируемых членов.

И мы также можем улучшить Record с запрошенным копированием:

Record::Record(Record const& other):
_data(other._data.clone())
{}

Record& Record::operator=(Record const& other) {
if (this == &other) { return *this; }
_data = other._data.clone();
return *this;
}

// These two got disabled when we wrote our own copy constructor
// and copy assignment operator, so let's re-enable them.
Record::Record(Record&&) = default;
Record& Record::operator=(Record&&) = default;

И глазурь на торте, как все это использовать:

class EmailDecorator final: public RecordDecorator {
public:
EmailDecorator(std::unique_ptr<IRecord> base, std::string email):
RecordDecorator(std::move(base)), _email(email) {}

virtual std::unique_ptr<IRecord> clone() const override {
return std::make_unique<EmailDecorator>(*this);
}

virtual bool lessThan(IRecord const&) const override; // up to you ;)

private:
std::string _email;
}; // class EmailDecorator

int main() {
Record record{"John, Doe", "12345 Mimosa Road, 3245 Washington DC"};
record.decorate<EmailDecorator>("john.doe@aol.com");

std::vector<Record> vec;
vec.push_back(record); // make a copy
vec.back().decorate<EmailDecorator>("doe.john@msn.com"); // another e-mail!
}

Но … добавление полей через украшение сделает любую логику в этих полях довольно неловкой … и вы скоро почувствуете боль: как только вы попытаетесь реализовать lessThan на самом деле.

2

Другие решения

Других решений пока нет …

По вопросам рекламы ammmcru@yandex.ru
Adblock
detector