面向对象设计来建模通用设备。

5
在面试中,我被问到了以下面向对象系统设计问题。
有多个设备,例如Echo Show、Echo Dot、Echo Tab、智能微波炉、Fire TV Stick等。
- Echo Show - 它有显示屏和扬声器。它使用电力供应。 - Echo Dot - 它有扬声器。它使用电源供应。 - Echo Tab - 它有扬声器。它使用电池供应。电池可以充电。 - 智能微波炉 - 它有屏幕显示。它使用电源供应。 - Fire TV Stick - 它有扬声器。它使用电源供应。
所以基本上这些是3个类别——扬声器/屏幕显示/扬声器和屏幕显示
有两个类别——电源供应/电池供应。
可以对任何这些设备进行查询,比如打印状态。以下是每个设备的可能输出:
- Echo Show - 根据它是否连接到电源供应,输出“正在充电”或“未在充电”。此输出应出现在屏幕和扬声器上。 - Echo Dot - 根据它是否连接到电源供应,输出“正在充电”或“未在充电”。此输出应只出现在扬声器上。 - Echo Tab - 根据电池是否正在充电,输出“电池正在充电”或“电池未充电且电量为70%”。此输出应只出现在扬声器上。 - 智能微波炉 - 根据它是否连接到电源供应,输出“正在充电”或“未在充电”。此输出应只出现在屏幕上。 - Fire TV Stick - 根据它是否连接到电源供应,输出“正在充电”或“未在充电”。此输出应只出现在扬声器上。
假设有内置的类来说话和打印屏幕。如果我们将字符串传递给这些类对象,它们将执行相应的工作。
现在编写4-5个类来模拟此场景。
设计应具有可扩展性,这意味着如果明天有任何新设备带有新组合,则可以实现它而不创建任何新类。因此,您不应为每个设备创建类。
以下是我的面向对象解决方案,但是面试官对vector<Output*> outputs特别不满意。他建议使用某种设计模式来代替向量。您能想到更好的解决方案吗?
class Output {
   public:
    virtual void print(string);
};
class Display : public Output {
    DisplayScreen obj;
   public:
      void print(string str) { obj.print(str); }
};
class Speaker : public Output {
    Audio obj;
   public:
      void print(string str) { obj.print(str); }
};
class PowerSupply {
   public :
    virtual string get_status();
};
class BatteryPower : PowerSupply {
   bool isCharging;
   int chargeLevel;
   public :
     string get_status();
};
class ElectricPower : PowerSupply {
   bool isCharging;
   public :
     string get_status();
};
class Device {
    vector<Output*> outputs;//I used vector because Echo show has both display and speaker
    PowerSupply powersupply;
    Device(vector<Output> &outputs, PowerSupply powersupply) {
        this->outputs = outputs;
        this->powersupply = powersupply;
     }
};

@463035818_is_not_a_number 为什么你要粘贴对象切片的链接?如果我将 vector<Output> 更改为 vector<Output*>,那么面试官也不喜欢使用 vector。 - Ranju
我们无法确定面试官究竟为什么不满意,但是 std::vector<Output> 明显是错误的,而 std::vector<Output*> 差不多还可以。我本来想将这个问题标记为重复的,但已经有一个答案了,我认为这个答案能够更好地解释问题,因此我只是放了链接。 - 463035818_is_not_a_number
我将“vector<Output> outputs”更改为“vector<Output*> outputs”,以澄清混淆。面试官建议使用某些设计模式,而不是向量。 - Ranju
1
附注:print(string) 应为 const。 在我看来,问题在于你没有与面试官沟通:“你有想到哪种模式?”或者“应该针对vector的哪个具体问题?” 我个人不太喜欢你使用vector的理由。为什么不将Device作为一个带有非类型模板参数std::size_t kOutputCount的模板类,并为Device<1ULL>进行模板特化呢? 此外,this->outputs是一个指向Output*vector,而不是Outputvector…… 哦,还有那个构造函数是private的。 - viraltaco_
3个回答

4

vector<Output>不允许继承,因为它直接存储Output而不是指针或引用。如果你将一个DisplaySpeaker存储在这个向量中,它就会被切片

由于每个设备的输出都是独特的,我建议使用一个独特指针(vector of unique pointers)。

std::vector<std::unique_ptr<Output>> outputs;

如果我将vector<Output>更改为vector<Output*>,那么面试官仍然不满意使用vector。 - Ranju
@Ranju,你现在正在面试吗? - 463035818_is_not_a_number

1
我认为面试官要求使用某种设计模式并没有任何建设性。设计模式是实现目标的工具;如果没有目标,设计模式就毫无意义。他本可以说:“我们预计需要构建许多类似设备,且差异极小。你如何完成这个任务?”而不是“现在加上一个随机的设计模式,我们就完成了。”除了 John Kugelman 在此回答的技术问题 之外,我认为你可以使用几种设计模式,而不仅仅是传入设备和电源的向量。以下是我能想到的一些例子:

工厂模式

关键字:工厂模式研究

class DotFactory
{
    DotFactory(/*maybe you need parameters*/);
    Device* getDevice(/*more parameters e.g. speaker count*/);
}

建造者模式

研究关键词:建造者模式

根据复杂性和用例,您可以将设备构建器集成到设备中。为简单起见,我是这样做的。

class Device 
{
    // Your stuff
    vector<Output*> outputs;
    PowerSupply powersupply;
    Device(vector<Output*> &outputs, PowerSupply *powersupply);

    // Builder functions
    bool addPowerSupply(PowerSupply*);
    bool addOutput(Output*);

    // If outside your Device class also integrate:
    Device* getBuiltDevice();
};

其他模式和组合(我原本期望能有多个。也许有人可以编辑更多。)

设备的单例列表(在大多数情况下都是反模式)

根据用例,您还可以拥有任何东西(方法、容器、类等),将预定义的“设备”保存在其中,如果每个应用程序只需要一个,则可以使用该方法。


1
我建议按照以下方式引入以下5个类:
  • PowerStatusProvider -> 用于电源状态等的接口/策略类
  • BatteryStatus -> 所有电池供电设备的具体实现
  • WiredStatus -> 所有有线供电设备的具体实现
  • OutputHandler -> 使用提供的服务(屏幕、扬声器)将状态发布到任何端点
  • Device -> 您的设备的实际抽象
我使用结构体代替类来缩短符号。通过依赖注入的函数对象获取电池电量。C++20有std::format,也许您的编译器也有这个功能。

// Assuming Screen and Speaker are provided in a common interface 
struct OutputDevice{
    virtual void publish(std::string const & output) = 0;
    virtual ~OutputDevice()=default;
}

using OutputDevice_sp = std::shared_ptr<OutputDevice>;

struct Screen : OutputDevice{
    void publish(std::string const & output) override {
        // ...
        };
};

struct Speaker : OutputDevice{
    void publish(std::string const & output) override {
        // ...
        };
};

/// Here goes the actual implementation

struct PowerStatusProvider{
    virtual std::string to_string(bool Connected) const = 0;
    virtual ~PowerStatusProvider()=default;
};

struct BatteryStatus : PowerStatusProvider{
    BatteryStatus(std::function<double()> BatteryLevelProvider)
    : mBatteryLevelProvider(BatteryLevelProvider){}
    
    std::string to_string(bool Connected) const override
    {
        if (Connected)
            return "Battery is charging";
        else
            return std::format("Battery is not charging and battery level is {}%",mBatteryLevelProvider());
    }
private:
    std::function<double()> mBatteryLevelProvider;
};

struct WiredStatus : PowerStatusProvider{
    std::string to_string(bool Connected) const override
    {
        if (Connected)
            return "Device connected";
        else
            return "Device not connected";
    }
};

struct OutputHandler{
    OutputHandler(std::vector<OutputDevice_sp> Outputs)  
    : mOutputs(Outputs) {}
    
  void handle(std::string const & output) const {
      for (auto &&output : mOutputs) {
          output->publish(output);
      }
  } 

private:
    std::vector<OutputDevice_sp> mOutputs;
};

using PowerStatusProvider_sp = std::shared_ptr<PowerStatusProvider>;
using OutputHandler_sp = std::shared_ptr<OutputHandler>;


struct Device{
    struct Device(std::string Name, PowerStatusProvider_sp PSP, OutputHandler_sp OH)
    : mName(Name)
    , mPSP(PSP)
    , mOH(OH) 
    {}

    void update_status() const{
        mOH->handle(mPSP->to_string());
    }
  
private:
    std::string mName;
    std::shared_ptr<PowerStatusProvider> mPSP;
    std::shared_ptr<OutputHandler> mOH;
};

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接