简体   繁体   English

Object 面向 model 通用器件的设计

[英]Object oriented design to model generic device

I was asked below object oriented system design question in an interview.在一次采访中,我被问到了下面 object 面向系统设计的问题。

There are multiple devices like Echo show, Echo dot, Echo tab, Smart microwave, Fire tv stick etc.有多种设备,如 Echo show、Echo dot、Echo tab、Smart 微波炉、Fire tv Stick 等。

  • Echo show - It has display screen and speaker. Echo show - 它有显示屏和扬声器。 It works on electric power supply.它适用于电源。
  • Echo dot - It has speaker.回声点 - 它有扬声器。 It works on electric supply.它适用于电力供应。
  • Echo tab - It has speaker.回声选项卡 - 它有扬声器。 It works on battery supply.它适用于电池供电。 Battery can be charged.电池可以充电。
  • Smart microwave - It has screen display.智能微波炉 - 它有屏幕显示。 It works on electric supply.它适用于电力供应。
  • Fire tv stick - It has speaker.消防电视棒 - 它有扬声器。 It works on electric supply.它适用于电力供应。

So basically these are 3 categories like - speaker / screen display / both speaker and screen display There are two categories like - electric supply / battery supply.所以基本上这些是 3 类,如 - 扬声器/屏幕显示/扬声器和屏幕显示有两个类别,如 - 电源/电池供应。 There can be queries on any of these devices like print status.可以在任何这些设备上进行查询,例如打印状态。 Here are possible outputs for each of this device -以下是每个设备的可能输出 -

  • Echo show - "It is on charging" or "It is not charging" depending on whether it is connected to electric supply or not.回声显示 - “它正在充电”或“它不充电”取决于它是否连接到电源。 This output should come on screen and speaker.这个 output 应该出现在屏幕和扬声器上。
  • Echo dot - "It is on charging" or "It is not charging" depending on whether it is connected to electric supply or not.回声点 - “它正在充电”或“它没有充电”,具体取决于它是否连接到电源。 This output should come on speaker.这个 output 应该出现在扬声器上。
  • Echo tab - "Battery is charging" or "Battery is not charging and battery level is 70%" depending on whether battery is charging or not. Echo 选项卡 - “电池正在充电”或“电池未充电且电池电量为 70%”,具体取决于电池是否正在充电。 This output should come on speaker.这个 output 应该出现在扬声器上。
  • Smart microwave - "It is on charging" or "It is not charging" depending on whether it is connected to electric supply or not.智能微波炉 - “它正在充电”或“它没有充电”取决于它是否连接到电源。 This output should come on screen.这个 output 应该出现在屏幕上。
  • Fire tv stick - "It is on charging" or "It is not charging" depending on whether it is connected to electric supply or not.消防电视棒 - “它正在充电”或“它不充电”取决于它是否连接到电源。 This output should come on speaker.这个 output 应该出现在扬声器上。

Assume that there are inbuilt classes for speaking and printing on screen.假设有用于在屏幕上说话和打印的内置类。 If we pass string to these class objects they will do respective job.如果我们将字符串传递给这些 class 对象,它们将完成相应的工作。

Now write 4-5 classes to model this scenario.现在写4-5个类到model这个场景。

Design should be extensible meaning if tomorrow any new device comes with a new combination then it can be implemented without creating any new class.设计应该是可扩展的,这意味着如果明天任何新设备带有新组合,那么它可以在不创建任何新 class 的情况下实现。 So you should not be creating class for each device.因此,您不应该为每个设备创建 class。

Here is my object oriented solution but interviewer is not happy with it particularly with vector<Output*> outputs .这是我的面向 object 的解决方案,但面试官对此并不满意,尤其是对于vector<Output*> outputs He was suggesting to use some design pattern instead of vector.他建议使用一些设计模式而不是矢量。 Can you think of a better solution?你能想出更好的解决方案吗?

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;
     }
};

vector<Output> doesn't allow for inheritance because it stores Output s directly instead pointers or references. vector<Output>不允许 inheritance 因为它直接存储Output而不是指针或引用。 If you store a Display or Speaker in the vector it will be sliced .如果您将DisplaySpeaker存储在向量中,它将被切片

Since outputs are unique to each device, I would make that a vector of unique pointers:由于每个设备的输出都是唯一的,因此我将其设为唯一指针的向量:

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

If I were in this interview, I would talk at some length about how "model this" is an invalid directive.如果我在这次采访中,我会详细讨论“建模 this”是如何成为无效指令的。 Every interface reflects the requirements of the consumer and the consumer is not described...每一个界面都反映了消费者的需求,没有描述消费者……

So we have to assume that there is something that needs to have a pointer to any of these things and then use any of the capabilities that it provides.所以我们必须假设有些东西需要有一个指向这些东西的指针,然后使用它提供的任何功能。 Sure, Device is a good name for this.当然, Device是个好名字。

A consumer is not going to consider all outputs to be the same so, this vector<Output> doesn't appear to be very useful.消费者不会认为所有输出都是相同的,所以这个vector<Output>似乎不是很有用。 I would imagine that the consumer of the Device interface needs something like this:我想Device接口的消费者需要这样的东西:

class Device {
    public:
    virtual ~Device() = 0;

    // Get a pointer to the primary display (e.g., for playing video), or nullptr if there isn't one
    // It's only valid while this Device exists
    virtual Display *getPrimaryDisplay() {
        return nullptr;
    }

    // Get a pointer to the audio output, or nullptr if there isn't one
    // It's only valid while this Device exists
    virtual AudioOut *getAudioOutput() {
        return nullptr;
    }

    // Get a pointer to the charge status, or nullptr if there isn't one
    // It's only valid while this Device exists
    virtual ChargeStatus *getChargeStatus() {
        return nullptr;
    }

    // Send a notification or message to the user via the primary
    // channel (text display and/or text to speech)
    virtual void message(const std::string &msg);
}

Note that we use Interface Segregation to separate out the various capabilities that the consumer might want to use.请注意,我们使用接口隔离来分离消费者可能想要使用的各种功能。

We also provide default implementations of these things so that when we add new ones, we don't invalidate existing Device implementations.我们还提供了这些东西的默认实现,这样当我们添加新的时,我们不会使现有的Device实现无效。 If all the implementations are in the same source repo, though, we would want to make these pure virtual so that adding a new kind of service would force a decision about whether to support it or not for each device.但是,如果所有实现都在同一个源代码库中,我们希望将这些纯虚拟化,以便添加一种新的服务将强制决定是否为每个设备支持它。

If we wanted to support both, then we'd provide a pure virtual interface and a base abstract implementation that returns nullptr for all services.如果我们想同时支持两者,那么我们将提供一个纯虚拟接口和一个为所有服务返回 nullptr 的基本抽象实现。

On the other hand, if we had to maintain binary backward compatibility to 3rd party Device implementations, then we would use a registration system instead of explicitly enumerating the possible services in this interface.另一方面,如果我们必须保持对第 3 方Device实现的二进制向后兼容性,那么我们将使用注册系统,而不是在此接口中显式枚举可能的服务。

Then you need implementations of this interface for each device.然后,您需要为每个设备实现此接口。 For Echo show, for example:以 Echo 节目为例:

class EchoShow : public Device {
   // implements Display
   std::unique_ptr<EchoShowDisplay> m_display;
   // implements AudioOut
   std::unique_ptr<EchoShowSpeaker> m_speaker;

   public:
   Display *getPrimaryDisplay() {
       return m_display.get();
   }

   AudioOutput *getAudioOutput() {
       return m_speaker.get();
   }

   // No ChargeStatus, since there's no battery -- inherit default nullptr

   void message(const std::string &msg) {
       m_display.showAlert(msg);
       // play bell and message at high priority to interrupt
       // any streaming audio, etc.
       m_speaker.queueSound("BELL", PRIORITY_NOTICE);
       m_speaker.queueSpeech(msg, PRIORITY_NOTICE);
   }

I do not think the interviewers request to use some design pattern was in any way constructive.我不认为面试官要求使用某种设计模式有任何建设性。 Design patterns are tools to accomplish something;设计模式是完成某事的工具; without a goal a design pattern is meaningless.没有目标,设计模式就毫无意义。 Instead he could have said: " We are expecting to have to construct a lot of similar devices with minimal deviations. How would you accomplish that? " insted of " Now tag on a random design pattern and we are golden. ".相反,他本可以说:“我们预计必须以最小的偏差构建许多类似的设备。你将如何做到这一点? ”插入“现在标记随机设计模式,我们是黄金。 ”。

Apart from the technical issue John Kugelman answered here .除了John Kugelman 在这里回答的技术问题。 I think there are several design patterns you could have used instead of just passing in a vector of devices and power supplies.我认为您可以使用几种设计模式,而不仅仅是传入设备和电源的向量。 Some examples from the top of my head:我脑海中的一些例子:

Factory pattern工厂模式

Keywords for research: Factory pattern研究关键词:工厂模式

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

Builder pattern生成器模式

Keywords for research: Builder pattern研究关键词:建造者模式

Depending on the complexity and use case you could integrate the Device builder into the device.根据复杂性和用例,您可以将设备构建器集成到设备中。 For simplicity I've done it this way.为简单起见,我已经这样做了。

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();
};

Additional patterns and combinations (I was expecting to come up with more than one. Maybe someone can edit more in.)额外的模式和组合(我期待想出不止一个。也许有人可以编辑更多。)

Singleton list of devices (antipattern in most cases) Singleton 设备列表(大多数情况下为反模式)

Depending on the usecase you could also have any thing (method, container, class etc.) holding your predefined 'Devices' if you only need one each per application.根据用例,如果每个应用程序只需要一个,您还可以拥有任何东西(方法、容器、class 等)来保存您预定义的“设备”。

My opinion is that the part with PowerSupply and Output is okay.我的观点是 PowerSupply 和PowerSupply的部分没Output

The Device design I would have done differently but it is a valid design nonetheless.我会做不同的Device设计,但它仍然是一个有效的设计。

I would also have used std::shared_ptr everywhere.我也会在任何地方使用std::shared_ptr std::unique_ptr is sometimes tricky for beginners so I prefer not to use it. std::unique_ptr有时对初学者来说很棘手,所以我不想使用它。

As per factory as another commenter suggested, I would just create a static method inside the Device class, it's the easiest way.根据另一位评论者建议的工厂,我只需在设备 class 内创建一个 static 方法,这是最简单的方法。

I would have done this way:我会这样做:


///////////////////////////////////////////////////////////////
// Device.h
...
using PowerSupplyPtr = std::shared_ptr<PowerSupply>;
using OutputPtr = std::shared_ptr<Output>;
using OutputList = std::list< OutputPtr >;

class Device {
public:
    Device();
    virtual ~Device();
    virtual PowerSupplyPtr getPowerSupply() = 0;
    virtual OutputList getOutputList() = 0;
    static DevicePtr create( const std::string& name );
};

///////////////////////////////////////////////////////////////
// EchoShow.h
#include "Device.h"
class EchoShow : public Device {
public:
    EchoShow();
    virtual PowerSupplyPtr getPowerSupply() override;
    virtual OutputList getOutputList() override;
private:
    PowerSupplyPtr _power;
    OutputPtr _sound;
    OutputPtr _wifi;
};
using DevicePtr = std::shared_ptr<Device>; 

///////////////////////////////////////////////////////////////
// EchoShow.cpp
#include "EchoShow.h"
EchoShow::EchoShow() 
 : _power( new PowerSupplyAwesome )
 , _sound( new SoundOutput )
 , _wifi( new WifiOutput ) {}

PowerSupplyPtr EchoShow::getPowerSupply() {
    return _power;
}

OutputList EchoShow::getOutputList() {
   return OutputList{ { _sound, _wifi } };
}

///////////////////////////////////////////////////////////////
// Device.cpp 
#include "Device.h"
#include "EchoShow.h"
#include "EchoDot.h"

DevicePtr Device::create( const std::string& name ) {
     if ( name == "Echo Show" ) return DevicePtr( new EchoShow );
     if ( name == "Echo Dot" ) return DevicePtr( new EchoDot );
     return {};
}

I suggest introducing the following 5 classes as shown below:我建议引入以下5个类,如下所示:

  • PowerStatusProvider -> interface/policy class for power status and such PowerStatusProvider -> 接口/策略 class 用于电源状态等
  • BatteryStatus -> concrete implementation for all things battery powered BatteryStatus -> 电池供电的所有事物的具体实现
  • WiredStatus -> concrete implementation for all things powered by a wire WiredStatus -> 由电线供电的所有事物的具体实现
  • OutputHandler -> publish status to whatever endpoint using provided services (Screen, Speaker) OutputHandler -> 使用提供的服务(屏幕、扬声器)将状态发布到任何端点
  • Device -> The actual abstraction to your gadget设备 -> 对您的小工具的实际抽象

I used structs instead of classes to shorten notation.我使用结构而不是类来缩短符号。 The battery level is retrieved via a dependency injected functor.电池电量是通过依赖注入函子检索的。 C++20 has std::format, maybe your compiler has that as well. 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;
};

you can use observer pattern here.你可以在这里使用观察者模式。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM