Calculadora de área para formas como prueba de entrevista OOP -- ++ campo con object-oriented campo con interview-questions campo con polymorphism camp codereview Relacionados El problema

Area calculator for shapes as an OOP interview test


9
vote

problema

Español

hace un tiempo Una pregunta de entrevista de C ++ complicada se acercó a mí, y desde que no podía decir lo que hice mal.

Crear una clase base llamada "Forma" con un método para devolver el área de la forma. Cree una clase llamada "triángulo" derivado de la clase de la clase base ".

triángulo "de clase" debe tener dos constructores. Uno recibe la longitud del (a) lado del triángulo y la altura relacionada (MA). El otro recibe la longitud de (a), (b), (c) lados del triángulo. En el segundo caso, el constructor debe validar la entrada comprobando que la longitud de un lado es más pequeña que la suma de las longitudes de los otros dos lados. Si la entrada no es válida, debe lanzar una excepción. Implementar el método (disponible en la clase base) que calcula el área del triángulo sobre la base de los datos disponibles.

Puedes usar dos fórmulas. Si se da la longitud de un lado y la altura: T=(a*ma)/2 . Si se dan las longitudes de los tres lados, puedes usar la fórmula de la garza: sqrt(s*(s-a)*(s-b)*(s-c)) donde s=(a+b+c)/2

Esto debería ser obvio para cualquier candidato. Claramente, pregunta si sabe cuáles son los principios de la base de la OOP, y si puede resolver un problema un poco complejo con él. Antes de que pudiera saltar a ella, encontré algo extraño. La evaluación requiere que cree alguna clase que se comporte de manera diferente como han creado. Le pide que use algún engaño para resolver este problema.

Bueno, yo, que leí un poco sobre lo que es sólido, y familiarizado con los patrones de diseño era trivial que usar el polimorfismo es la forma de resolver esta la forma más elegante. Mi enfoque fue algo así:

  • Crea la clase base
  • Crea la clase derivada
  • Use el constructor de clase derivado como una fábrica abstracta para un tipo para un compuesto , y hacer que la implementación actúe como un proxy hacia los diferentes Implementaciones
  • Crea otras dos clases que maneja los datos reales y se ocupan del cálculo
  • Cuida las excepciones usando STD :: EXCEPCIÓN: estos son complicados también en C ++
  • Cuida la implementación

Al final, mi solución se veía así:

  #include <exception> #include <string> #include <cmath>  class Shape { public:     virtual double Area() = 0; };  class Exception : public std::exception { public:     Exception(std::string const &message) throw() : m_msg(message) {}     ~Exception() throw() {}     virtual char const *what() const _NOEXCEPT { return m_msg.c_str(); }  private:     std::string m_msg; };  class Triangle : public Shape {     class TriangleSides;     class TriangleBase;  public:     Triangle(double base, double height);     Triangle(double a, double b, double c);     ~Triangle();     double Area() override;  private:     Shape *m_shape;      class TriangleSides : public Shape     {     public:         TriangleSides(double a, double b, double c);         double Area() override;      private:         double m_a, m_b, m_c;     };      class TriangleBase : public Shape     {     public:         TriangleBase(double base, double height);         double Area() override;      private:         double m_base, m_height;     }; };  Triangle::Triangle(double base, double height) : m_shape(nullptr) {     m_shape = new Triangle::TriangleBase(base, height); }  Triangle::Triangle(double a, double b, double c) : m_shape(nullptr) {     if (a + b <= c || a + c <= b || b + c <= a)         throw Exception("Any two sides of a triangle must be longer than one");     m_shape = new Triangle::TriangleSides(a, b, c); }  Triangle::~Triangle() {     delete m_shape; }  double Triangle::Area() {     if (m_shape)         return m_shape->Area();     else         return 0; }  Triangle::TriangleSides::TriangleSides(double a, double b, double c) : m_a(a), m_b(b), m_c(c) { }  double Triangle::TriangleSides::Area() {     const double s = (m_a + m_b + m_c) * .5;     return sqrt(s * (s - m_a) * (s - m_b) * (s - m_c)); }  Triangle::TriangleBase::TriangleBase(double base, double height) : m_base(base), m_height(height) { }  double Triangle::TriangleBase::Area() {     return .5 * (m_base * m_height); }   
Original en ingles

A while ago a tricky C++ interview question came across to me, and ever since I could not tell what I did wrong.

Create a base class named xe2x80x9cShapexe2x80x9d with a method to return the area of the shape. Create a class named xe2x80x9cTrianglexe2x80x9d derived from the base class xe2x80x9cShapexe2x80x9d.

Class xe2x80x9cTrianglexe2x80x9d must have two constructors. One receives the length of the (a) side of the triangle and the related (ma) height. The other receives the length of (a),(b),(c) sides of the triangle. In the second case, the constructor must validate the input by checking that the length of one side is smaller than the sum of the lengths of the other two sides. If the input is invalid, it should throw an exception. Implement the method (available in the base class) that calculates the area of the triangle on the basis of the available data.

You can use two formulas. If the length of a side and height is given: T=(a*ma)/2. If the lengths of the three sides are given, you can use the Heron formula: sqrt(s*(s-a)*(s-b)*(s-c)) where s=(a+b+c)/2

This should be obvious for any candidate. It clearly asks if you know what the base principles of OOP is, and if you can solve a bit complex problem with it. Before I could just jump into it, I found something odd. The assessment requires you to create some class that behaves differently as they've created. It asks you to use some trickery to solve this problem.

Well, I, who read a bit about what SOLID is, and familiar with design patterns were trivial that using polymorphism is the way to solve this the most elegant way. My approach was something like this:

  • Create the base class
  • Create the derived class
  • Use the derived class constructor as an abstract factory for a composite type, and make the implementation to act as a proxy toward the different implementations
  • Create two other classes which handles the actual data and takes care of the calculation
  • Take care of the exceptions using std::exception - these are tricky as well in C++
  • Take care of the implementation

In the end my solution looked like this:

#include <exception> #include <string> #include <cmath>  class Shape { public:     virtual double Area() = 0; };  class Exception : public std::exception { public:     Exception(std::string const &message) throw() : m_msg(message) {}     ~Exception() throw() {}     virtual char const *what() const _NOEXCEPT { return m_msg.c_str(); }  private:     std::string m_msg; };  class Triangle : public Shape {     class TriangleSides;     class TriangleBase;  public:     Triangle(double base, double height);     Triangle(double a, double b, double c);     ~Triangle();     double Area() override;  private:     Shape *m_shape;      class TriangleSides : public Shape     {     public:         TriangleSides(double a, double b, double c);         double Area() override;      private:         double m_a, m_b, m_c;     };      class TriangleBase : public Shape     {     public:         TriangleBase(double base, double height);         double Area() override;      private:         double m_base, m_height;     }; };  Triangle::Triangle(double base, double height) : m_shape(nullptr) {     m_shape = new Triangle::TriangleBase(base, height); }  Triangle::Triangle(double a, double b, double c) : m_shape(nullptr) {     if (a + b <= c || a + c <= b || b + c <= a)         throw Exception("Any two sides of a triangle must be longer than one");     m_shape = new Triangle::TriangleSides(a, b, c); }  Triangle::~Triangle() {     delete m_shape; }  double Triangle::Area() {     if (m_shape)         return m_shape->Area();     else         return 0; }  Triangle::TriangleSides::TriangleSides(double a, double b, double c) : m_a(a), m_b(b), m_c(c) { }  double Triangle::TriangleSides::Area() {     const double s = (m_a + m_b + m_c) * .5;     return sqrt(s * (s - m_a) * (s - m_b) * (s - m_c)); }  Triangle::TriangleBase::TriangleBase(double base, double height) : m_base(base), m_height(height) { }  double Triangle::TriangleBase::Area() {     return .5 * (m_base * m_height); } 
           

Lista de respuestas

8
 
vote
vote
La mejor respuesta
 

Para un alquiler de junior, es posible que haya aceptado la respuesta, pero habría señalado las breves idas que discutiendo la respuesta y ver si el candidato podría ajustar el código en consecuencia.

Estoy de acuerdo con la evaluación de Edward que debe usar store.get0 Para la excepción, y que el método 998877766555443311 debe ser store.get2 .

Destructor virtual faltante

Como una cuestión de higiene OOP C ++, todas las clases de base con métodos virtuales también deben incluir un destructor virtual. Sin él, si el código del marco destruyó el store.get3 , el destructor de la clase derivada no se llamaría.

  store.get4  

Es cierto que un destructor virtual puede incurrir en algunos puntos generales adicionales. Pero si el código del marco necesita destruir los tipos polimórficos, las soluciones que evitan el destructor virtual son mucho más engorrosas para implementar.

Regla de tres

La solución propuesta utiliza store.get5 para asignar dinámicamente el objeto de implementación elegido que puede diferir según el constructor se invocó. Sin embargo, su implementación viola la regla de tres. En su caso, esto significa que no es seguro copiar, ni asignar a, un 99887766555443316 .

La regla de tres es que si implementa uno de los destructor , copia constructor , o Operador de asignación , lo más probable es que necesite implementar los tres.

Mientras se está discutiendo la solución propuesta, podría preguntarle al candidato lo que podría suceder si el objeto 99887766555443317 se construyó, y luego se destruye tanto la copia como la original. Dado el diseño actual, esperaría que el candidato determine que se debe implementar un constructor de copia adecuado que realiza una copia profunda.

Se le preguntará una pregunta similar con respecto a la asignación si el candidato no se dio cuenta de que se produce un problema algo similar si se asigna un 99887777765555443318 a un 998877766555443319 (que también induciría una fuga de memoria). Dado el diseño actual, esperaría que el candidato determine que un operador de asignación adecuado que libera el puntero existente y luego realiza una copia profunda que tendría que implementarse.

Esto podría llevar a una pregunta sobre la copia y el Idiago de SWAP para los operadores de asignaciones, lo que podría llevar a preguntas sobre cómo implementar una función de intercambio adecuada para una clase.

Regla de Zero

Otra forma de obedecer la regla de tres es volver a trabajar su data0 para que no sea necesario implementar un destructor. Podría desafiar al candidato a mantener la asignación dinámica, pero perder el destructor sin filtrar la memoria. Espero que el candidato se presente con el uso de un solo contenedor de elementos o, más convencionalmente, un puntero inteligente.

Evite data1

Sin embargo, otra forma de evitar implementar un destructor sería evitar la asignación dinámica por completo. La respuesta de Edward es una técnica clara para lograr esto. Sin embargo, si desea que el área se calcule cuando sea necesario, en lugar de durante la construcción, entonces solo necesita algún método para que la lógica de cálculo del área elija la fórmula correcta.

  • use lambdas y data2
    Este es un primo cercano de su elección para usar el polimorfismo, menos la complejidad de definir en realidad las clases derivadas. En su lugar, define Lambdas dentro de su constructor que se esconde en el data3 , e invocado cuando se consulta el área.

      data4  
  • Utilice un data5 Verifique
    Cambie la lógica de su área de cálculo para decidir qué fórmula usar. No necesitas almacenar una bandera para un cheque. En su lugar, puede consultar cuántas entradas están disponibles.

      data6  

¿Qué es data7 ?

De la formulación de la pregunta, no está claro si data8 está destinado a representar un 99887766655443329 . Espero que el candidato señale que la especificación de una base y altura no define en realidad un triángulo, sino una clase de triángulos que tienen un lado con una longitud específica, y cada uno con la misma área. Si el resultado de esta pregunta es que la clase 998877666555443330 se usa para calcular un área, el enfoque de Edward es absolutamente el correcto para usar.

Entradas negativas?

No está claro si el Asker tenía la intención de permitir insumos negativos. Si la entrada negativa es la Los seres no invalidan la entrada, los valores absolutos deberían usarse para validar la suma de longitudes y calcular el área.

 

For a junior hire, I may have accepted the answer, but I would have pointed out the short comings while discussing the answer, and see if the candidate could adjust the code accordingly.

I agree with Edward's assessment that you should use std::domain_error for the exception, and that the Area method should be const.

Missing Virtual Destructor

As a matter of C++ OOP hygiene, all base classes with virtual methods should also include a virtual destructor. Without it, if framework code destroyed the Shape, the destructor of the derived class would not get called.

struct Shape {     virtual double Area() const = 0;     virtual ~Shape () = default; }; 

It is true that a virtual destructor may incur some additional overhead. But if the framework code needs to destroy polymorphic types, solutions avoiding the virtual destructor are much more cumbersome to implement.

Rule of Three

The proposed solution uses new to dynamically allocate the chosen implementation object which may differ based on which constructor was invoked. However, your implementation violates the Rule of Three. In your case, this means it is not safe to copy, nor assign to, a Triangle.

The Rule of Three is that if you implement one of destructor, copy constructor, or assignment operator, you most likely need to implement all three.

While discussing the proposed solution, I might ask the candidate what might happen if the Triangle object was copy constructed, and then both the copy and the original are destructed. Given the current design, I would expect the candidate to determine that a proper copy constructor that performs a deep copy would have to be implemented.

A similar question regarding assignment would be asked if the candidate did not realize a somewhat similar problem occurs if a Triangle is assigned to a different Triangle (which would also induce a memory leak). Given the current design, I would expect the candidate to determine that a proper assignment operator that frees the existing pointer and then performs a deep copy would have to be implemented.

This might then lead to a question about the copy and swap idiom for assignment operators, which might lead to questions about how to implement a proper swap function for a class.

Rule of Zero

Another way to obey the Rule of Three is to rework your Triangle so that it does not need to implement a destructor. I might challenge the candidate to maintain the dynamic allocation, but lose the destructor without leaking memory. I would expect the candidate to come up with using a single element container or, more conventionally, a smart pointer.

Avoid new

However, another way to avoid implementing a destructor would be to avoid dynamic allocation altogether. Edward's answer is a clear technique to achieving this. However, if you want the area to be computed when needed rather than during construction, then you just need some method to let the area calculation logic choose the right formula.

  • Use lambdas and std::function
    This is a close cousin of your choice to use polymorphism, minus the complexity of actually defining derived classes. Instead, you define lambdas within your constructor that get stashed into the Triangle, and invoked when the area is queried.

    class Triangle : Shape {     std::function<double()> area_;     static double area(double a, double am) { return (a * am)/2; }     static double area(double a, double b, double c) {         double s = (a + b + c)/2;         return std::sqrt(s*(s-a)*(s-b)*(s-c));     } public:     Triangle(double base, double height)     : area_{[base,height](){return area(base,height);}} {}     Triangle(double a, double b, double c)     : area_{[a,b,c](){return area(a,b,c);}} {         double s = a + b + c;         if ((s > 2*a) && (s > 2*b) && (s > 2*c)) return;         throw std::domain_error(             "Any two sides of a triangle must be longer than one");     }     double Area() const override { return area_();} }; 
  • Use an if check
    Change your area calculation logic to decide which formula to use. You don't need to store a flag for such a check. Instead, you can query how many inputs are available.

    class Triangle : Shape {     std::vector<double> in_;     static double area(double a, double am) { return (a * am)/2; }     static double area(double a, double b, double c) {         double s = (a + b + c)/2;         return std::sqrt(s*(s-a)*(s-b)*(s-c));     } public:     Triangle(double base, double height)     : in_{base, height} {}     Triangle(double a, double b, double c)     : in_{a, b, c} {         double s = a + b + c;         if ((s > 2*a) && (s > 2*b) && (s > 2*c)) return;         throw std::domain_error(             "Any two sides of a triangle must be longer than one");     }     double Area() const override {         return (in_.size() < 3                 ? area(in_[0], in_[1])                 : area(in_[0], in_[1], in_[2]));     } }; 

What is Triangle?

From the formulation of the question, it is not clear if Triangle is meant to represent a Shape. I would hope the candidate would point out that the specification of a base and height does not actually define a triangle, but a class of triangles that have a side with a specific length, and each having the same area. If the outcome of this question is that the Triangle class is only ever used to compute an area, then Edward's approach is absolutely the correct one to use.

Negative Inputs?

It is unclear if the asker intended to allow negative inputs. If negative inputs themselves do not invalidate the input, then absolute values would have to be used to validate the sum of lengths and to compute the area.

 
 
11
 
vote

Veo algunas cosas que pueden ayudarlo a mejorar su código.

No supere su código

Lo único requerido del window.store1 y window.store32 Las clases son el cálculo del área. Por esa razón, el código se puede simplificar considerablemente si el constructor calcula y almacena el área.

Uso window.store3 Donde sea práctico

El window.store4 Probablemente no debe modificar el objeto 9988777665554433355554433335 subyacente, por lo que tiene sentido declararlo window.store6 .

Use una excepción estándar cuando corresponda

Realmente no hay nada, bueno ... Excepcional Acerca del window.store7 clase, por lo que no se puede requerir realmente. Tal vez window.store8 se puede usar en su lugar así:

  window.store9  

Proporcionar un controlador de prueba

Se trata más de obtener una buena revisión en lugar del propio código, pero a menudo es bueno dar a los revisores un ejemplo completo y compilable para permitirles ver el código en contexto. Esto es lo que usé:

  $(function () { ... });0  

Uso $(function () { ... });1 Para las funciones matemáticas

La función $(function () { ... });22 se encuentra a menudo en el espacio de nombres global, pero se requiere que esté en el espacio de nombres 998877766554433343 . Por esa razón, es probable que sea más seguro que escriba $(function () { ... });4 que solo $(function () { ... });5 .

Use palabras clave estándar

en lugar de $(function () { ... });6 que no es estándar, el código podría usar $(function () { ... });7 .

Ejemplo reelaborado

  $(function () { ... });8  
 

I see some things that may help you improve your code.

Don't overcomplicate your code

The only thing required of the Shape and Triangle classes is the calculation of area. For that reason, the code can be considerably simplified if the constructor calculates and stores the area.

Use const where practical

The Area() function probably shouldn't alter the underlying Shape object, so it makes sense to declare it const.

Use a standard exception where appropriate

There's really nothing, well... exceptional about the Exception class, so it seems not really to be required. Maybe std::domain_error could be used instead like this:

throw std::domain_error("Any two sides of a triangle must be longer than one"); 

Provide a test driver

This is more about getting a good review rather than the code itself, but it's often good to give reviewers a complete, compileable example to allow them to see the code in context. Here's what I used:

#include <iostream> int main() {     Triangle t1{3, 4, 5};     std::cout << t1.Area() << '\n';     try {         Triangle t2{3, 4, 15};     }     catch (std::exception &e) {         std::cout << e.what() << '\n';     }     Triangle t3{3, 4};     std::cout << t3.Area() << '\n'; } 

Use std:: for math functions

The sqrt function is often in the global namespace, but it's required to be in the std namespace. For that reason, it's probably safer to write std::sqrt than just sqrt.

Use standard keywords

Instead of _NOEXCEPT which is not standard, the code could use noexcept.

Reworked example

#include <string> #include <cmath> #include <stdexcept>  class Shape { public:     virtual double Area() const = 0; };  class Triangle : public Shape { public:     Triangle(double base, double height);     Triangle(double a, double b, double c);     double Area() const override { return area;}  private:     double area; };  Triangle::Triangle(double base, double height)      : area{(base * height)/2} { }  Triangle::Triangle(double a, double b, double c)      : area{(a-b-c)*(-a+b-c)*(a+b-c)*(a+b+c)/16} {     if (area < 0)         throw std::domain_error("Any two sides of a triangle must be longer than one");     area = std::sqrt(area); } 
 
 
   
   

Relacionados problema

20  Un contenedor polimórfico similar a un vector para almacenar objetos de una jerarquía de tipo en memoria contigua  ( A vector like polymorphic container to store objects from a type hierarchy in co ) 
normalmente, para tener una colección polimórfica, almacenamos punteros a una clase base: std::vector<base*> v; // or std::vector<std::unique_ptr<...

5  Gestionar estilos de Excel con el enfoque VBA OOP  ( Manage excel styles with vba oop approach ) 
Quiero administrar los estilos de Excel de una manera más flexible con acciones a granel y al mismo tiempo mejorar mis conceptos de OOP recién adquiridos. O...

3  Sistema de banca utilizando polimorfismo  ( Banking system using polymorphism ) 
Le estoy preguntando esto, porque después de estudiar, estoy confundido de que es necesario declarar que declare objeto de clase base, luego declare el objeto...

1  Patrón de creación de autos genéricos para minimizar GC  ( Generic car instantiating pattern to minimize gc ) 
Estoy enfrentando el problema de necesitar instanciar a muchos autos, y me gustaría reciclar los autos ya no se usan. Cada uno de mis tipos de automóviles, ...

4  Simple Trie Class en C ++  ( Simple trie class in c ) 
Estaría muy agradecido de obtener algunos pensamientos sobre esta implementación de juguetes de un Trie que escribí para practicar mi C ++. Algunas pregunta...

10  Herencia de múltiples niveles estática con CRTP (punteros intrusivos rápidos)  ( Static multilevel inheritance with crtp fast intrusive pointers ) 
Quería tener una manera de poder poder static_cast al puntero de tipo derivado adecuado de una clase base para simular una llamada virtual. Aquí la llamada ...

4  Insertando y mostrando libros  ( Inserting and displaying books ) 
Estoy tratando de escribir un programa donde puede insertar y mostrar algunos libros (sin usar una base de datos). por hacer esto, uso tres clases: Book...

0  Entendiendo polimorfismo en Python  ( Understanding polymorphism in python ) 
Siento que no entiendo cómo funciona el polimorfismo en Python. Aquí hay algo que puse juntos. class _Tag(object): def __init__(self): #Disjoin...

8  Colección polimórfica basada en la interfaz  ( Interface based polymorphic collection ) 
Aquí hay una colección de utilidad C ++ 11 pequeña para almacenar cualquier objeto que cumpla con una interfaz determinada. Solo se proporcionan los conceptos...

14  Dilema de diseño: extensibilidad vs simplicidad  ( Design dilemma extensibility vs simplicity ) 
Aquí está mi problema abstraído en Bird Clases. Sé que el número de Bird se incrementará en el futuro y podrían ser necesarios nuevos comportamientos. Con...




© 2022 respuesta.top Reservados todos los derechos. Centro de preguntas y respuestas reservados todos los derechos