C ++中的策略模式。实施选项

时间:2016-03-12 01:46:15

标签: c++ templates pointers shared-ptr strategy-pattern

以下是所谓的简化示例(我希望 - 如果我错了,请纠正我)策略模式:有一个类FileWriter,它将键值对写入一个文件并使用IFormatter接口的对象来格式化正在写入的文本。有不同的格式化程序实现,并且在创建FileWriter时传递格式化程序对象。 这是这种模式的一个(坏)实现:

#include <iostream>
#include <fstream>
#include <stdlib.h>
#include <sstream>

using namespace std;

class IFormatter {
  public:
    virtual string format(string key, double value) = 0;
};

class JsonFormatter : public IFormatter {
  public:
    string format(string key, double value) { 
        stringstream ss;
        ss << "\""+key+"\": " << value;
        return ss.str();
    }
};

class TabFormatter : public IFormatter {
  public:
    string format(string key, double value) { 
        stringstream ss;
        ss << key+"\t" << value;
        return ss.str();
    }
};

class FileWriter {
  public:  
    FileWriter(string fname, IFormatter& fmt):fmt_(fmt)
    {
        f_.open(fname.c_str(), ofstream::out);
    }

    void writePair(string key, double value)
    {
        f_ << fmt_.format(key, value);
    }

  private:
    ofstream f_;
    IFormatter& fmt_;
};    

可以看出,这种方法的主要缺点是不可靠 - 传递给Formatter的{​​{1}}对象必须在整个FileWriter期间存在生命周期,因此像FileWriter这样的调用直接导致FileWriter("test.txt", JsonFormatter())

在这方面,我想讨论以“易于使用”和简单的要求实施这种方法的其他选择:

  • 创建文件编写器时可以传递新的格式化程序,或
  • 可以传递和使用现有的格式化程序。

我想出了下面描述的几种替代方案(IMO):

  • 模板:将SegFault作为模板类,将精确的FileWriter作为参数; 缺点:丑陋的呼叫:FormatterClass - 此处,FileWriter<JsonFormatter>("test.txt", JsonFormatter())输入两次。
  • 原始指针: JsonFormatter; 缺点 - 谁应该删除格式化程序对象? FileWriter("test.txt", new JsonFormatter())?如果是,那么一旦FileWriter对象尝试删除格式化程序,则传递现有格式化程序的地址将导致SegFault
  • 共享指针: FileWriter; 缺点:难以调用,再次,如果在创建文件编写器之前创建了formatter会怎么样?

这里最好的做法是什么?

更新

回答建议使用FileWriter("test.txt", dynamic_pointer_cast<IFormatter*>(shared_ptr<JsonFormatter*>(new JsonFormatter()))的答案 - 如果Formatter可以存储状态(例如,精确度)并且还有其他方法,例如std::function,对于CSV文件会怎么样?

另外,由于它是一个抽象类,因此无法按值存储getHeader()

4 个答案:

答案 0 :(得分:2)

最简单的解决方案是使用:

JsonFormatter formatter;
FileWriter writer("test.txt", formatter);
// Use writer.

另一个更好的选择是clone()中有一个IFormatter函数。然后,FileWriter可以克隆该对象,获取克隆的所有权并在其析构函数中删除它。

 class IFormatter {
   public:
     virtual string format(string key, double value) = 0;
     virtual IFormatter* clone() const = 0;
 };


 class FileWriter {
   public:  

     FileWriter(string fname, IFormatter const& fmt):fmt_(fmt.clone())
     {
         f_.open(fname.c_str(), ofstream::out);
     }

     ~FileWriter()
     {
        delete fmt_;
     }

     void writePair(string key, double value)
     {
         f_ << fmt_->format(key, value);
     }

   private:
     ofstream f_;
     IFormatter* fmt_;
 };    

现在,您也可以使用临时对象调用FileWriter

FileWriter writer("test.txt", JsonFormatter());
// Use writer.

答案 1 :(得分:1)

  

templates:将FileWriter作为模板类,将FormatterClass作为参数;缺点:难看调用:FileWriter(“test.txt”,JsonFormatter()) - 这里,JsonFormatter被输入两次。

更多模板!

template<class Formatter> 
FileWriter<Formatter> makeFileWriter(const std::string& filename, const Formatter& formatter) 
{return FileWriter<Formatter>(filename, formatter);}
塔达!现在它很简单:

auto fileWriter = makeFileWriter("test.txt", JSonFormatter());`

答案 2 :(得分:1)

这是标准库的作用(例如std::shared_ptr可以删除)。 Formatter必须是可复制的,显然表达式f << fmt(key, value)必须格式正确。

class FileWriter {
public:
    template<typename Formatter>
    FileWriter(std::string fname, Formatter fmt) :
        fmt(fmt)
    {
        f.open(fname.c_str(), std::ofstream::out);
    }

    void writePair(std::string key, double value)
    {
        f << fmt(key, value);
    }

private:
    std::ofstream f;
    std::function<std::string (std::string, double)> fmt;
};

如果界面中需要多个函数,则可以使用原始方法,但使用std::unique_ptrstd::shared_ptr控制格式化程序的生命周期(请记住使析构函数为虚拟)。

struct Formatter
{
    virtual ~Formatter() {}
    virtual std::string format(std::string key, double value) = 0;
};

class FileWriter {
public:
    FileWriter(std::string fname, std::unique_ptr<Formatter>&& fmt_)
    {
        if (!fmt_)
        {
            throw std::runtime_error("Formatter cannot be null");
        }

        f.open(fname.c_str(), std::ofstream::out);

        fmt = std::move(fmt_); // strong exception safety guarantee
    }

    void writePair(std::string key, double value)
    {
        f << fmt->format(key, value);
    }

private:
    std::ofstream f;
    std::unique_ptr<Formatter> fmt;
};

如果要将现有Formatter传递给FileWriter,则需要将其复制/移动到智能指针以转移所有权,或者需要将其包装在格式化程序界面中。

class FormatterProxy : public Formatter
{
public:
    FormatterProxy(Formatter& fmt) :
        fmt(fmt)
    {
    }

    std::string format(std::string key, double value)
    {
      return fmt.format(key, value);
    }

private:
  Formatter& fmt;
};

这仍然存在您要避免的生命周期管理问题。 但是,我没有看到任何解决方法。您可以将Formatter的唯一或共享所有权提供给FileWriter,或者将生命周期管理留给来电者(如果您重视效率而不是安全,这是一种非常有效的方法。)

答案 3 :(得分:0)

using IFormatter - std::function<std::string(std::string,double)>;

您的格式化程序应该是一个函数,而不是一个接口。

如果用户希望保证生命周期,则可以使用std::ref,如果他们想要模糊的生命周期,或者通过按值传递,则可以使用共享ptr。

如果你想要一个更丰富的界面,你可以采取一堆这样的,或者写一堆这样的类(通过继承或手动编写notstd::functions)。

存储IFormatter fmt;按值,使用fmt(a,b)代替fmt.format(a,b)(DRY!)。如果需要,客户端代码可以使其成为ref或智能语义。

继承作为一个实现细节,而不是驱动你的设计,正在释放。