【问题标题】:Class composition with public member objects具有公共成员对象的类组合
【发布时间】:2016-06-15 23:47:43
【问题描述】:

我正在尝试找出设计程序功能的最佳方式。

该程序的一个主要组件是一个 Camera 类。这个 Camera 对象代表了一个真实相机的程序用户界面,它通过一个图像采集卡连接到计算机。相机类可以链接到图像采集卡,开始和停止采集,还可以改变/访问许多不同的相机属性。当我说很多时,我指的是超过 250 个独特的命令。每个独特的命令都是通过帧采集器向物理相机发送一个串行字符串来发送给相机的。每个命令都可以被认为是三种类型之一。一个操作、一个查询和一个值。

动作命令是不需要等号的东西,例如“reset”、“open”、“close”

查询是您可以获得但不能设置的东西,通常与一个值相关联。例如“温度=?”、“sernum=?”、“maxframerate=?”命令将导致相机发回信息。这些值不能改变,因此“温度=20”会导致错误。

值是您可以获取和设置的东西,通常与值相关联。例如“framerate=30”和“framerate=?”是两个独特的命令,但我认为基本字符串“framerate”是一种值命令类型,因为它可以被变异和访问。

250 个独特的命令可以减少到 ~100 个 CameraActions、CameraQuerys 和 CameraValues。在我的 Camera 类中没有 250 个方法,我有一个想法来组合命令对象,而不是单独的 setter、getter 和 action。命令字符串可以在构造函数中提供,也可以使用设置器重置。然后我可以编写一个包含所有可用命令的 CameraCommands 对象,并将其作为公共成员提供给我的相机。

//CameraAction.h =============================================
class CameraAction {
public:
  CameraAction(std::string commandString, SerialInterface* serialInterface);
  void operator()() { _serialInterface->sendString(_commandString); }

private:
  SerialInterface* _serialInterface;
  std::string      _commandString;

}; 


//CameraValue.h =====================================================
class CameraValue {

public:

  CameraValue(std::string commandString, double min, double max, SerialInterface* serialInterface);

  void set(double value)
  {
    if(value > _maxValue) { throw std::runtime_error("value too high"); }
    if(value < _minValue) { throw std::runtime_error("value too low"); }
    std::string valueString = std::to_string(value);
    _serialInterface->sendString(_commandString + "=" + valueString);
  }

  double get()
  {
    std::string valueString = _serialInterface->sendString(_commandString + "=?");
    return atof(valueString.c_str());
  }

private:

  SerialInterface* _serialInterface;
  std::string      _commandString;
  double           _minValue;
  double           _maxValue;

};  


//CameraCommands.h ===================================================
class CameraCommands {

public:
  CameraCommands();
  CameraAction reset;
  CameraQuery  temperature;
  CameraValue  framerate;
  CameraValue  sensitivity;
  //... >100 more of these guys

};


//Camera.h ===========================================================
class Camera {
public:
  Camera();
  CameraCommands cmd;
  void startAcquisition();
  void stopAcquisition();
  void setDataBuffer(void* buffer);
  void setOtherThing(int thing);
};

以便用户可以执行以下操作:

Camera myCamera;
myCamera.cmd.reset();
myCamera.cmd.framerate.set(30);
myCamera.cmd.sensitivity.set(95);
double temperature = myCamera.cmd.temperature.get();
myCamera.startAcquisition();

等等……

这里的主要问题是我公开了公共成员变量,这应该是一个巨大的禁忌。我当前的对象设计是否合乎逻辑,或者我应该简单地实现 250 个 setter 和 getter 以及 100 多个 setter 和 getter 来改变最小和最大可设置值。

这对我来说似乎很笨拙,因为还有许多与 Camera 对象相关联的 setter/getter 与用户命令无关。用户界面最好提供方法 (cmd) 的范围,以便用户知道某些东西是在相机中发生物理变异,还是只是在编程对象中发生变异(其他方法)。有没有更好的方法来设计我的程序?

【问题讨论】:

  • 这似乎主要是界面设计的问题。您必须决定如何编写与Camera 类交互的代码,然后找出一种方法使您的界面尽可能接近。对我来说,camera.cmd.foo 语法似乎有多余的cmd 部分。我希望能够编写 Temperature t = camera.temperature();camera.setFrameRate(6); 甚至 camera.frameRate = 6;他们之间的互动。
  • 你的前几句话真的很重要。我试图使程序的内部工作变得干净且易于扩展,但我并没有真正考虑用户与 API 交互的最佳方式。到目前为止,很多建议似乎都喜欢 camera.frameRate = 5;这是一个很棒的概念,但可能会让其他查看代码的人感到困惑。我需要更深入地思考。

标签: c++ oop


【解决方案1】:

您基本上描述了一个有趣的层次结构:

Command -> Query -> Value.

  • Command 保存作为命令文本的字符串; 它还可以提供一个protected Send() 方法供其子级调用。
  • Query 还包含一个 (protected) int 变量(或其他变量),您可以立即使用 get() 和/或 operator int(),或从摄像头获取 query()
  • Valueset() 和/或operator =(int) 命令添加到Query

Value 的构造函数(特别是)可以有minmax,正如你所描述的那样。

Camera 对象可以有多个public 成员:

class Camera {

private: // Classes that no-one else can have!

    class Command; friend Command;
#include "Camera.Command.inc"

    class Query; friend Query;
#include "Camera.Query.inc"

    class Value; friend Value;
#include "Camera.Value.inc"

public: // Variables using above classes

    Command reset;
    Command open;  // Maybe make this one private, for friends?
    Command close; // Ditto?

    Query temperature;
    Query sernum;
    Query maxFrameRate;

    Value frameRate;

private: // Variables

    SerialPort port; // Allow Command and co. access to this

}; // Camera

这样组织,那么:

  • 变量的用户不能发出不可能的请求 - 没有办法这样做;
  • query()set() 方法隐藏了与物理相机接口的机制。

您会注意到我在Camera 类的中间添加了#include "Camera.XXX.inc"。注意:

  1. 这些子类的定义不会使 Camera 类混乱 - 但 C++ 编译器在使用它们之前需要它们,因此您需要将它们放在那里。如果您想知道他们的工作,只需打开文件!
  2. 我给了它们.inc 扩展名,因为它们“包含”在.h 文件中:它们不会作为自己的头文件单独存在。

【讨论】:

  • 我非常喜欢这个概念和继承。我不确定在你的类中包含是否是常见的做法,但我以前从未考虑过它,它在嵌套类令人眼花缭乱的其他场景中对我非常有用。使用 Command、Query 和 Value 与 Camera 交朋友的目的是什么?
  • @Sam 这样他们就可以访问SerialPort(或任何你称之为的)。您需要将Camera 传递给每个Command,那么为什么不将SerialPort 存储在那里供他们使用呢?也许只有Command 需要friends - 也许没有。
  • @Sam 另请注意我的文件命名方案:inc 用于哪个主标题很明显。但还要注意,如果父类只需要引用或指向子类的指针,可以将class XXX;留在类内,但实际上将#include放在别处。
  • 所以您是说 Command 将有一个私有成员,它是指向 this 的 Camera* 指针,并且通过该指针(在构造函数中发送)我将能够访问私有 SerialPort?还是访问 Camera::port 比这更简单/直接?
  • 我会使用 ref (&amp;) 而不是指针 (*)。请注意,如果这是您需要反向引用的唯一原因,您可能会考虑在 Camera 中创建 serialPort private static - 这样Command 不需要反向引用,它可以使用 Camera::serialPort而不是camera.serialPort。这意味着一次只能连接一个摄像头,所以我认为最好从一开始就放入 back-ref。
【解决方案2】:

您可以使用一个或多个结构来对“设置”进行分组,然后公开一种方法来设置它们:

typedef struct settings{
  int setting1;
  int setting2;
}MySettings;

class Myclass{
 private :
   int setting1;
   int setting2;

 public Myclass(MySettigs *settings)
{
  if(null != settings){
     setting1=settings->setting1;
     setting2=settings->setting2;
  }
}

public void ChangeSettings (MySettings *setting){
  if(null != settings)
  {
     setting1=settings->setting1;
     setting2=settings->setting2;
  }
}

public void TakeSettings (MySettigs *settings){
  [copy local variables into the passed struct]
}

我强烈建议在对象“可操作”时更改设置时要小心。您可能会陷入未定义状态,即在另一个线程正在使用它们时更改设置。

【讨论】:

    【解决方案3】:

    在你提到的设计中,我不认为通过组合暴露公共成员是一个很大的禁忌。

    当暴露公共成员时,最大的禁忌是不安全访问你的类内部。

    例如允许公开访问CameraValue::_maxValue。用户可以将该值更改为任何值,从而导致各种未定义的行为。

    如果由我来设计,我不会有 CameraCommands 成员,因为从外观上看,除了间接级别之外,它没有添加任何其他内容。

    我要么将所有 CameraActionCameraValue 成员添加为相机类的一部分,要么继承它们。

    类似这样的:

    CameraCommands 合并到Camera

    class Camera 
    {
    public:
      Camera();
    
      CameraAction reset;
      CameraQuery  temperature;
      CameraValue  framerate;
      CameraValue  sensitivity;
      //... >100 more of these guys
    
      void startAcquisition();
      void stopAcquisition();
      void setDataBuffer(void* buffer);
      void setOtherThing(int thing);
    };
    

    CameraCommands 继承到Camera

    class Camera : public CameraCommands
    {
    public:
      Camera();
    
      void startAcquisition();
      void stopAcquisition();
      void setDataBuffer(void* buffer);
      void setOtherThing(int thing);
    };
    

    您甚至可以为 CameraValue 等提供一些运算符,以便您可以通过赋值 (operator=) 设置值,并通过隐式转换 (operator T) 或取消引用 (operator*) 获取值:

    template<typename T>
    class CameraValue
    {
    public
        CameraValue(SerialInterface*, std::string cmd);
    
        CameraValue& operator=(const T& val)
        {
            _val = val;
            std::string val_str = std::to_string(_val);
           _ser_ifc->sendString(_cmd + "=" + val_str);            
        }
    
        const T& get() const
        {
            return _val;
        }
    
        // implicit access to _val
        operator const T&() const
        {
            return _val;
        }
    
        // dereference operator to access _val
        const T& operator*() const
        {
            return _val;
        }
    private:
        T _val;
        SerialInterface* _ser_ifc;
        std::string      _cmd;
    };
    

    然后在您的班级中使用CameraValue,如下所示:

    using CameraFramerate = CameraValue<int>;
    
    CameraFramerate framerate;
    

    上述技术为 (IMO) 提供了Camera 的更可组合使用,例如:

    Camera camera;
    
    // setting values
    camera.framerate = 30;
    camera.sensitivity = 95;
    
    // getting values
    int framerate =  camera.framerate; // uses operator T&()
    int framerate = *camera.framerate; // uses operator*()
    

    这里的关键点是Camera::framerate 等不允许任何可能以未定义和/或不安全的方式更改相机类的内部状态的访问。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-09-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-10-10
      • 1970-01-01
      相关资源
      最近更新 更多